Add spec of the basic staking features
This commit is contained in:
parent
ee0b396bad
commit
5b0e222639
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
```
|
```
|
||||||
|
|
Loading…
Reference in New Issue