From 5b0e222639618366138d697cd272cffe8d918abd Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Tue, 13 Feb 2018 17:06:54 +0100 Subject: [PATCH] Add spec of the basic staking features --- docs/spec/README.md | 15 +- docs/spec/staking/definitions and examples.md | 250 +++--- docs/spec/staking/spec-technical.md | 755 +++++++++++------- 3 files changed, 617 insertions(+), 403 deletions(-) diff --git a/docs/spec/README.md b/docs/spec/README.md index 6a507dc03..5f3942ff9 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -1,13 +1,18 @@ # Cosmos Hub Spec -This directory contains specifications for the application level components of the Cosmos Hub. +This directory contains specifications for the application level components of +the Cosmos Hub. NOTE: the specifications are not yet complete and very much a work in progress. -- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for sending tokens. -- [Staking](staking) - Proof of Stake related specifications including bonding and delegation transactions, inflation, fees, etc. -- [Governance](governance) - Governance related specifications including proposals and voting. -- [Other](other) - Other components of the Cosmos Hub, including the reserve pool, All in Bits vesting, etc. +- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for + sending tokens. +- [Staking](staking) - Proof of Stake related specifications including bonding + and delegation transactions, inflation, fees, etc. +- [Governance](governance) - Governance related specifications including + proposals and voting. +- [Other](other) - Other components of the Cosmos Hub, including the reserve + pool, All in Bits vesting, etc. The [specification for Tendermint](https://github.com/tendermint/tendermint/tree/develop/docs/specification/new-spec), i.e. the underlying blockchain, can be found elsewhere. diff --git a/docs/spec/staking/definitions and examples.md b/docs/spec/staking/definitions and examples.md index 72dbc3a3d..ba4c1563e 100644 --- a/docs/spec/staking/definitions and examples.md +++ b/docs/spec/staking/definitions and examples.md @@ -2,113 +2,183 @@ ## Basic Terms and Definitions -- Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system -- Atom - native token of the Cosmsos Hub -- Atom holder - an entity that holds some amount of Atoms -- Candidate - an Atom holder that is actively involved in Tendermint blockchain protocol (running Tendermint Full Node -TODO: add link to Full Node definition) and is competing with other candidates to be elected as a Validator -(TODO: add link to Validator definition)) -- Validator - a candidate that is currently selected among a set of candidates to be able to sign protocol messages -in the Tendermint consensus protocol -- Delegator - an Atom holder that has bonded any of its Atoms by delegating them to a validator (or a candidate) -- Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms under protocol control). -Atoms are always bonded through a validator (or candidate) process. Bonded atoms can be slashed (burned) in case a -validator process misbehaves (does not behave according to a protocol specification). Atom holder can regain access -to its bonded Atoms (if they are not slashed in the meantime), i.e., they can be moved to its account, -after Unbonding period has expired. -- Unbonding period - a period of time after which Atom holder gains access to its bonded Atoms (they can be withdrawn -to a user account) or they can re-delegate -- Inflationary provisions - inflation is a process of increasing Atom supply. Atoms are being created in the process of -(Cosmos Hub) blocks creation. Owners of bonded atoms are rewarded for securing network with inflationary provisions -proportional to it's bonded Atom share. -- Transaction fees - transaction fee is a fee that is included in the Cosmsos Hub transactions. The fees are collected -by the current validator set and distributed among validators and delegators in proportion to it's bonded Atom share. -- Commission fee - a fee taken from the transaction fees by a validator for it's service +* Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system +* Atom - native token of the Cosmsos Hub +* Atom holder - an entity that holds some amount of Atoms +* Candidate - an Atom holder that is actively involved in the Tendermint + blockchain protocol (running Tendermint Full Node (TODO: add link to Full + Node definition) and is competing with other candidates to be elected as a + validator (TODO: add link to Validator definition)) +* Validator - a candidate that is currently selected among a set of candidates + to be able to sign protocol messages in the Tendermint consensus protocol +* Delegator - an Atom holder that has bonded some of its Atoms by delegating + them to a validator (or a candidate) +* Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms + under protocol control). Atoms are always bonded through a validator (or + candidate) process. Bonded atoms can be slashed (burned) in case a validator + process misbehaves (does not behave according to the protocol specification). + Atom holders can regain access to their bonded Atoms if they have not been + slashed by waiting an Unbonding period. +* Unbonding period - a period of time after which Atom holder gains access to + its bonded Atoms (they can be withdrawn to a user account) or they can be + re-delegated. +* Inflationary provisions - inflation is the process of increasing the Atom supply. + Atoms are periodically created on the Cosmos Hub and issued to bonded Atom holders. + The goal of inflation is to incentize most of the Atoms in existence to be bonded. +* Transaction fees - transaction fee is a fee that is included in a Cosmsos Hub + transaction. The fees are collected by the current validator set and + distributed among validators and delegators in proportion to their bonded + Atom share. +* Commission fee - a fee taken from the transaction fees by a validator for + their service ## The pool and the share -At the core of the Staking module is the concept of a pool which denotes collection of Atoms contributed by different -Atom holders. There are two global pools in the Staking module: bonded pool and unbonded pool. Bonded Atoms are part -of the global bonded pool. On the other side, if a candidate or delegator wants to unbond its Atoms, those Atoms are -kept in the unbonding pool for a duration of the unbonding period. In the Staking module, a pool is logical concept, -i.e., there is no pool data structure that would be responsible for managing pool resources. Instead, it is managed -in a distributed way. More precisely, at the global level, for each pool, we track only the total amount of -(bonded or unbonded) Atoms and a current amount of issued shares. A share is a unit of Atom distribution and the -value of the share (share-to-atom exchange rate) is changing during the system execution. The -share-to-atom exchange rate can be computed as: +At the core of the Staking module is the concept of a pool which denotes a +collection of Atoms contributed by different Atom holders. There are two global +pools in the Staking module: the bonded pool and unbonding pool. Bonded Atoms +are part of the global bonded pool. If a candidate or delegator wants to unbond +its Atoms, those Atoms are moved to the the unbonding pool for the duration of +the unbonding period. In the Staking module, a pool is a logical concept, i.e., +there is no pool data structure that would be responsible for managing pool +resources. Instead, it is managed in a distributed way. More precisely, at the +global level, for each pool, we track only the total amount of bonded or unbonded +Atoms and the current amount of issued shares. A share is a unit of Atom distribution +and the value of the share (share-to-atom exchange rate) changes during +system execution. The share-to-atom exchange rate can be computed as: -`share-to-atom-ex-rate = size of the pool / ammount of issued shares` +`share-to-atom-exchange-rate = size of the pool / ammount of issued shares` -Then for each candidate (in a per candidate data structure) we keep track of an amount of shares the candidate is owning -in a pool. At any point in time, the exact amount of Atoms a candidate has in the pool -can be computed as the number of shares it owns multiplied with the share-to-atom exchange rate: +Then for each validator candidate (in a per candidate data structure) we keep track of +the amount of shares the candidate owns in a pool. At any point in time, +the exact amount of Atoms a candidate has in the pool can be computed as the +number of shares it owns multiplied with the current share-to-atom exchange rate: -`candidate-coins = candidate.Shares * share-to-atom-ex-rate` +`candidate-coins = candidate.Shares * share-to-atom-exchange-rate` -The benefit of such accounting of the pool resources is the fact that a modification to the pool because of -bonding/unbonding/slashing/provisioning of atoms affects only global data (size of the pool and the number of shares) -and the related validator/candidate data structure, i.e., the data structure of other validators do not need to be -modified. Let's explain this further with several small examples: +The benefit of such accounting of the pool resources is the fact that a +modification to the pool from bonding/unbonding/slashing/provisioning of +Atoms affects only global data (size of the pool and the number of shares) and +not the related validator/candidate data structure, i.e., the data structure of +other validators do not need to be modified. This has the advantage that +modifying global data is much cheaper computationally than modifying data of +every validator. Let's explain this further with several small examples: -We consider initially 4 validators p1, p2, p3 and p4, and that each validator has bonded 10 Atoms -to a bonded pool. Furthermore, let's assume that we have issued initially 40 shares (note that the initial distribution -of the shares, i.e., share-to-atom exchange rate can be set to any meaningful value), i.e., -share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we have, the size of the pool is 40 Atoms, and -the amount of issued shares is equal to 40. And for each validator we store in their corresponding data structure -that each has 10 shares of the bonded pool. Now lets assume that the validator p4 starts process of unbonding of 5 -shares. Then the total size of the pool is decreased and now it will be 35 shares and the amount of Atoms is 35. -Note that the only change in other data structures needed is reducing the number of shares for a validator p4 from 10 -to 5. +We consider initially 4 validators p1, p2, p3 and p4, and that each validator +has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have +issued initially 40 shares (note that the initial distribution of the shares, +i.e., share-to-atom exchange rate can be set to any meaningful value), i.e., +share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we +have, the size of the pool is 40 Atoms, and the amount of issued shares is +equal to 40. And for each validator we store in their corresponding data +structure that each has 10 shares of the bonded pool. Now lets assume that the +validator p4 starts process of unbonding of 5 shares. Then the total size of +the pool is decreased and now it will be 35 shares and the amount of Atoms is +35 . Note that the only change in other data structures needed is reducing the +number of shares for a validator p4 from 10 to 5. -Let's consider now the case where a validator p1 wants to bond 15 more atoms to the pool. Now the size of the pool -is 50, and as the exchange rate hasn't changed (1 share is still worth 1 Atom), we need to create more shares, -i.e. we now have 50 shares in the pool in total. -Validators p2, p3 and p4 still have (correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we -don't need to modify anything in their corresponding data structures. But p1 now has 25 shares, so we update the amount -of shares owned by the p1 in its data structure. Note that apart from the size of the pool that is in Atoms, all other -data structures refer only to shares. +Let's consider now the case where a validator p1 wants to bond 15 more atoms to +the pool. Now the size of the pool is 50, and as the exchange rate hasn't +changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we +now have 50 shares in the pool in total. Validators p2, p3 and p4 still have +(correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we +don't need to modify anything in their corresponding data structures. But p1 +now has 25 shares, so we update the amount of shares owned by p1 in its +data structure. Note that apart from the size of the pool that is in Atoms, all +other data structures refer only to shares. -Finally, let's consider what happens when new Atoms are created and added to the pool due to inflation. Let's assume that -the inflation rate is 10 percent and that it is applied to the current state of the pool. This means that 5 Atoms are -created and added to the pool and that each validator now proportionally increase it's Atom count. Let's analyse how this -change is reflected in the data structures. First, the size of the pool is increased and is now 55 atoms. As a share of -each validator in the pool hasn't changed, this means that the total number of shares stay the same (50) and that the -amount of shares of each validator stays the same (correspondingly 25, 10, 10, 5). But the exchange rate has changed and -each share is now worth 55/50 Atoms per share, so each validator has effectively increased amount of Atoms it has. -So validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms. +Finally, let's consider what happens when new Atoms are created and added to +the pool due to inflation. Let's assume that the inflation rate is 10 percent +and that it is applied to the current state of the pool. This means that 5 +Atoms are created and added to the pool and that each validator now +proportionally increase it's Atom count. Let's analyse how this change is +reflected in the data structures. First, the size of the pool is increased and +is now 55 atoms. As a share of each validator in the pool hasn't changed, this +means that the total number of shares stay the same (50) and that the amount of +shares of each validator stays the same (correspondingly 25, 10, 10, 5). But +the exchange rate has changed and each share is now worth 55/50 Atoms per +share, so each validator has effectively increased amount of Atoms it has. So +validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms. -The concepts of the pool and its shares is at the core of the accounting in the Staking module. It is used for managing -the global pools (such as bonding and unbonding pool), but also for distribution of Atoms between validator and its -delegators (we will explain this in section X). +The concepts of the pool and its shares is at the core of the accounting in the +Staking module. It is used for managing the global pools (such as bonding and +unbonding pool), but also for distribution of Atoms between validator and its +delegators (we will explain this in section X). #### Delegator shares -A candidate is, depending on it's status, contributing Atoms to either the bonded or unbonded pool, and in return gets -some amount of (global) pool shares. Note that not all those Atoms (and respective shares) are owned by the candidate -as some Atoms could be delegated to a candidate. The mechanism for distribution of Atoms (and shares) between a -candidate and it's delegators is based on a notion of delegator shares. More precisely, every candidate is issuing -(local) delegator shares (`Candidate.IssuedDelegatorShares`) that represents some portion of global shares -managed by the candidate (`Candidate.GlobalStakeShares`). The principle behind managing delegator shares is the same -as described in [Section](#The pool and the share). We now illustrate it with an example. +A candidate is, depending on it's status, contributing Atoms to either the +bonded or unbonding pool, and in return gets some amount of (global) pool +shares. Note that not all those Atoms (and respective shares) are owned by the +candidate as some Atoms could be delegated to a candidate. The mechanism for +distribution of Atoms (and shares) between a candidate and it's delegators is +based on a notion of delegator shares. More precisely, every candidate is +issuing (local) delegator shares (`Candidate.IssuedDelegatorShares`) that +represents some portion of global shares managed by the candidate +(`Candidate.GlobalStakeShares`). The principle behind managing delegator shares +is the same as described in [Section](#The pool and the share). We now +illustrate it with an example. -Lets consider 4 validators p1, p2, p3 and p4, and assume that each validator has bonded 10 Atoms to a bonded pool. -Furthermore, lets assume that we have issued initially 40 global shares, i.e., that `share-to-atom-ex-rate = 1 atom per -share`. So we will `GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the Candidate data structure -of each validator `Candidate.GlobalStakeShares = 10`. Furthermore, each validator issued 10 delegator -shares which are initially owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where +Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator +has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have +issued initially 40 global shares, i.e., that +`share-to-atom-exchange-rate = 1 atom per share`. So we will set +`GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the +Candidate data structure of each validator `Candidate.GlobalStakeShares = 10`. +Furthermore, each validator issued 10 delegator shares which are initially +owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where `delegator-share-to-global-share-ex-rate = 1 global share per delegator share`. -Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and consider what are the updates we need -to make to the data structures. First, `GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, -for validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to issue also additional delegator shares, -i.e., `Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator shares of validator p1, where -each delegator share is worth 1 global shares, i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due -to inflation. In that case, we only need to update `GlobalState.BondedPool` which is now equal to 50 Atoms as created -Atoms are added to the bonded pool. Note that the amount of global and delegator shares stay the same but they are now -worth more as share-to-atom-ex-rate is now worth 50/45 Atoms per share. Therefore, a delegator d1 now owns - -`delegatorCoins = 10 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 100/9 Atoms` - +Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and +consider what are the updates we need to make to the data structures. First, +`GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for +validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to +issue also additional delegator shares, i.e., +`Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator +shares of validator p1, where each delegator share is worth 1 global shares, +i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due to +inflation. In that case, we only need to update `GlobalState.BondedPool` which +is now equal to 50 Atoms as created Atoms are added to the bonded pool. Note +that the amount of global and delegator shares stay the same but they are now +worth more as share-to-atom-exchange-rate is now worth 50/45 Atoms per share. +Therefore, a delegator d1 now owns: +`delegatorCoins = 5 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 5.55 Atoms` + +### Inflation provisions + +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 provisions cycle. The +inflation is also subject to a rate change (positive or negative) depending on +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%. + +```go +inflationRateChange(0) = 0 +GlobalState.Inflation(0) = 0.07 + +bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply +AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13 + +annualInflation += AnnualInflationRateChange + +if annualInflation > 0.20 then GlobalState.Inflation = 0.20 +if annualInflation < 0.07 then GlobalState.Inflation = 0.07 + +provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24) +``` + +Because the validators hold a relative bonded share (`GlobalStakeShares`), when +more bonded tokens are added proportionally to all validators, the only term +which needs to be updated is the `GlobalState.BondedPool`. So for each +provisions cycle: + +```go +GlobalState.BondedPool += provisionTokensHourly +``` diff --git a/docs/spec/staking/spec-technical.md b/docs/spec/staking/spec-technical.md index e3a528d94..8d9baa796 100644 --- a/docs/spec/staking/spec-technical.md +++ b/docs/spec/staking/spec-technical.md @@ -2,54 +2,66 @@ ## Overview -The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that serves as a backbone of the Cosmos ecosystem. -It is operated and secured by an open and globally decentralized set of validators. Tendermint consensus is a -Byzantine fault-tolerant distributed protocol that involves all validators in the process of exchanging protocol -messages in the production of each block. To avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up -coins in a bond deposit. Tendermint protocol messages are signed by the validator's private key, and this is a basis for -Tendermint strict accountability that allows punishing misbehaving validators by slashing (burning) their bonded Atoms. -On the other hand, validators are for it's service of securing blockchain network rewarded by the inflationary -provisions and transactions fees. This incentivizes correct behavior of the validators and provide economic security -of the network. +The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that +serves as a backbone of the Cosmos ecosystem. It is operated and secured by an +open and globally decentralized set of validators. Tendermint consensus is a +Byzantine fault-tolerant distributed protocol that involves all validators in +the process of exchanging protocol messages in the production of each block. To +avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up +coins in a bond deposit. Tendermint protocol messages are signed by the +validator's private key, and this is a basis for Tendermint strict +accountability that allows punishing misbehaving validators by slashing +(burning) their bonded Atoms. On the other hand, validators are rewarded for +their service of securing blockchain network by the inflationary provisions and +transactions fees. This incentives correct behavior of the validators and +provides the economic security of the network. + +The native token of the Cosmos Hub is called Atom; becoming a validator of the +Cosmos Hub requires holding Atoms. However, not all Atom holders are validators +of the Cosmos Hub. More precisely, there is a selection process that determines +the validator set as a subset of all validator candidates (Atom holders that +wants to become a validator). The other option for Atom holder is to delegate +their atoms to validators, i.e., being a delegator. A delegator is an Atom +holder that has bonded its Atoms by delegating it to a validator (or validator +candidate). By bonding Atoms to secure the network (and taking a risk of being +slashed in case of misbehaviour), a user is rewarded with inflationary +provisions and transaction fees proportional to the amount of its bonded Atoms. +The Cosmos Hub is designed to efficiently facilitate a small numbers of +validators (hundreds), and large numbers of delegators (tens of thousands). +More precisely, it is the role of the Staking module of the Cosmos Hub to +support various staking functionality including validator set selection, +delegating, bonding and withdrawing Atoms, and the distribution of inflationary +provisions and transaction fees. -The native token of the Cosmos Hub is called Atom; becoming a validator of the Cosmos Hub requires holding Atoms. -However, not all Atom holders are validators of the Cosmos Hub. More precisely, there is a selection process that -determines the validator set as a subset of all validator candidates (Atom holder that wants to -become a validator). The other option for Atom holder is to delegate their atoms to validators, i.e., -being a delegator. A delegator is an Atom holder that has bonded its Atoms by delegating it to a validator -(or validator candidate). By bonding Atoms to securing network (and taking a risk of being slashed in case the -validator misbehaves), a user is rewarded with inflationary provisions and transaction fees proportional to the amount -of its bonded Atoms. The Cosmos Hub is designed to efficiently facilitate a small numbers of validators (hundreds), and -large numbers of delegators (tens of thousands). More precisely, it is the role of the Staking module of the Cosmos Hub -to support various staking functionality including validator set selection; delegating, bonding and withdrawing Atoms; -and the distribution of inflationary provisions and transaction fees. - ## State The staking module persists the following information to the store: -- `GlobalState`, describing the global pools and the inflation related fields -- `map[PubKey]Candidate`, a map of validator candidates (including current validators), indexed by public key -- `map[rational.Rat]Candidate`, an ordered map of validator candidates (including current validators), indexed by -shares in the global pool (bonded or unbonded depending on candidate status) -- `map[[]byte]DelegatorBond`, a map of DelegatorBonds (for each delegation to a candidate by a delegator), indexed by -the delegator address and the candidate public key -- `queue[QueueElemUnbondDelegation]`, a queue of unbonding delegations -- `queue[QueueElemReDelegate]`, a queue of re-delegations +* `GlobalState`, describing the global pools and the inflation related fields +* validator candidates (including current validators), indexed by public key and shares in the global pool +(bonded or unbonded depending on candidate status) +* delegator bonds (for each delegation to a candidate by a delegator), indexed by the delegator address and the candidate + public key +* the queue of unbonding delegations +* the queue of re-delegations ### Global State -GlobalState data structure contains total Atoms supply, amount of Atoms in the bonded pool, sum of all shares -distributed for the bonded pool, amount of Atoms in the unbonded pool, sum of all shares distributed for the -unbonded pool, a timestamp of the last processing of inflation, the current annual inflation rate, a timestamp -for the last comission accounting reset, the global fee pool, a pool of reserve taxes collected for the governance use -and an adjustment factor for calculating global feel accum (?). - -``` golang +The GlobalState data structure contains total Atom supply, amount of Atoms in +the bonded pool, sum of all shares distributed for the bonded pool, amount of +Atoms in the unbonded pool, sum of all shares distributed for the unbonded +pool, a timestamp of the last processing of inflation, the current annual +inflation rate, a timestamp for the last comission accounting reset, the global +fee pool, a pool of reserve taxes collected for the governance use and an +adjustment factor for calculating global fee accum. `Params` is global data +structure that stores system parameters and defines overall functioning of the +module. + +``` go type GlobalState struct { TotalSupply int64 // total supply of Atoms BondedPool int64 // reserve of bonded tokens BondedShares rational.Rat // sum of all shares distributed for the BondedPool - UnbondedPool int64 // reserve of unbonded tokens held with candidates + UnbondedPool int64 // reserve of unbonding tokens held with candidates UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool InflationLastTime int64 // timestamp of last processing of inflation Inflation rational.Rat // current annual inflation rate @@ -58,19 +70,40 @@ type GlobalState struct { ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use Adjustment rational.Rat // Adjustment factor for calculating global fee accum } + +type Params struct { + HoldBonded Address // account where all bonded coins are held + HoldUnbonding Address // account where all delegated but unbonding 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 +} ``` ### Candidate -The `Candidate` data structure holds the current state and some historical actions of -validators or candidate-validators. +The `Candidate` data structure holds the current state and some historical +actions of validators or candidate-validators. -``` golang +``` go type Candidate struct { Status CandidateStatus - PubKey crypto.PubKey + ConsensusPubKey crypto.PubKey GovernancePubKey crypto.PubKey - Owner Address + Owner crypto.Address GlobalStakeShares rational.Rat IssuedDelegatorShares rational.Rat RedelegatingShares rational.Rat @@ -83,118 +116,115 @@ type Candidate struct { Adjustment rational.Rat Description Description } -``` -CandidateStatus can be VyingUnbonded, VyingUnbonding, Bonded, KickUnbonding and KickUnbonded. - - -``` golang type Description struct { - Name string - DateBonded string - Identity string - Website string - Details string + 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` otherwise - - IssuedDelegatorShares: Sum of all shares a candidate issued to delegators (which - includes the candidate's self-bond); a delegator share represents 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 candidate is a validator. - - Commission: The commission rate of fees charged to any delegators - - CommissionMax: The maximum commission rate 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 +* Status: it can be Bonded (active validator), Unbonding (validator candidate) + or Revoked +* ConsensusPubKey: candidate public key that is used strictly for participating in + consensus +* GovernancePubKey: public key used by the validator for governance voting +* Owner: Address that is allowed to unbond coins. +* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if + `Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool` + otherwise +* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators + (which includes the candidate's self-bond); a delegator share represents + 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 `Candidate.Status` is `Bonded`; otherwise it is equal to `0` +* Commission: The commission rate of fees charged to any delegators +* CommissionMax: The maximum commission rate 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 ### DelegatorBond -Atom holders may delegate coins to validators; under this circumstance their -funds are held in a `DelegatorBond` data structure. It is owned by one delegator, and is -associated with the shares for one validator. The sender of the transaction is -considered the owner of the bond. +Atom holders may delegate coins to candidates; under this circumstance their +funds are held in a `DelegatorBond` data structure. It is owned by one +delegator, and is associated with the shares for one candidate. The sender of +the transaction is the owner of the bond. -``` golang +``` go type DelegatorBond struct { - Candidate crypto.PubKey - Shares rational.Rat + Candidate crypto.PubKey + Shares rational.Rat AdjustmentFeePool coin.Coins AdjustmentRewardPool coin.Coins } ``` Description: - - Candidate: the public key of the validator candidate: bonding too - - Shares: the number of delegator 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`` +* Candidate: the public key of the validator candidate: bonding too +* Shares: the number of delegator 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` + ### QueueElem -Unbonding and re-delegation process is implemented using the ordered queue data structure. -All queue elements used share a common structure: +The Unbonding and re-delegation process is implemented using the ordered queue +data structure. All queue elements share a common structure: -``` golang +```golang type QueueElem struct { - Candidate crypto.PubKey - InitHeight int64 // when the queue was initiated + Candidate crypto.PubKey + InitTime int64 // when the element was added to 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`, the final settlement of the unbonding is started or re-delegation is executed, and the element is -pop from the queue. Each `QueueElem` is persisted in the store until it is popped from the queue. +The queue is ordered so the next element 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 `InitTime`, the final settlement of the unbonding is started or +re-delegation is executed, and the element is popped from the queue. Each +`QueueElem` is persisted in the store until it is popped from the queue. ### QueueElemUnbondDelegation -``` golang +QueueElemUnbondDelegation structure is used in the unbonding queue. + +```golang type QueueElemUnbondDelegation struct { - QueueElem - Payout Address // account to pay out to - Shares rational.Rat // amount of delegator shares which are unbonding - StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation + QueueElem + Payout Address // account to pay out to + Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding + StartSlashRatio rational.Rat // candidate slash ratio } ``` -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. ### QueueElemReDelegate -``` golang +QueueElemReDelegate structure is used in the re-delegation queue. + +```golang type QueueElemReDelegate struct { - QueueElem - Payout Address // account to pay out to + 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 } @@ -203,31 +233,38 @@ type QueueElemReDelegate struct { ### Transaction Overview Available Transactions: - - TxDeclareCandidacy - - TxEditCandidacy - - TxLivelinessCheck - - TxProveLive - - TxDelegate - - TxUnbond - - TxRedelegate +* TxDeclareCandidacy +* TxEditCandidacy +* TxDelegate +* TxUnbond +* TxRedelegate +* TxLivelinessCheck +* TxProveLive ## Transaction processing -In this section we describe the processing of the transactions and the corresponding updates to the global state. -For the following text we will use gs to refer to the GlobalState data structure, candidateMap is a reference to the -map[PubKey]Candidate, delegatorBonds is a reference to map[[]byte]DelegatorBond, unbondDelegationQueue is a -reference to the queue[QueueElemUnbondDelegation] and redelegationQueue is the reference for the -queue[QueueElemReDelegate]. We use tx to denote reference to a transaction that is being processed. +In this section we describe the processing of the transactions and the +corresponding updates to the global state. In the following text we will use +`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a +reference to the queue of unbond delegations, `reDelegationQueue` is the +reference for the queue of redelegations. We use `tx` to denote a +reference to a transaction that is being processed, and `sender` to denote the +address of the sender of the transaction. We use function +`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store, +and `saveCandidate(store, candidate)` to save it. Similarly, we use +`loadDelegatorBond(store, sender, PubKey)` to load a delegator bond with the +key (sender and PubKey) from the store, and +`saveDelegatorBond(store, sender, bond)` to save it. +`removeDelegatorBond(store, sender, bond)` is used to remove the bond from the +store. ### TxDeclareCandidacy -A 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 (TODO: What does this mean?). +A validator candidacy is declared using the `TxDeclareCandidacy` transaction. -``` golang +```golang type TxDeclareCandidacy struct { - PubKey crypto.PubKey + ConsensusPubKey crypto.PubKey Amount coin.Coin GovernancePubKey crypto.PubKey Commission rational.Rat @@ -235,28 +272,25 @@ type TxDeclareCandidacy struct { CommissionMaxChange int64 Description Description } -``` -``` declareCandidacy(tx TxDeclareCandidacy): - // create and save the empty candidate candidate = loadCandidate(store, tx.PubKey) - if candidate != nil then return + if candidate != nil return // candidate with that public key already exists candidate = NewCandidate(tx.PubKey) candidate.Status = Unbonded candidate.Owner = sender - init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares,RedelegatingShares and Adjustment to rational.Zero + init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero init commision related fields based on the values from tx candidate.ProposerRewardPool = Coin(0) candidate.Description = tx.Description saveCandidate(store, candidate) - // move coins from the sender account to a (self-bond) delegator account - // the candidate account and global shares are updated within here - txDelegate = TxDelegate{tx.BondUpdate} - return delegateWithCandidate(txDelegate, candidate) + txDelegate = TxDelegate(tx.PubKey, tx.Amount) + return delegateWithCandidate(txDelegate, candidate) + +// see delegateWithCandidate function in [TxDelegate](TxDelegate) ``` ### TxEditCandidacy @@ -265,221 +299,326 @@ 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 +```golang type TxEditCandidacy struct { GovernancePubKey crypto.PubKey Commission int64 Description Description } -``` -``` editCandidacy(tx TxEditCandidacy): candidate = loadCandidate(store, tx.PubKey) - if candidate == nil or candidate.Status == Unbonded return - if tx.GovernancePubKey != nil then candidate.GovernancePubKey = tx.GovernancePubKey - if tx.Commission >= 0 then candidate.Commission = tx.Commission - if tx.Description != nil then candidate.Description = tx.Description + if candidate == nil or candidate.Status == Revoked return + + if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey + if tx.Commission >= 0 candidate.Commission = tx.Commission + if tx.Description != nil candidate.Description = tx.Description + saveCandidate(store, candidate) return - ``` +``` ### TxDelegate -All bonding, whether self-bonding or delegation, is done via `TxDelegate`. +Delegator bonds are created using the `TxDelegate` transaction. Within this +transaction the delegator provides an amount of coins, and in return receives +some amount of candidate's delegator shares that are assigned to +`DelegatorBond.Shares`. -Delegator bonds are created using the `TxDelegate` transaction. Within this transaction the delegator provides -an amount of coins, and in return receives some amount of candidate's delegator shares that are assigned to -`DelegatorBond.Shares`. The amount of created delegator shares depends on the candidate's -delegator-shares-to-atoms exchange rate and is computed as -`delegator-shares = delegator-coins / delegator-shares-to-atom-ex-rate`. - -``` golang -type TxDelegate struct { - PubKey crypto.PubKey - Amount coin.Coin +```golang +type TxDelegate struct { + PubKey crypto.PubKey + Amount coin.Coin } -``` -``` delegate(tx TxDelegate): candidate = loadCandidate(store, tx.PubKey) - if candidate == nil then return + if candidate == nil return return delegateWithCandidate(tx, candidate) delegateWithCandidate(tx TxDelegate, candidate Candidate): - if candidate.Status == Revoked then return + if candidate.Status == Revoked return - if candidate.Status == Bonded then - poolAccount = address of the bonded pool - else - poolAccount = address of the unbonded pool + if candidate.Status == Bonded + poolAccount = params.HoldBonded + else + poolAccount = params.HoldUnbonded - // Move coins from the delegator account to the bonded pool account - err = transfer(sender, poolAccount, tx.Amount) - if err != nil then return + err = transfer(sender, poolAccount, tx.Amount) + if err != nil return - // Get or create the delegator bond - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then - bond = DelegatorBond{tx.PubKey,rational.Zero, Coin(0), Coin(0)} + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0)) - issuedDelegatorShares = candidate.addTokens(tx.Amount, gs) - bond.Shares = bond.Shares.Add(issuedDelegatorShares) + issuedDelegatorShares = addTokens(tx.Amount, candidate) + bond.Shares += issuedDelegatorShares - saveCandidate(store, candidate) - - store.Set(GetDelegatorBondKey(sender, bond.PubKey), bond) - - saveGlobalState(store, gs) - return + saveCandidate(store, candidate) + saveDelegatorBond(store, sender, bond) + saveGlobalState(store, gs) + return -addTokens(amount int64, gs GlobalState, candidate Candidate): - - // get the exchange rate of global pool shares over delegator shares - if candidate.IssuedDelegatorShares.IsZero() then +addTokens(amount coin.Coin, candidate Candidate): + if candidate.Status == Bonded + gs.BondedPool += amount + issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool) + gs.BondedShares += issuedShares + else + gs.UnbondedPool += amount + issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) + gs.UnbondedShares += issuedShares + + candidate.GlobalStakeShares += issuedShares + + if candidate.IssuedDelegatorShares.IsZero() exRate = rational.One else - exRate = candiate.GlobalStakeShares.Quo(candidate.IssuedDelegatorShares) - - if candidate.Status == Bonded then - gs.BondedPool += amount - issuedShares = exchangeRate(gs.BondedShares, gs.BondedPool).Inv().Mul(amount) // (tokens/shares)^-1 * tokens - gs.BondedShares = gs.BondedShares.Add(issuedShares) - else - gs.UnbondedPool += amount - issuedShares = exchangeRate(gs.UnbondedShares, gs.UnbondedPool).Inv().Mul(amount) // (tokens/shares)^-1 * tokens - gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) + exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares - candidate.GlobalStakeShares = candidate.GlobalStakeShares.Add(issuedShares) - - issuedDelegatorShares = exRate.Mul(receivedGlobalShares) - candidate.IssuedDelegatorShares = candidate.IssuedDelegatorShares.Add(issuedDelegatorShares) - return + issuedDelegatorShares = issuedShares / exRate + candidate.IssuedDelegatorShares += issuedDelegatorShares + return issuedDelegatorShares exchangeRate(shares rational.Rat, tokenAmount int64): if shares.IsZero() then return rational.One - return shares.Inv().Mul(tokenAmount) + return tokenAmount / shares ``` ### TxUnbond + Delegator unbonding is defined with the following transaction: -``` golang -type TxUnbond struct { - PubKey crypto.PubKey - Shares rational.Rat +```golang +type TxUnbond struct { + PubKey crypto.PubKey + Shares rational.Rat +} + +unbond(tx TxUnbond): + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil return + if bond.Shares < tx.Shares return + + bond.Shares -= tx.Shares + + candidate = loadCandidate(store, tx.PubKey) + + revokeCandidacy = false + if bond.Shares.IsZero() + if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond) + else + saveDelegatorBond(store, sender, bond) + + if candidate.Status == Bonded + poolAccount = params.HoldBonded + else + poolAccount = params.HoldUnbonded + + returnedCoins = removeShares(candidate, shares) + + unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio) + unbondDelegationQueue.add(unbondDelegationElem) + + transfer(poolAccount, unbondingPoolAddress, returnCoins) + + if revokeCandidacy + if candidate.Status == Bonded then bondedToUnbondedPool(candidate) + candidate.Status = Revoked + + if candidate.IssuedDelegatorShares.IsZero() + removeCandidate(store, tx.PubKey) + else + saveCandidate(store, candidate) + + saveGlobalState(store, gs) + return + +removeShares(candidate Candidate, shares rational.Rat): + globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares + + if candidate.Status == Bonded + gs.BondedShares -= globalPoolSharesToRemove + removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove + gs.BondedPool -= removedTokens + else + gs.UnbondedShares -= globalPoolSharesToRemove + removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove + gs.UnbondedPool -= removedTokens + + candidate.GlobalStakeShares -= removedTokens + candidate.IssuedDelegatorShares -= shares + return returnedCoins + +delegatorShareExRate(candidate Candidate): + if candidate.IssuedDelegatorShares.IsZero() then return rational.One + return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares + +bondedToUnbondedPool(candidate Candidate): + removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares + gs.BondedShares -= candidate.GlobalStakeShares + gs.BondedPool -= removedTokens + + gs.UnbondedPool += removedTokens + issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) + gs.UnbondedShares += issuedShares + + candidate.GlobalStakeShares = issuedShares + candidate.Status = Unbonded + + return transfer(address of the bonded pool, address of the unbonded pool, removedTokens) +``` + +### TxRedelegate + +The re-delegation command allows delegators to switch validators while still +receiving equal reward to as if they had never unbonded. + +```golang +type TxRedelegate struct { + PubKeyFrom crypto.PubKey + PubKeyTo crypto.PubKey + Shares rational.Rat +} + +redelegate(tx TxRedelegate): + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil then return + + if bond.Shares < tx.Shares return + candidate = loadCandidate(store, tx.PubKeyFrom) + if candidate == nil return + + candidate.RedelegatingShares += tx.Shares + reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo) + redelegationQueue.add(reDelegationElem) + return +``` + +### TxLivelinessCheck + +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`. + +### TxProveLive + +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 liveliness is demonstrated +by sending in a `TxProveLive` transaction: + +```golang +type TxProveLive struct { + PubKey crypto.PubKey +} ``` -unbond(tx TxUnbond): - // get delegator bond - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then return +### End of block handling - // subtract bond tokens from delegator bond - if bond.Shares.LT(tx.Shares) return // bond shares < tx shares - - bond.Shares = bond.Shares.Sub(ts.Shares) - - candidate = loadCandidate(store, tx.PubKey) - if candidate == nil return - - revokeCandidacy = false - if bond.Shares.IsZero() { - // if the bond is the owner of the candidate then trigger a revoke candidacy - if sender.Equals(candidate.Owner) and candidate.Status != Revoked then - revokeCandidacy = true - - // remove the bond - removeDelegatorBond(store, sender, tx.PubKey) - else - saveDelegatorBond(store, sender, bond) - - // transfer coins back to account - if candidate.Status == Bonded then - poolAccount = address of the bonded pool - else - poolAccount = address of the unbonded pool - - returnCoins = candidate.removeShares(shares, gs) - // TODO: Shouldn't it be created a queue element in this case? - transfer(poolAccount, sender, returnCoins) - - if revokeCandidacy then - // change the share types to unbonded if they were not already - if candidate.Status == Bonded then - // replace bonded shares with unbonded shares - tokens = gs.removeSharesBonded(candidate.GlobalStakeShares) - candidate.GlobalStakeShares = gs.addTokensUnbonded(tokens) - candidate.Status = Unbonded +```golang +tick(ctx Context): + hrsPerYr = 8766 // as defined by a julian year of 365.25 days + + time = ctx.Time() + if time > gs.InflationLastTime + ProvisionTimeout + gs.InflationLastTime = time + gs.Inflation = nextInflation(hrsPerYr).Round(1000000000) - transfer(address of the bonded pool, address of the unbonded pool, tokens) - // lastly update the status - candidate.Status = Revoked + provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr) + + gs.BondedPool += provisions + gs.TotalSupply += provisions + + saveGlobalState(store, gs) + + if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod + for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do + transfer(unbondingQueueAddress, elem.Payout, elem.Tokens) + unbondDelegationQueue.remove(elem) + + if time > reDelegationQueue.head().InitTime + UnbondingPeriod + for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do + candidate = getCandidate(store, elem.PubKey) + returnedCoins = removeShares(candidate, elem.Shares) + candidate.RedelegatingShares -= elem.Shares + delegateWithCandidate(TxDelegate(elem.NewCandidate, returnedCoins), candidate) + reDelegationQueue.remove(elem) + + return UpdateValidatorSet() - // deduct shares from the candidate and save - if candidate.GlobalStakeShares.IsZero() then - removeCandidate(store, tx.PubKey) - else - saveCandidate(store, candidate) +nextInflation(hrsPerYr rational.Rat): + if gs.TotalSupply > 0 + bondedRatio = gs.BondedPool / gs.TotalSupply + else + bondedRation = 0 + + inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange + inflationRateChange = inflationRateChangePerYear / hrsPerYr - saveGlobalState(store, gs) - return + inflation = gs.Inflation + inflationRateChange + if inflation > params.InflationMax then inflation = params.InflationMax -removeDelegatorBond(candidate Candidate): + if inflation < params.InflationMin then inflation = params.InflationMin + + return inflation - // first remove from the list of bonds - pks = loadDelegatorCandidates(store, sender) - for i, pk := range pks { - if candidate.Equals(pk) { - pks = append(pks[:i], pks[i+1:]...) - } - } - b := wire.BinaryBytes(pks) - store.Set(GetDelegatorBondsKey(delegator), b) +UpdateValidatorSet(): + candidates = loadCandidates(store) - // now remove the actual bond - store.Remove(GetDelegatorBondKey(delegator, candidate)) - //updateDelegatorBonds(store, delegator) -} -``` + v1 = candidates.Validators() + v2 = updateVotingPower(candidates).Validators() -### Inflation provisions + change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets + return change -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%. +updateVotingPower(candidates Candidates): + foreach candidate in candidates do + candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate) + + candidates.Sort() + + foreach candidate in candidates do + if candidate is not in the first params.MaxVals + candidate.VotingPower = rational.Zero + if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate) + + else if candidate.Status == UnBonded then unbondedToBondedPool(candidate) + + saveCandidate(store, c) + + return candidates + +unbondedToBondedPool(candidate Candidate): + removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares + gs.UnbondedShares -= candidate.GlobalStakeShares + gs.UnbondedPool -= removedTokens + + gs.BondedPool += removedTokens + issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool) + gs.BondedShares += issuedShares -``` -inflationRateChange(0) = 0 -GlobalState.Inflation(0) = 0.07 - -bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply -AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13 - -annualInflation += AnnualInflationRateChange - -if annualInflation > 0.20 then GlobalState.Inflation = 0.20 -if annualInflation < 0.07 then GlobalState.Inflation = 0.07 - -provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24) -``` - -Because the validators hold a relative bonded share (`GlobalStakeShares`), when -more bonded tokens are added proportionally to all validators, the only term -which needs to be updated is the `GlobalState.BondedPool`. So for each previsions -cycle: - -``` -GlobalState.BondedPool += provisionTokensHourly + candidate.GlobalStakeShares = issuedShares + candidate.Status = Bonded + + return transfer(address of the unbonded pool, address of the bonded pool, removedTokens) ```