# Stake Module ## Overview The stake module is tasked with various core staking functionality. Through the stake module atoms may be bonded, delegated, and provisions/rewards are distributed. Atom provisions are distributed to validators and their delegators through share distribution of a collective pool of all staked atoms. As atoms are created they are added to the common pool and each share become proportionally worth more atoms. Fees are distributed through a similar pooling mechanism but where each validator and delegator maintains an adjustment factor to determine the true proportion of fees they are entitled too. This adjustment factor is updated for each delegator and validator for each block where changes to the voting power occurs in the network. Broken down, the stake module at a high level is responsible for: - Declaration of candidacy for becoming a validator - Updating Tendermint validating power to reflect slashable stake - Delegation and unbonding transactions - Implementing unbonding period - Provisioning Atoms - Managing and distributing transaction fees - Providing the framework for validator commission on delegators ### Transaction Overview Available Transactions: - TxDeclareCandidacy - TxEditCandidacy - TxLivelinessCheck - TxProveLive - TxDelegate - TxUnbond - TxRedelegate ## Global State `Params` and `GlobalState` represent the global persistent state of Gaia. `Params` is intended to remain static whereas `GlobalState` is anticipated to change each block. ``` golang type Params struct { HoldBonded Address // account where all bonded coins are held HoldUnbonded Address // account where all delegated but unbonded coins are held InflationRateChange rational.Rational // maximum annual change in inflation rate InflationMax rational.Rational // maximum inflation rate InflationMin rational.Rational // minimum inflation rate GoalBonded rational.Rational // Goal of percent bonded atoms ReserveTax rational.Rational // Tax collected on all fees MaxVals uint16 // maximum number of validators AllowedBondDenom string // bondable coin denomination // gas costs for txs GasDeclareCandidacy int64 GasEditCandidacy int64 GasDelegate int64 GasRedelegate int64 GasUnbond int64 } ``` ``` golang type GlobalState struct { TotalSupply int64 // total supply of atom tokens BondedShares rational.Rat // sum of all shares distributed for the BondedPool UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool BondedPool int64 // reserve of bonded tokens UnbondedPool int64 // reserve of unbonded tokens held with candidates InflationLastTime int64 // timestamp of last processing of inflation Inflation rational.Rat // current annual inflation rate DateLastCommissionReset int64 // unix timestamp for last commission accounting reset FeePool coin.Coins // fee pool for all the fee shares which have already been distributed ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use Adjustment rational.Rat // Adjustment factor for calculating global fee accum } ``` ### The Queue The queue is ordered so the next to unbond/re-delegate is at the head. Every tick the head of the queue is checked and if the unbonding period has passed since `InitHeight` commence with final settlement of the unbonding and pop the queue. All queue elements used for unbonding share a common struct: ``` golang type QueueElem struct { Candidate crypto.PubKey InitHeight int64 // when the queue was initiated } ``` Each `QueueElem` is persisted in the store until it is popped from the queue. ## Validator-Candidate The `Candidate` struct holds the current state and some historical actions of validators or candidate-validators. ``` golang type Candidate struct { Status CandidateStatus PubKey crypto.PubKey GovernancePubKey crypto.PubKey Owner Address GlobalStakeShares rational.Rat IssuedDelegatorShares rational.Rat RedelegatingShares rational.Rat VotingPower rational.Rat Commission rational.Rat CommissionMax rational.Rat CommissionChangeRate rational.Rat CommissionChangeToday rational.Rat ProposerRewardPool coin.Coins Adjustment rational.Rat Description Description } type CandidateStatus byte const ( VyingUnbonded CandidateStatus = 0x00 VyingUnbonding CandidateStatus = 0x01 Bonded CandidateStatus = 0x02 KickUnbonding CandidateStatus = 0x03 KickUnbonded CandidateStatus = 0x04 ) type Description struct { Name string DateBonded string Identity string Website string Details string } ``` Candidate parameters are described: - Status: signal that the candidate is either vying for validator status either unbonded or unbonding, an active validator, or a kicked validator either unbonding or unbonded. - PubKey: separated key from the owner of the candidate as is used strictly for participating in consensus. - Owner: Address where coins are bonded from and unbonded to - GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if `Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` if `Candidate.Status` is otherwise - IssuedDelegatorShares: Sum of all shares issued to delegators (which includes the candidate's self-bond) which represent each of their stake in the Candidate's `GlobalStakeShares` - RedelegatingShares: The portion of `IssuedDelegatorShares` which are currently re-delegating to a new validator - VotingPower: Proportional to the amount of bonded tokens which the validator has if the validator is within the top 100 validators. - Commission: The commission rate of fees charged to any delegators - CommissionMax: The maximum commission rate which this candidate can charge each day from the date `GlobalState.DateLastCommissionReset` - CommissionChangeRate: The maximum daily increase of the candidate commission - CommissionChangeToday: Counter for the amount of change to commission rate which has occurred today, reset on the first block of each day (UTC time) - ProposerRewardPool: reward pool for extra fees collected when this candidate is the proposer of a block - Adjustment factor used to passively calculate each validators entitled fees from `GlobalState.FeePool` - Description - Name: moniker - DateBonded: date determined which the validator was bonded - Identity: optional field to provide a signature which verifies the validators identity (ex. UPort or Keybase) - Website: optional website link - Details: optional details validator candidacy can be declared using the `TxDeclareCandidacy` transaction. During this transaction a self-delegation transaction is executed to bond tokens which are sent in with the transaction. ``` golang type TxDeclareCandidacy struct { PubKey crypto.PubKey Amount coin.Coin GovernancePubKey crypto.PubKey Commission rational.Rat CommissionMax int64 CommissionMaxChange int64 Description Description } ``` For all subsequent self-bonding, whether self-bonding or delegation the `TxDelegate` function should be used. In this context `TxUnbond` is used to unbond either delegation bonds or validator self-bonds. If either the `Description` (excluding `DateBonded` which is constant), `Commission`, or the `GovernancePubKey` need to be updated, the `TxEditCandidacy` transaction should be sent from the owner account: ``` golang type TxEditCandidacy struct { GovernancePubKey crypto.PubKey Commission int64 Description Description } ``` ### Persistent State Within the store, each `Candidate` is stored by validator-pubkey. - key: validator-pubkey - value: `Candidate` object A second key-value pair is also persisted in order to quickly sort though the group of all candidates, this second index is however not persisted through the merkle store. - key: `Candidate.GlobalStakeShares` - value: `Candidate.PubKey` When the set of all validators needs to be determined from the group of all candidates, the top candidates, sorted by GlobalStakeShares can be retrieved from this sorting without the need to retrieve the entire group of candidates. When validators are kicked from the validator set they are removed from this list. ### New Validators The validator set is updated in the first block of every hour. Validators are taken as the first `GlobalState.MaxValidators` number of candidates with the greatest amount of staked atoms who have not been kicked from the validator set. ### Kicked Validators Unbonding of an entire validator-candidate to a temporary liquid account occurs under the scenarios: - not enough stake to be within the validator set - the owner unbonds all of their staked tokens - validator liveliness issues - crosses a self-imposed safety threshold - minimum number of tokens staked by owner - minimum ratio of tokens staked by owner to delegator tokens When this occurs delegator's tokens do not unbond to their personal wallets but begin the unbonding process to a pool where they must then transact in order to withdraw to their respective wallets. The following unbonding will use the following queue element ``` golang type QueueElemUnbondCandidate struct { QueueElem } ``` If a delegator chooses to initiate an unbond or re-delegation of their shares while a candidate-unbond is commencing, then that unbond/re-delegation is subject to a reduced unbonding period based on how much time those funds have already spent in the unbonding queue. #### Liveliness issues Liveliness issues are calculated by keeping track of the block precommits in the block header. A queue is persisted which contains the block headers from all recent blocks for the duration of the unbonding period. A validator is defined as having livliness issues if they have not been included in more than 33% of the blocks over: - The most recent 24 Hours if they have >= 20% of global stake - The most recent week if they have = 0% of global stake - Linear interpolation of the above two scenarios Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is submitted. ``` golang type TxLivelinessCheck struct { PubKey crypto.PubKey RewardAccount Addresss } ``` If the `TxLivelinessCheck is successful in kicking a validator, 5% of the liveliness punishment is provided as a reward to `RewardAccount`. #### Validator Liveliness Proof If the validator was kicked for liveliness issues and is able to regain liveliness then all delegators in the temporary unbonding pool which have not transacted to move will be bonded back to the now-live validator and begin to once again collect provisions and rewards. Regaining livliness is demonstrated by sending in a `TxProveLive` transaction: ``` golang type TxProveLive struct { PubKey crypto.PubKey } ``` ## Delegator bond Atom holders may delegate coins to validators, under this circumstance their funds are held in a `DelegatorBond`. It is owned by one delegator, and is associated with the shares for one validator. The sender of the transaction is considered to be the owner of the bond, ``` golang type DelegatorBond struct { Candidate crypto.PubKey Shares rational.Rat AdjustmentFeePool coin.Coins AdjustmentRewardPool coin.Coins } ``` Description: - Candidate: pubkey of the validator candidate: bonding too - Shares: the number of shares received from the validator candidate - AdjustmentFeePool: Adjustment factor used to passively calculate each bonds entitled fees from `GlobalState.FeePool` - AdjustmentRewardPool: Adjustment factor used to passively calculate each bonds entitled fees from `Candidate.ProposerRewardPool`` Each `DelegatorBond` is individually indexed within the store by delegator address and candidate pubkey. - key: Delegator and Candidate-Pubkey - value: DelegatorBond ### Delegating Delegator bonds are created using the TxDelegate transaction. Within this transaction the validator candidate queried with an amount of coins, whereby given the current exchange rate of candidate's delegator-shares-to-atoms the candidate will return shares which are assigned in `DelegatorBond.Shares`. ``` golang type TxDelegate struct { PubKey crypto.PubKey Amount coin.Coin } ``` ### Unbonding Delegator unbonding is defined by the following transaction type: ``` golang type TxUnbond struct { PubKey crypto.PubKey Shares rational.Rat } ``` When unbonding is initiated, delegator shares are immediately removed from the candidate and added to a queue object. ``` golang type QueueElemUnbondDelegation struct { QueueElem Payout Address // account to pay out to Shares rational.Rat // amount of shares which are unbonding StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation } ``` In the unbonding queue - the fraction of all historical slashings on that validator are recorded (`StartSlashRatio`). When this queue reaches maturity if that total slashing applied is greater on the validator then the difference (amount that should have been slashed from the first validator) is assigned to the amount being paid out. ### Re-Delegation The re-delegation command allows delegators to switch validators while still receiving equal reward to as if you had never unbonded. ``` golang type TxRedelegate struct { PubKeyFrom crypto.PubKey PubKeyTo crypto.PubKey Shares rational.Rat } ``` When re-delegation is initiated, delegator shares remain accounted for within the `Candidate.Shares`, the term `RedelegatingShares` is incremented and a queue element is created. ``` golang type QueueElemReDelegate struct { QueueElem Payout Address // account to pay out to Shares rational.Rat // amount of shares which are unbonding NewCandidate crypto.PubKey // validator to bond to after unbond } ``` During the unbonding period all unbonding shares do not count towards the voting power of a validator. Once the `QueueElemReDelegation` has reached maturity, the appropriate unbonding shares are removed from the `Shares` and `RedelegatingShares` term. Note that with the current menchanism a delegator cannot redelegate funds which are currently redelegating. ### Cancel Unbonding A delegator who is in the process of unbonding from a validator may use the re-delegate transaction to bond back to the original validator they're currently unbonding from (and only that validator). If initiated, the delegator will immediately begin to one again collect rewards from their validator. ## Provision Calculations Every hour atom provisions are assigned proportionally to the each slashable bonded token which includes re-delegating atoms but not unbonding tokens. Validation provisions are payed directly to a global hold account (`BondedTokenPool`) and proportions of that hold account owned by each validator is defined as the `GlobalStakeBonded`. The tokens are payed as bonded tokens. Here, the bonded tokens that a candidate has can be calculated as: ``` globalStakeExRate = params.BondedTokenPool / params.IssuedGlobalStakeShares candidateCoins = candidate.GlobalStakeShares * globalStakeExRate ``` If a delegator chooses to add more tokens to a validator then the amount of validator shares distributed is calculated on exchange rate (aka every delegators shares do not change value at that moment. The validator's accounting of distributed shares to delegators must also increased at every deposit. ``` delegatorExRate = validatorCoins / candidate.IssuedDelegatorShares createShares = coinsDeposited / delegatorExRate candidate.IssuedDelegatorShares += createShares ``` Whenever a validator has new tokens added to it, the `BondedTokenPool` is increased and must be reflected in the global parameter as well as the validators `GlobalStakeShares`. This calculation ensures that the worth of the `GlobalStakeShares` of other validators remains worth a constant absolute amount of the `BondedTokenPool` ``` createdGlobalStakeShares = coinsDeposited / globalStakeExRate validator.GlobalStakeShares += createdGlobalStakeShares params.IssuedGlobalStakeShares += createdGlobalStakeShares params.BondedTokenPool += coinsDeposited ``` Similarly, if a delegator wanted to unbond coins: ``` coinsWithdrawn = withdrawlShares * delegatorExRate destroyedGlobalStakeShares = coinsWithdrawn / globalStakeExRate validator.GlobalStakeShares -= destroyedGlobalStakeShares params.IssuedGlobalStakeShares -= destroyedGlobalStakeShares params.BondedTokenPool -= coinsWithdrawn ``` Note that when an re-delegation occurs the shares to move are placed in an re-delegation queue where they continue to collect validator provisions until queue element matures. Although provisions are collected during re-delegation, re-delegation tokens do not contribute to the voting power of a validator. Validator provisions are minted on an hourly basis (the first block of a new hour). The annual target of between 7% and 20%. The long-term target ratio of bonded tokens to unbonded tokens is 67%. The target annual inflation rate is recalculated for each previsions cycle. The inflation is also subject to a rate change (positive of negative) depending or the distance from the desired ratio (67%). The maximum rate change possible is defined to be 13% per year, however the annual inflation is capped as between 7% and 20%. ``` inflationRateChange(0) = 0 annualInflation(0) = 0.07 bondedRatio = bondedTokenPool / totalTokenSupply AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13 annualInflation += AnnualInflationRateChange if annualInflation > 0.20 then annualInflation = 0.20 if annualInflation < 0.07 then annualInflation = 0.07 provisionTokensHourly = totalTokenSupply * annualInflation / (365.25*24) ``` Because the validators hold a relative bonded share (`GlobalStakeShare`), when more bonded tokens are added proportionally to all validators the only term which needs to be updated is the `BondedTokenPool`. So for each previsions cycle: ``` params.BondedTokenPool += provisionTokensHourly ``` ## Fee Calculations Collected fees are pooled globally and divided out passively to validators and delegators. Each validator has the opportunity to charge commission to the delegators on the fees collected on behalf of the delegators by the validators. Fees are paid directly into a global fee pool. Due to the nature of of passive accounting whenever changes to parameters which affect the rate of fee distribution occurs, withdrawal of fees must also occur. - when withdrawing one must withdrawal the maximum amount they are entitled too, leaving nothing in the pool, - when bonding, unbonding, or re-delegating tokens to an existing account a full withdrawal of the fees must occur (as the rules for lazy accounting change), - when a candidate chooses to change the commission on fees, all accumulated commission fees must be simultaneously withdrawn. When the validator is the proposer of the round, that validator (and their delegators) receives between 1% and 5% of fee rewards, the reserve tax is then charged, then the remainder is distributed socially by voting power to all validators including the proposer validator. The amount of proposer reward is calculated from pre-commits Tendermint messages. All provision rewards are added to a provision reward pool which validator holds individually. Here note that `BondedShares` represents the sum of all voting power saved in the `GlobalState` (denoted `gs`). ``` proposerReward = feesCollected * (0.01 + 0.04 * sumOfVotingPowerOfPrecommitValidators / gs.BondedShares) candidate.ProposerRewardPool += proposerReward reserveTaxed = feesCollected * params.ReserveTax gs.ReservePool += reserveTaxed distributedReward = feesCollected - proposerReward - reserveTaxed gs.FeePool += distributedReward gs.SumFeesReceived += distributedReward gs.RecentFee = distributedReward ``` The entitlement to the fee pool held by the each validator can be accounted for lazily. First we must account for a candidate's `count` and `adjustment`. The `count` represents a lazy accounting of what that candidates entitlement to the fee pool would be if there `VotingPower` was to never change and they were to never withdraw fees. ``` candidate.count = candidate.VotingPower * BlockHeight ``` Similarly the GlobalState count can be passively calculated whenever needed, where `BondedShares` is the updated sum of voting powers from all validators. ``` gs.count = gs.BondedShares * BlockHeight ``` The `adjustment` term accounts for changes in voting power and withdrawals of fees. The adjustment factor must be persisted with the candidate and modified whenever fees are withdrawn from the candidate or the voting power of the candidate changes. When the voting power of the candidate changes the `Adjustment` factor is increased/decreased by the cumulative difference in the voting power if the voting power has been the new voting power as opposed to the old voting power for the entire duration of the blockchain up the previous block. Each time there is an adjustment change the GlobalState (denoted `gs`) `Adjustment` must also be updated. ``` simplePool = candidate.count / gs.count * gs.SumFeesReceived projectedPool = candidate.PrevPower * (height-1) / (gs.PrevPower * (height-1)) * gs.PrevFeesReceived + candidate.Power / gs.Power * gs.RecentFee AdjustmentChange = simplePool - projectedPool candidate.AdjustmentRewardPool += AdjustmentChange gs.Adjustment += AdjustmentChange ``` Every instance that the voting power changes, information about the state of the validator set during the change must be recorded as a `powerChange` for other validators to run through. Before any validator modifies its voting power it must first run through the above calculation to determine the change in their `caandidate.AdjustmentRewardPool` for all historical changes in the set of `powerChange` which they have not yet synced to. The set of all `powerChange` may be trimmed from its oldest members once all validators have synced past the height of the oldest `powerChange`. This trim procedure will occur on an epoch basis. ```golang type powerChange struct { height int64 // block height at change power rational.Rat // total power at change prevpower rational.Rat // total power at previous height-1 feesin coins.Coin // fees in at block height prevFeePool coins.Coin // total fees in at previous block height } ``` Note that the adjustment factor may result as negative if the voting power of a different candidate has decreased. ``` candidate.AdjustmentRewardPool += withdrawn gs.Adjustment += withdrawn ``` Now the entitled fee pool of each candidate can be lazily accounted for at any given block: ``` candidate.feePool = candidate.simplePool - candidate.Adjustment ``` So far we have covered two sources fees which can be withdrawn from: Fees from proposer rewards (`candidate.ProposerRewardPool`), and fees from the fee pool (`candidate.feePool`). However we should note that all fees from fee pool are subject to commission rate from the owner of the candidate. These next calculations outline the math behind withdrawing fee rewards as either a delegator to a candidate providing commission, or as the owner of a candidate who is receiving commission. ### Calculations For Delegators and Candidates The same mechanism described to calculate the fees which an entire validator is entitled to is be applied to delegator level to determine the entitled fees for each delegator and the candidates entitled commission from `gs.FeesPool` and `candidate.ProposerRewardPool`. The calculations are identical with a few modifications to the parameters: - Delegator's entitlement to `gs.FeePool`: - entitled party voting power should be taken as the effective voting power after commission is retrieved, `bond.Shares/candidate.TotalDelegatorShares * candidate.VotingPower * (1 - candidate.Commission)` - Delegator's entitlement to `candidate.ProposerFeePool` - global power in this context is actually shares `candidate.TotalDelegatorShares` - entitled party voting power should be taken as the effective shares after commission is retrieved, `bond.Shares * (1 - candidate.Commission)` - Candidate's commission entitlement to `gs.FeePool` - entitled party voting power should be taken as the effective voting power of commission portion of total voting power, `candidate.VotingPower * candidate.Commission` - Candidate's commission entitlement to `candidate.ProposerFeePool` - global power in this context is actually shares `candidate.TotalDelegatorShares` - entitled party voting power should be taken as the of commission portion of total delegators shares, `candidate.TotalDelegatorShares * candidate.Commission` For more implementation ideas see spreadsheet `spec/AbsoluteFeeDistrModel.xlsx` As mentioned earlier, every time the voting power of a delegator bond is changing either by unbonding or further bonding, all fees must be simultaneously withdrawn. Similarly if the validator changes the commission rate, all commission on fees must be simultaneously withdrawn. ### Other general notes on fees accounting - When a delegator chooses to re-delegate shares, fees continue to accumulate until the re-delegation queue reaches maturity. At the block which the queue reaches maturity and shares are re-delegated all available fees are simultaneously withdrawn. - Whenever a totally new validator is added to the validator set, the `accum` of the entire candidate must be 0, meaning that the initial value for `candidate.Adjustment` must be set to the value of `canidate.Count` for the height which the candidate is added on the validator set. - The feePool of a new delegator bond will be 0 for the height at which the bond was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to the height which the bond was added.