Add spec of the basic staking features

This commit is contained in:
Zarko Milosevic 2018-02-13 17:06:54 +01:00
parent ee0b396bad
commit 5b0e222639
3 changed files with 617 additions and 403 deletions

View File

@ -1,13 +1,18 @@
# Cosmos Hub Spec # 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. 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. - [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for
- [Staking](staking) - Proof of Stake related specifications including bonding and delegation transactions, inflation, fees, etc. sending tokens.
- [Governance](governance) - Governance related specifications including proposals and voting. - [Staking](staking) - Proof of Stake related specifications including bonding
- [Other](other) - Other components of the Cosmos Hub, including the reserve pool, All in Bits vesting, etc. 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), The [specification for Tendermint](https://github.com/tendermint/tendermint/tree/develop/docs/specification/new-spec),
i.e. the underlying blockchain, can be found elsewhere. i.e. the underlying blockchain, can be found elsewhere.

View File

@ -2,113 +2,183 @@
## Basic Terms and Definitions ## Basic Terms and Definitions
- Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system * Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system
- Atom - native token of the Cosmsos Hub * Atom - native token of the Cosmsos Hub
- Atom holder - an entity that holds some amount of Atoms * 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 * Candidate - an Atom holder that is actively involved in the Tendermint
TODO: add link to Full Node definition) and is competing with other candidates to be elected as a Validator blockchain protocol (running Tendermint Full Node (TODO: add link to Full
(TODO: add link to Validator definition)) Node definition) and is competing with other candidates to be elected as a
- Validator - a candidate that is currently selected among a set of candidates to be able to sign protocol messages validator (TODO: add link to Validator definition))
in the Tendermint consensus protocol * Validator - a candidate that is currently selected among a set of candidates
- Delegator - an Atom holder that has bonded any of its Atoms by delegating them to a validator (or a candidate) to be able to sign protocol messages in the Tendermint consensus protocol
- Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms under protocol control). * Delegator - an Atom holder that has bonded some of its Atoms by delegating
Atoms are always bonded through a validator (or candidate) process. Bonded atoms can be slashed (burned) in case a them to a validator (or a candidate)
validator process misbehaves (does not behave according to a protocol specification). Atom holder can regain access * Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms
to its bonded Atoms (if they are not slashed in the meantime), i.e., they can be moved to its account, under protocol control). Atoms are always bonded through a validator (or
after Unbonding period has expired. candidate) process. Bonded atoms can be slashed (burned) in case a validator
- Unbonding period - a period of time after which Atom holder gains access to its bonded Atoms (they can be withdrawn process misbehaves (does not behave according to the protocol specification).
to a user account) or they can re-delegate Atom holders can regain access to their bonded Atoms if they have not been
- Inflationary provisions - inflation is a process of increasing Atom supply. Atoms are being created in the process of slashed by waiting an Unbonding period.
(Cosmos Hub) blocks creation. Owners of bonded atoms are rewarded for securing network with inflationary provisions * Unbonding period - a period of time after which Atom holder gains access to
proportional to it's bonded Atom share. its bonded Atoms (they can be withdrawn to a user account) or they can be
- Transaction fees - transaction fee is a fee that is included in the Cosmsos Hub transactions. The fees are collected re-delegated.
by the current validator set and distributed among validators and delegators in proportion to it's bonded Atom share. * Inflationary provisions - inflation is the process of increasing the Atom supply.
- Commission fee - a fee taken from the transaction fees by a validator for it's service 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 ## 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 At the core of the Staking module is the concept of a pool which denotes a
Atom holders. There are two global pools in the Staking module: bonded pool and unbonded pool. Bonded Atoms are part collection of Atoms contributed by different Atom holders. There are two global
of the global bonded pool. On the other side, if a candidate or delegator wants to unbond its Atoms, those Atoms are pools in the Staking module: the bonded pool and unbonding pool. Bonded Atoms
kept in the unbonding pool for a duration of the unbonding period. In the Staking module, a pool is logical concept, are part of the global bonded pool. If a candidate or delegator wants to unbond
i.e., there is no pool data structure that would be responsible for managing pool resources. Instead, it is managed its Atoms, those Atoms are moved to the the unbonding pool for the duration of
in a distributed way. More precisely, at the global level, for each pool, we track only the total amount of the unbonding period. In the Staking module, a pool is a logical concept, i.e.,
(bonded or unbonded) Atoms and a current amount of issued shares. A share is a unit of Atom distribution and the there is no pool data structure that would be responsible for managing pool
value of the share (share-to-atom exchange rate) is changing during the system execution. The resources. Instead, it is managed in a distributed way. More precisely, at the
share-to-atom exchange rate can be computed as: 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 Then for each validator candidate (in a per candidate data structure) we keep track of
in a pool. At any point in time, the exact amount of Atoms a candidate has in the pool the amount of shares the candidate owns in a pool. At any point in time,
can be computed as the number of shares it owns multiplied with the share-to-atom exchange rate: 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 The benefit of such accounting of the pool resources is the fact that a
bonding/unbonding/slashing/provisioning of atoms affects only global data (size of the pool and the number of shares) modification to the pool from bonding/unbonding/slashing/provisioning of
and the related validator/candidate data structure, i.e., the data structure of other validators do not need to be Atoms affects only global data (size of the pool and the number of shares) and
modified. Let's explain this further with several small examples: 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 We consider initially 4 validators p1, p2, p3 and p4, and that each validator
to a bonded pool. Furthermore, let's assume that we have issued initially 40 shares (note that the initial distribution has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
of the shares, i.e., share-to-atom exchange rate can be set to any meaningful value), i.e., issued initially 40 shares (note that the initial distribution of the shares,
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 i.e., share-to-atom exchange rate can be set to any meaningful value), i.e.,
the amount of issued shares is equal to 40. And for each validator we store in their corresponding data structure share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we
that each has 10 shares of the bonded pool. Now lets assume that the validator p4 starts process of unbonding of 5 have, the size of the pool is 40 Atoms, and the amount of issued shares is
shares. Then the total size of the pool is decreased and now it will be 35 shares and the amount of Atoms is 35. equal to 40. And for each validator we store in their corresponding data
Note that the only change in other data structures needed is reducing the number of shares for a validator p4 from 10 structure that each has 10 shares of the bonded pool. Now lets assume that the
to 5. 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 Let's consider now the case where a validator p1 wants to bond 15 more atoms to
is 50, and as the exchange rate hasn't changed (1 share is still worth 1 Atom), we need to create more shares, the pool. Now the size of the pool is 50, and as the exchange rate hasn't
i.e. we now have 50 shares in the pool in total. changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we
Validators p2, p3 and p4 still have (correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we now have 50 shares in the pool in total. Validators p2, p3 and p4 still have
don't need to modify anything in their corresponding data structures. But p1 now has 25 shares, so we update the amount (correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we
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 don't need to modify anything in their corresponding data structures. But p1
data structures refer only to shares. 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 Finally, let's consider what happens when new Atoms are created and added to
the inflation rate is 10 percent and that it is applied to the current state of the pool. This means that 5 Atoms are the pool due to inflation. Let's assume that the inflation rate is 10 percent
created and added to the pool and that each validator now proportionally increase it's Atom count. Let's analyse how this and that it is applied to the current state of the pool. This means that 5
change is reflected in the data structures. First, the size of the pool is increased and is now 55 atoms. As a share of Atoms are created and added to the pool and that each validator now
each validator in the pool hasn't changed, this means that the total number of shares stay the same (50) and that the proportionally increase it's Atom count. Let's analyse how this change is
amount of shares of each validator stays the same (correspondingly 25, 10, 10, 5). But the exchange rate has changed and reflected in the data structures. First, the size of the pool is increased and
each share is now worth 55/50 Atoms per share, so each validator has effectively increased amount of Atoms it has. is now 55 atoms. As a share of each validator in the pool hasn't changed, this
So validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms. 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 concepts of the pool and its shares is at the core of the accounting in the
the global pools (such as bonding and unbonding pool), but also for distribution of Atoms between validator and its 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). delegators (we will explain this in section X).
#### Delegator shares #### Delegator shares
A candidate is, depending on it's status, contributing Atoms to either the bonded or unbonded pool, and in return gets A candidate is, depending on it's status, contributing Atoms to either the
some amount of (global) pool shares. Note that not all those Atoms (and respective shares) are owned by the candidate bonded or unbonding pool, and in return gets some amount of (global) pool
as some Atoms could be delegated to a candidate. The mechanism for distribution of Atoms (and shares) between a shares. Note that not all those Atoms (and respective shares) are owned by the
candidate and it's delegators is based on a notion of delegator shares. More precisely, every candidate is issuing candidate as some Atoms could be delegated to a candidate. The mechanism for
(local) delegator shares (`Candidate.IssuedDelegatorShares`) that represents some portion of global shares distribution of Atoms (and shares) between a candidate and it's delegators is
managed by the candidate (`Candidate.GlobalStakeShares`). The principle behind managing delegator shares is the same based on a notion of delegator shares. More precisely, every candidate is
as described in [Section](#The pool and the share). We now illustrate it with an example. 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. Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator
Furthermore, lets assume that we have issued initially 40 global shares, i.e., that `share-to-atom-ex-rate = 1 atom per has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
share`. So we will `GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the Candidate data structure issued initially 40 global shares, i.e., that
of each validator `Candidate.GlobalStakeShares = 10`. Furthermore, each validator issued 10 delegator `share-to-atom-exchange-rate = 1 atom per share`. So we will set
shares which are initially owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where `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`. `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 Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and
to make to the data structures. First, `GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, consider what are the updates we need to make to the data structures. First,
for validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to issue also additional delegator shares, `GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for
i.e., `Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator shares of validator p1, where validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to
each delegator share is worth 1 global shares, i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due issue also additional delegator shares, i.e.,
to inflation. In that case, we only need to update `GlobalState.BondedPool` which is now equal to 50 Atoms as created `Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator
Atoms are added to the bonded pool. Note that the amount of global and delegator shares stay the same but they are now shares of validator p1, where each delegator share is worth 1 global shares,
worth more as share-to-atom-ex-rate is now worth 50/45 Atoms per share. Therefore, a delegator d1 now owns 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
`delegatorCoins = 10 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 100/9 Atoms` 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
```

View File

@ -2,54 +2,66 @@
## Overview ## Overview
The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that serves as a backbone of the Cosmos ecosystem. The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that
It is operated and secured by an open and globally decentralized set of validators. Tendermint consensus is a serves as a backbone of the Cosmos ecosystem. It is operated and secured by an
Byzantine fault-tolerant distributed protocol that involves all validators in the process of exchanging protocol open and globally decentralized set of validators. Tendermint consensus is a
messages in the production of each block. To avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up Byzantine fault-tolerant distributed protocol that involves all validators in
coins in a bond deposit. Tendermint protocol messages are signed by the validator's private key, and this is a basis for the process of exchanging protocol messages in the production of each block. To
Tendermint strict accountability that allows punishing misbehaving validators by slashing (burning) their bonded Atoms. avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up
On the other hand, validators are for it's service of securing blockchain network rewarded by the inflationary coins in a bond deposit. Tendermint protocol messages are signed by the
provisions and transactions fees. This incentivizes correct behavior of the validators and provide economic security validator's private key, and this is a basis for Tendermint strict
of the network. 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. The native token of the Cosmos Hub is called Atom; becoming a validator of the
However, not all Atom holders are validators of the Cosmos Hub. More precisely, there is a selection process that Cosmos Hub requires holding Atoms. However, not all Atom holders are validators
determines the validator set as a subset of all validator candidates (Atom holder that wants to of the Cosmos Hub. More precisely, there is a selection process that determines
become a validator). The other option for Atom holder is to delegate their atoms to validators, i.e., the validator set as a subset of all validator candidates (Atom holders that
being a delegator. A delegator is an Atom holder that has bonded its Atoms by delegating it to a validator wants to become a validator). The other option for Atom holder is to delegate
(or validator candidate). By bonding Atoms to securing network (and taking a risk of being slashed in case the their atoms to validators, i.e., being a delegator. A delegator is an Atom
validator misbehaves), a user is rewarded with inflationary provisions and transaction fees proportional to the amount holder that has bonded its Atoms by delegating it to a validator (or validator
of its bonded Atoms. The Cosmos Hub is designed to efficiently facilitate a small numbers of validators (hundreds), and candidate). By bonding Atoms to secure the network (and taking a risk of being
large numbers of delegators (tens of thousands). More precisely, it is the role of the Staking module of the Cosmos Hub slashed in case of misbehaviour), a user is rewarded with inflationary
to support various staking functionality including validator set selection; delegating, bonding and withdrawing Atoms; provisions and transaction fees proportional to the amount of its bonded Atoms.
and the distribution of inflationary provisions and transaction fees. 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 ## State
The staking module persists the following information to the store: The staking module persists the following information to the store:
- `GlobalState`, describing the global pools and the inflation related fields * `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 * validator candidates (including current validators), indexed by public key and shares in the global pool
- `map[rational.Rat]Candidate`, an ordered map of validator candidates (including current validators), indexed by (bonded or unbonded depending on candidate status)
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
- `map[[]byte]DelegatorBond`, a map of DelegatorBonds (for each delegation to a candidate by a delegator), indexed by public key
the delegator address and the candidate public key * the queue of unbonding delegations
- `queue[QueueElemUnbondDelegation]`, a queue of unbonding delegations * the queue of re-delegations
- `queue[QueueElemReDelegate]`, a queue of re-delegations
### Global State ### Global State
GlobalState data structure contains total Atoms supply, amount of Atoms in the bonded pool, sum of all shares The GlobalState data structure contains total Atom supply, amount of Atoms in
distributed for the bonded pool, amount of Atoms in the unbonded pool, sum of all shares distributed for the the bonded pool, sum of all shares distributed for the bonded pool, amount of
unbonded pool, a timestamp of the last processing of inflation, the current annual inflation rate, a timestamp Atoms in the unbonded pool, sum of all shares distributed for the unbonded
for the last comission accounting reset, the global fee pool, a pool of reserve taxes collected for the governance use pool, a timestamp of the last processing of inflation, the current annual
and an adjustment factor for calculating global feel accum (?). 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.
``` golang ``` go
type GlobalState struct { type GlobalState struct {
TotalSupply int64 // total supply of Atoms TotalSupply int64 // total supply of Atoms
BondedPool int64 // reserve of bonded tokens BondedPool int64 // reserve of bonded tokens
BondedShares rational.Rat // sum of all shares distributed for the BondedPool 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 UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool
InflationLastTime int64 // timestamp of last processing of inflation InflationLastTime int64 // timestamp of last processing of inflation
Inflation rational.Rat // current annual inflation rate 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 ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use
Adjustment rational.Rat // Adjustment factor for calculating global fee accum 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 ### Candidate
The `Candidate` data structure holds the current state and some historical actions of The `Candidate` data structure holds the current state and some historical
validators or candidate-validators. actions of validators or candidate-validators.
``` golang ``` go
type Candidate struct { type Candidate struct {
Status CandidateStatus Status CandidateStatus
PubKey crypto.PubKey ConsensusPubKey crypto.PubKey
GovernancePubKey crypto.PubKey GovernancePubKey crypto.PubKey
Owner Address Owner crypto.Address
GlobalStakeShares rational.Rat GlobalStakeShares rational.Rat
IssuedDelegatorShares rational.Rat IssuedDelegatorShares rational.Rat
RedelegatingShares rational.Rat RedelegatingShares rational.Rat
@ -83,118 +116,115 @@ type Candidate struct {
Adjustment rational.Rat Adjustment rational.Rat
Description Description Description Description
} }
```
CandidateStatus can be VyingUnbonded, VyingUnbonding, Bonded, KickUnbonding and KickUnbonded.
``` golang
type Description struct { type Description struct {
Name string Name string
DateBonded string DateBonded string
Identity string Identity string
Website string Website string
Details string Details string
} }
``` ```
Candidate parameters are described: Candidate parameters are described:
- Status: signal that the candidate is either vying for validator status, * Status: it can be Bonded (active validator), Unbonding (validator candidate)
either unbonded or unbonding, an active validator, or a kicked validator or Revoked
either unbonding or unbonded. * ConsensusPubKey: candidate public key that is used strictly for participating in
- PubKey: separated key from the owner of the candidate as is used strictly consensus
for participating in consensus. * GovernancePubKey: public key used by the validator for governance voting
- Owner: Address where coins are bonded from and unbonded to * Owner: Address that is allowed to unbond coins.
- GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if * GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
`Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` otherwise `Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool`
- IssuedDelegatorShares: Sum of all shares a candidate issued to delegators (which otherwise
includes the candidate's self-bond); a delegator share represents their stake in * IssuedDelegatorShares: Sum of all shares a candidate issued to delegators
the Candidate's `GlobalStakeShares` (which includes the candidate's self-bond); a delegator share represents
- RedelegatingShares: The portion of `IssuedDelegatorShares` which are their stake in the Candidate's `GlobalStakeShares`
currently re-delegating to a new validator * RedelegatingShares: The portion of `IssuedDelegatorShares` which are
- VotingPower: Proportional to the amount of bonded tokens which the validator currently re-delegating to a new validator
has if the candidate is a validator. * VotingPower: Proportional to the amount of bonded tokens which the validator
- Commission: The commission rate of fees charged to any delegators has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0`
- CommissionMax: The maximum commission rate this candidate can charge * Commission: The commission rate of fees charged to any delegators
each day from the date `GlobalState.DateLastCommissionReset` * CommissionMax: The maximum commission rate this candidate can charge each
- CommissionChangeRate: The maximum daily increase of the candidate commission day from the date `GlobalState.DateLastCommissionReset`
- CommissionChangeToday: Counter for the amount of change to commission rate * CommissionChangeRate: The maximum daily increase of the candidate commission
which has occurred today, reset on the first block of each day (UTC time) * CommissionChangeToday: Counter for the amount of change to commission rate
- ProposerRewardPool: reward pool for extra fees collected when this candidate which has occurred today, reset on the first block of each day (UTC time)
is the proposer of a block * ProposerRewardPool: reward pool for extra fees collected when this candidate
- Adjustment factor used to passively calculate each validators entitled fees is the proposer of a block
from `GlobalState.FeePool` * Adjustment factor used to passively calculate each validators entitled fees
- Description from `GlobalState.FeePool`
- Name: moniker * Description
- DateBonded: date determined which the validator was bonded * Name: moniker
- Identity: optional field to provide a signature which verifies the * DateBonded: date determined which the validator was bonded
validators identity (ex. UPort or Keybase) * Identity: optional field to provide a signature which verifies the
- Website: optional website link validators identity (ex. UPort or Keybase)
- Details: optional details * Website: optional website link
* Details: optional details
### DelegatorBond ### DelegatorBond
Atom holders may delegate coins to validators; under this circumstance their 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 funds are held in a `DelegatorBond` data structure. It is owned by one
associated with the shares for one validator. The sender of the transaction is delegator, and is associated with the shares for one candidate. The sender of
considered the owner of the bond. the transaction is the owner of the bond.
``` golang ``` go
type DelegatorBond struct { type DelegatorBond struct {
Candidate crypto.PubKey Candidate crypto.PubKey
Shares rational.Rat Shares rational.Rat
AdjustmentFeePool coin.Coins AdjustmentFeePool coin.Coins
AdjustmentRewardPool coin.Coins AdjustmentRewardPool coin.Coins
} }
``` ```
Description: Description:
- Candidate: the public key of the validator candidate: bonding too * Candidate: the public key of the validator candidate: bonding too
- Shares: the number of delegator shares received from the validator candidate * Shares: the number of delegator shares received from the validator candidate
- AdjustmentFeePool: Adjustment factor used to passively calculate each bonds * AdjustmentFeePool: Adjustment factor used to passively calculate each bonds
entitled fees from `GlobalState.FeePool` entitled fees from `GlobalState.FeePool`
- AdjustmentRewardPool: Adjustment factor used to passively calculate each * AdjustmentRewardPool: Adjustment factor used to passively calculate each
bonds entitled fees from `Candidate.ProposerRewardPool`` bonds entitled fees from `Candidate.ProposerRewardPool`
### QueueElem ### QueueElem
Unbonding and re-delegation process is implemented using the ordered queue data structure. The Unbonding and re-delegation process is implemented using the ordered queue
All queue elements used share a common structure: data structure. All queue elements share a common structure:
``` golang ```golang
type QueueElem struct { type QueueElem struct {
Candidate crypto.PubKey Candidate crypto.PubKey
InitHeight int64 // when the queue was initiated 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 The queue is ordered so the next element to unbond/re-delegate is at the head.
tick the head of the queue is checked and if the unbonding period has passed Every tick the head of the queue is checked and if the unbonding period has
since `InitHeight`, the final settlement of the unbonding is started or re-delegation is executed, and the element is passed since `InitTime`, the final settlement of the unbonding is started or
pop from the queue. Each `QueueElem` is persisted in the store until it is popped from the queue. 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 ### QueueElemUnbondDelegation
``` golang QueueElemUnbondDelegation structure is used in the unbonding queue.
```golang
type QueueElemUnbondDelegation struct { type QueueElemUnbondDelegation struct {
QueueElem QueueElem
Payout Address // account to pay out to Payout Address // account to pay out to
Shares rational.Rat // amount of delegator shares which are unbonding Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding
StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation 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 ### QueueElemReDelegate
``` golang QueueElemReDelegate structure is used in the re-delegation queue.
```golang
type QueueElemReDelegate struct { type QueueElemReDelegate struct {
QueueElem QueueElem
Payout Address // account to pay out to Payout Address // account to pay out to
Shares rational.Rat // amount of shares which are unbonding Shares rational.Rat // amount of shares which are unbonding
NewCandidate crypto.PubKey // validator to bond to after unbond NewCandidate crypto.PubKey // validator to bond to after unbond
} }
@ -203,31 +233,38 @@ type QueueElemReDelegate struct {
### Transaction Overview ### Transaction Overview
Available Transactions: Available Transactions:
- TxDeclareCandidacy * TxDeclareCandidacy
- TxEditCandidacy * TxEditCandidacy
- TxLivelinessCheck * TxDelegate
- TxProveLive * TxUnbond
- TxDelegate * TxRedelegate
- TxUnbond * TxLivelinessCheck
- TxRedelegate * TxProveLive
## Transaction processing ## Transaction processing
In this section we describe the processing of the transactions and the corresponding updates to the global state. In this section we describe the processing of the transactions and the
For the following text we will use gs to refer to the GlobalState data structure, candidateMap is a reference to the corresponding updates to the global state. In the following text we will use
map[PubKey]Candidate, delegatorBonds is a reference to map[[]byte]DelegatorBond, unbondDelegationQueue is a `gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a
reference to the queue[QueueElemUnbondDelegation] and redelegationQueue is the reference for the reference to the queue of unbond delegations, `reDelegationQueue` is the
queue[QueueElemReDelegate]. We use tx to denote reference to a transaction that is being processed. 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 ### TxDeclareCandidacy
A validator candidacy can be declared using the `TxDeclareCandidacy` transaction. A validator candidacy is 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?).
``` golang ```golang
type TxDeclareCandidacy struct { type TxDeclareCandidacy struct {
PubKey crypto.PubKey ConsensusPubKey crypto.PubKey
Amount coin.Coin Amount coin.Coin
GovernancePubKey crypto.PubKey GovernancePubKey crypto.PubKey
Commission rational.Rat Commission rational.Rat
@ -235,28 +272,25 @@ type TxDeclareCandidacy struct {
CommissionMaxChange int64 CommissionMaxChange int64
Description Description Description Description
} }
```
```
declareCandidacy(tx TxDeclareCandidacy): declareCandidacy(tx TxDeclareCandidacy):
// create and save the empty candidate
candidate = loadCandidate(store, tx.PubKey) 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 = NewCandidate(tx.PubKey)
candidate.Status = Unbonded candidate.Status = Unbonded
candidate.Owner = sender 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 init commision related fields based on the values from tx
candidate.ProposerRewardPool = Coin(0) candidate.ProposerRewardPool = Coin(0)
candidate.Description = tx.Description candidate.Description = tx.Description
saveCandidate(store, candidate) saveCandidate(store, candidate)
// move coins from the sender account to a (self-bond) delegator account txDelegate = TxDelegate(tx.PubKey, tx.Amount)
// the candidate account and global shares are updated within here
txDelegate = TxDelegate{tx.BondUpdate}
return delegateWithCandidate(txDelegate, candidate) return delegateWithCandidate(txDelegate, candidate)
// see delegateWithCandidate function in [TxDelegate](TxDelegate)
``` ```
### TxEditCandidacy ### TxEditCandidacy
@ -265,221 +299,326 @@ If either the `Description` (excluding `DateBonded` which is constant),
`Commission`, or the `GovernancePubKey` need to be updated, the `Commission`, or the `GovernancePubKey` need to be updated, the
`TxEditCandidacy` transaction should be sent from the owner account: `TxEditCandidacy` transaction should be sent from the owner account:
``` golang ```golang
type TxEditCandidacy struct { type TxEditCandidacy struct {
GovernancePubKey crypto.PubKey GovernancePubKey crypto.PubKey
Commission int64 Commission int64
Description Description Description Description
} }
```
```
editCandidacy(tx TxEditCandidacy): editCandidacy(tx TxEditCandidacy):
candidate = loadCandidate(store, tx.PubKey) candidate = loadCandidate(store, tx.PubKey)
if candidate == nil or candidate.Status == Unbonded return if candidate == nil or candidate.Status == Revoked return
if tx.GovernancePubKey != nil then candidate.GovernancePubKey = tx.GovernancePubKey
if tx.Commission >= 0 then candidate.Commission = tx.Commission if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey
if tx.Description != nil then candidate.Description = tx.Description if tx.Commission >= 0 candidate.Commission = tx.Commission
if tx.Description != nil candidate.Description = tx.Description
saveCandidate(store, candidate) saveCandidate(store, candidate)
return return
``` ```
### TxDelegate ### 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 ```golang
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 { type TxDelegate struct {
PubKey crypto.PubKey PubKey crypto.PubKey
Amount coin.Coin Amount coin.Coin
} }
```
```
delegate(tx TxDelegate): delegate(tx TxDelegate):
candidate = loadCandidate(store, tx.PubKey) candidate = loadCandidate(store, tx.PubKey)
if candidate == nil then return if candidate == nil return
return delegateWithCandidate(tx, candidate) return delegateWithCandidate(tx, candidate)
delegateWithCandidate(tx TxDelegate, candidate Candidate): delegateWithCandidate(tx TxDelegate, candidate Candidate):
if candidate.Status == Revoked then return if candidate.Status == Revoked return
if candidate.Status == Bonded then if candidate.Status == Bonded
poolAccount = address of the bonded pool poolAccount = params.HoldBonded
else else
poolAccount = address of the unbonded pool poolAccount = params.HoldUnbonded
// Move coins from the delegator account to the bonded pool account err = transfer(sender, poolAccount, tx.Amount)
err = transfer(sender, poolAccount, tx.Amount) if err != nil return
if err != nil then return
// Get or create the delegator bond bond = loadDelegatorBond(store, sender, tx.PubKey)
bond = loadDelegatorBond(store, sender, tx.PubKey) if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0))
if bond == nil then
bond = DelegatorBond{tx.PubKey,rational.Zero, Coin(0), Coin(0)}
issuedDelegatorShares = candidate.addTokens(tx.Amount, gs) issuedDelegatorShares = addTokens(tx.Amount, candidate)
bond.Shares = bond.Shares.Add(issuedDelegatorShares) bond.Shares += issuedDelegatorShares
saveCandidate(store, candidate) saveCandidate(store, candidate)
saveDelegatorBond(store, sender, bond)
saveGlobalState(store, gs)
return
store.Set(GetDelegatorBondKey(sender, bond.PubKey), bond) 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
saveGlobalState(store, gs) candidate.GlobalStakeShares += issuedShares
return
addTokens(amount int64, gs GlobalState, candidate Candidate): if candidate.IssuedDelegatorShares.IsZero()
// get the exchange rate of global pool shares over delegator shares
if candidate.IssuedDelegatorShares.IsZero() then
exRate = rational.One exRate = rational.One
else else
exRate = candiate.GlobalStakeShares.Quo(candidate.IssuedDelegatorShares) exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares
if candidate.Status == Bonded then issuedDelegatorShares = issuedShares / exRate
gs.BondedPool += amount candidate.IssuedDelegatorShares += issuedDelegatorShares
issuedShares = exchangeRate(gs.BondedShares, gs.BondedPool).Inv().Mul(amount) // (tokens/shares)^-1 * tokens return issuedDelegatorShares
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)
candidate.GlobalStakeShares = candidate.GlobalStakeShares.Add(issuedShares)
issuedDelegatorShares = exRate.Mul(receivedGlobalShares)
candidate.IssuedDelegatorShares = candidate.IssuedDelegatorShares.Add(issuedDelegatorShares)
return
exchangeRate(shares rational.Rat, tokenAmount int64): exchangeRate(shares rational.Rat, tokenAmount int64):
if shares.IsZero() then return rational.One if shares.IsZero() then return rational.One
return shares.Inv().Mul(tokenAmount) return tokenAmount / shares
``` ```
### TxUnbond ### TxUnbond
Delegator unbonding is defined with the following transaction: Delegator unbonding is defined with the following transaction:
``` golang ```golang
type TxUnbond struct { type TxUnbond struct {
PubKey crypto.PubKey PubKey crypto.PubKey
Shares rational.Rat Shares rational.Rat
} }
```
```
unbond(tx TxUnbond): unbond(tx TxUnbond):
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil return
if bond.Shares < tx.Shares return
// get delegator bond bond.Shares -= tx.Shares
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil then return
// subtract bond tokens from delegator bond candidate = loadCandidate(store, tx.PubKey)
if bond.Shares.LT(tx.Shares) return // bond shares < tx shares
bond.Shares = bond.Shares.Sub(ts.Shares) revokeCandidacy = false
if bond.Shares.IsZero()
candidate = loadCandidate(store, tx.PubKey) if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond)
if candidate == nil return else
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) saveDelegatorBond(store, sender, bond)
// transfer coins back to account if candidate.Status == Bonded
if candidate.Status == Bonded then poolAccount = params.HoldBonded
poolAccount = address of the bonded pool
else else
poolAccount = address of the unbonded pool poolAccount = params.HoldUnbonded
returnCoins = candidate.removeShares(shares, gs) returnedCoins = removeShares(candidate, shares)
// TODO: Shouldn't it be created a queue element in this case?
transfer(poolAccount, sender, returnCoins)
if revokeCandidacy then unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio)
// change the share types to unbonded if they were not already unbondDelegationQueue.add(unbondDelegationElem)
if candidate.Status == Bonded then
// replace bonded shares with unbonded shares
tokens = gs.removeSharesBonded(candidate.GlobalStakeShares)
candidate.GlobalStakeShares = gs.addTokensUnbonded(tokens)
candidate.Status = Unbonded
transfer(address of the bonded pool, address of the unbonded pool, tokens) transfer(poolAccount, unbondingPoolAddress, returnCoins)
// lastly update the status
candidate.Status = Revoked
// deduct shares from the candidate and save if revokeCandidacy
if candidate.GlobalStakeShares.IsZero() then if candidate.Status == Bonded then bondedToUnbondedPool(candidate)
removeCandidate(store, tx.PubKey) candidate.Status = Revoked
else
saveCandidate(store, candidate)
saveGlobalState(store, gs) if candidate.IssuedDelegatorShares.IsZero()
return removeCandidate(store, tx.PubKey)
else
saveCandidate(store, candidate)
removeDelegatorBond(candidate Candidate): saveGlobalState(store, gs)
return
// first remove from the list of bonds removeShares(candidate Candidate, shares rational.Rat):
pks = loadDelegatorCandidates(store, sender) globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares
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)
// now remove the actual bond if candidate.Status == Bonded
store.Remove(GetDelegatorBondKey(delegator, candidate)) gs.BondedShares -= globalPoolSharesToRemove
//updateDelegatorBonds(store, delegator) 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
} }
``` ```
### Inflation provisions If the `TxLivelinessCheck` is successful in kicking a validator, 5% of the
liveliness punishment is provided as a reward to `RewardAccount`.
Validator provisions are minted on an hourly basis (the first block of a new ### TxProveLive
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 If the validator was kicked for liveliness issues and is able to regain
inflation is also subject to a rate change (positive of negative) depending or liveliness then all delegators in the temporary unbonding pool which have not
the distance from the desired ratio (67%). The maximum rate change possible is transacted to move will be bonded back to the now-live validator and begin to
defined to be 13% per year, however the annual inflation is capped as between once again collect provisions and rewards. Regaining liveliness is demonstrated
7% and 20%. by sending in a `TxProveLive` transaction:
``` ```golang
inflationRateChange(0) = 0 type TxProveLive struct {
GlobalState.Inflation(0) = 0.07 PubKey crypto.PubKey
}
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 ### End of block handling
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:
``` ```golang
GlobalState.BondedPool += provisionTokensHourly 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)
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()
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
inflation = gs.Inflation + inflationRateChange
if inflation > params.InflationMax then inflation = params.InflationMax
if inflation < params.InflationMin then inflation = params.InflationMin
return inflation
UpdateValidatorSet():
candidates = loadCandidates(store)
v1 = candidates.Validators()
v2 = updateVotingPower(candidates).Validators()
change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets
return change
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
candidate.GlobalStakeShares = issuedShares
candidate.Status = Bonded
return transfer(address of the unbonded pool, address of the bonded pool, removedTokens)
``` ```