Merge branch 'master' into gamarin/update_gov_spec
This commit is contained in:
+20
-12
@@ -1,19 +1,27 @@
|
||||
# Cosmos Hub Spec
|
||||
|
||||
This directory contains specifications for the application level components of
|
||||
the Cosmos Hub.
|
||||
This directory contains specifications for the state transition machine of the
|
||||
Cosmos Hub.
|
||||
|
||||
NOTE: the specifications are not yet complete and very much a work in progress.
|
||||
The Cosmos Hub holds all of its state in a Merkle store. Updates to
|
||||
the store may be made during transactions and at the beginning and end of every
|
||||
block.
|
||||
|
||||
- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for
|
||||
sending tokens.
|
||||
- [Staking](staking) - Proof of Stake related specifications including bonding
|
||||
and delegation transactions, inflation, fees, etc.
|
||||
- [Governance](governance) - Governance related specifications including
|
||||
proposals and voting.
|
||||
- [IBC](ibc) - Specification of the Cosmos inter-blockchain communication (IBC) protocol.
|
||||
While the first implementation of the Cosmos Hub is built using the Cosmos-SDK,
|
||||
these specifications aim to be independent of any implementation details. That
|
||||
said, they provide a detailed resource for understanding the Cosmos-SDK.
|
||||
|
||||
- [Store](store) - The core Merkle store that holds the state.
|
||||
- [Auth](auth) - The structure and authentication of accounts and transactions.
|
||||
- [Bank](bank) - Sending tokens.
|
||||
- [Governance](governance) - Proposals and voting.
|
||||
- [Staking](staking) - Proof-of-stake bonding, delegation, etc.
|
||||
- [Slashing](slashing) - Validator punishment mechanisms.
|
||||
- [Provisioning](provisioning) - Fee distribution, and atom provision distribution
|
||||
- [IBC](ibc) - Inter-Blockchain Communication (IBC) protocol.
|
||||
- [Other](other) - Other components of the Cosmos Hub, including the reserve
|
||||
pool, All in Bits vesting, etc.
|
||||
|
||||
The [specification for Tendermint](https://github.com/tendermint/tendermint/tree/develop/docs/specification/new-spec),
|
||||
i.e. the underlying blockchain, can be found elsewhere.
|
||||
For details on the underlying blockchain and p2p protocols, see
|
||||
the [Tendermint specification](https://github.com/tendermint/tendermint/tree/develop/docs/spec).
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ const (
|
||||
|
||||
type ProposalStatus byte
|
||||
|
||||
|
||||
const (
|
||||
ProposalStatusOpen = 0x1 // Proposal is submitted. Participants can deposit on it but not vote
|
||||
ProposalStatusActive = 0x2 // MinDeposit is reachhed, participants can vote
|
||||
|
||||
@@ -166,6 +166,7 @@ vote on the proposal.
|
||||
|
||||
*Note: Gas cost for this message has to take into account the future tallying of the vote in EndBlocker*
|
||||
|
||||
|
||||
Next is a pseudocode proposal of the way `TxGovVote` transactions are
|
||||
handled:
|
||||
|
||||
@@ -188,11 +189,10 @@ handled:
|
||||
|
||||
if (proposal.CurrentStatus == ProposalStatusActive)
|
||||
|
||||
|
||||
// Sender can vote if
|
||||
// Proposal is active
|
||||
// Sender has some bonds
|
||||
|
||||
store(Governance, <txGovVote.ProposalID|'addresses'|sender>, txGovVote.Vote) // Voters can vote multiple times. Re-voting overrides previous vote. This is ok because tallying is done once at the end.
|
||||
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Bech32 on Cosmos
|
||||
|
||||
The Cosmos network prefers to use the Bech32 address format whereever users must handle binary data. Bech32 encoding provides robust integrity checks on data and the human readable part(HRP) provides contextual hints that can assist UI developers with providing informative error messages.
|
||||
|
||||
In the Cosmos network, keys and addresses may refer to a number of different roles in the network like accounts, validators etc.
|
||||
|
||||
|
||||
## HRP table
|
||||
|
||||
| HRP | Definition |
|
||||
| ------------- |:-------------:|
|
||||
| `cosmosaccaddr` | Cosmos Account Address |
|
||||
| `cosmosaccpub` | Cosmos Account Public Key |
|
||||
| `cosmosvaladdr` | Cosmos Consensus Address |
|
||||
| `cosmosvalpub` | Cosmos Consensus Public Key|
|
||||
|
||||
## Encoding
|
||||
|
||||
While all user facing interfaces to Cosmos software should exposed bech32 interfaces, many internal interfaces encode binary value in hex or base64 encoded form.
|
||||
|
||||
To covert between other binary reprsentation of addresses and keys, it is important to first apply the Amino enocoding process before bech32 encoding.
|
||||
|
||||
A complete implementation of the Amino serialization format is unncessary in most cases. Simply prepending bytes from this [table](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md#public-key-cryptography) to the bytestring payload before bech32 encoding will sufficient for compatible representation.
|
||||
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
# Fee Distribution
|
||||
|
||||
## Overview
|
||||
|
||||
Fees are pooled separately and withdrawn lazily, at any time. They are not
|
||||
bonded, and can be paid in multiple tokens. An adjustment factor is maintained
|
||||
for each validator and delegator to determine the true proportion of fees in
|
||||
the pool they are entitled too. Adjustment factors are updated every time a
|
||||
validator or delegator's voting power changes. Validators and delegators must
|
||||
withdraw all fees they are entitled too before they can bond or unbond Atoms.
|
||||
|
||||
## Affect on Staking
|
||||
|
||||
Because fees are optimized to note
|
||||
|
||||
Commission on Atom Provisions and having atoms autobonded are mutually
|
||||
exclusive (we can’t have both). The reason for this is that if there are atoms
|
||||
commissions and autobonding, the portion of atoms the fee distribution
|
||||
calculation would become very large as the atom portion for each delegator
|
||||
would change each block making a withdrawal of fees for a delegator require a
|
||||
calculation for every single block since the last withdrawal. Conclusion we can
|
||||
only have atom commission and unbonded atoms provisions, or bonded atom
|
||||
provisions and no atom commission
|
||||
|
||||
## Fee Calculations
|
||||
|
||||
Collected fees are pooled globally and divided out passively to validators and
|
||||
delegators. Each validator has the opportunity to charge commission to the
|
||||
delegators on the fees collected on behalf of the delegators by the validators.
|
||||
Fees are paid directly into a global fee pool. Due to the nature of of passive
|
||||
accounting whenever changes to parameters which affect the rate of fee
|
||||
distribution occurs, withdrawal of fees must also occur.
|
||||
|
||||
- when withdrawing one must withdrawal the maximum amount they are entitled
|
||||
too, leaving nothing in the pool,
|
||||
- when bonding, unbonding, or re-delegating tokens to an existing account a
|
||||
full withdrawal of the fees must occur (as the rules for lazy accounting
|
||||
change),
|
||||
- when a validator chooses to change the commission on fees, all accumulated
|
||||
commission fees must be simultaneously withdrawn.
|
||||
|
||||
When the validator is the proposer of the round, that validator (and their
|
||||
delegators) receives between 1% and 5% of fee rewards, the reserve tax is then
|
||||
charged, then the remainder is distributed socially by voting power to all
|
||||
validators including the proposer validator. The amount of proposer reward is
|
||||
calculated from pre-commits Tendermint messages. All provision rewards are
|
||||
added to a provision reward pool which validator holds individually. Here note
|
||||
that `BondedShares` represents the sum of all voting power saved in the
|
||||
`GlobalState` (denoted `gs`).
|
||||
|
||||
```
|
||||
proposerReward = feesCollected * (0.01 + 0.04
|
||||
* sumOfVotingPowerOfPrecommitValidators / gs.BondedShares)
|
||||
validator.ProposerRewardPool += proposerReward
|
||||
|
||||
reserveTaxed = feesCollected * params.ReserveTax
|
||||
gs.ReservePool += reserveTaxed
|
||||
|
||||
distributedReward = feesCollected - proposerReward - reserveTaxed
|
||||
gs.FeePool += distributedReward
|
||||
gs.SumFeesReceived += distributedReward
|
||||
gs.RecentFee = distributedReward
|
||||
```
|
||||
|
||||
The entitlement to the fee pool held by the each validator can be accounted for
|
||||
lazily. First we must account for a validator's `count` and `adjustment`. The
|
||||
`count` represents a lazy accounting of what that validators entitlement to the
|
||||
fee pool would be if there `VotingPower` was to never change and they were to
|
||||
never withdraw fees.
|
||||
|
||||
```
|
||||
validator.count = validator.VotingPower * BlockHeight
|
||||
```
|
||||
|
||||
Similarly the GlobalState count can be passively calculated whenever needed,
|
||||
where `BondedShares` is the updated sum of voting powers from all validators.
|
||||
|
||||
```
|
||||
gs.count = gs.BondedShares * BlockHeight
|
||||
```
|
||||
|
||||
The `adjustment` term accounts for changes in voting power and withdrawals of
|
||||
fees. The adjustment factor must be persisted with the validator and modified
|
||||
whenever fees are withdrawn from the validator or the voting power of the
|
||||
validator changes. When the voting power of the validator changes the
|
||||
`Adjustment` factor is increased/decreased by the cumulative difference in the
|
||||
voting power if the voting power has been the new voting power as opposed to
|
||||
the old voting power for the entire duration of the blockchain up the previous
|
||||
block. Each time there is an adjustment change the GlobalState (denoted `gs`)
|
||||
`Adjustment` must also be updated.
|
||||
|
||||
```
|
||||
simplePool = validator.count / gs.count * gs.SumFeesReceived
|
||||
projectedPool = validator.PrevPower * (height-1)
|
||||
/ (gs.PrevPower * (height-1)) * gs.PrevFeesReceived
|
||||
+ validator.Power / gs.Power * gs.RecentFee
|
||||
|
||||
AdjustmentChange = simplePool - projectedPool
|
||||
validator.AdjustmentRewardPool += AdjustmentChange
|
||||
gs.Adjustment += AdjustmentChange
|
||||
```
|
||||
|
||||
Every instance that the voting power changes, information about the state of
|
||||
the validator set during the change must be recorded as a `powerChange` for
|
||||
other validators to run through. Before any validator modifies its voting power
|
||||
it must first run through the above calculation to determine the change in
|
||||
their `caandidate.AdjustmentRewardPool` for all historical changes in the set
|
||||
of `powerChange` which they have not yet synced to. The set of all
|
||||
`powerChange` may be trimmed from its oldest members once all validators have
|
||||
synced past the height of the oldest `powerChange`. This trim procedure will
|
||||
occur on an epoch basis.
|
||||
|
||||
```golang
|
||||
type powerChange struct {
|
||||
height int64 // block height at change
|
||||
power rational.Rat // total power at change
|
||||
prevpower rational.Rat // total power at previous height-1
|
||||
feesin coins.Coin // fees in at block height
|
||||
prevFeePool coins.Coin // total fees in at previous block height
|
||||
}
|
||||
```
|
||||
|
||||
Note that the adjustment factor may result as negative if the voting power of a
|
||||
different validator has decreased.
|
||||
|
||||
```
|
||||
validator.AdjustmentRewardPool += withdrawn
|
||||
gs.Adjustment += withdrawn
|
||||
```
|
||||
|
||||
Now the entitled fee pool of each validator can be lazily accounted for at
|
||||
any given block:
|
||||
|
||||
```
|
||||
validator.feePool = validator.simplePool - validator.Adjustment
|
||||
```
|
||||
|
||||
So far we have covered two sources fees which can be withdrawn from: Fees from
|
||||
proposer rewards (`validator.ProposerRewardPool`), and fees from the fee pool
|
||||
(`validator.feePool`). However we should note that all fees from fee pool are
|
||||
subject to commission rate from the owner of the validator. These next
|
||||
calculations outline the math behind withdrawing fee rewards as either a
|
||||
delegator to a validator providing commission, or as the owner of a validator
|
||||
who is receiving commission.
|
||||
|
||||
### Calculations For Delegators and Validators
|
||||
|
||||
The same mechanism described to calculate the fees which an entire validator is
|
||||
entitled to is be applied to delegator level to determine the entitled fees for
|
||||
each delegator and the validators entitled commission from `gs.FeesPool` and
|
||||
`validator.ProposerRewardPool`.
|
||||
|
||||
The calculations are identical with a few modifications to the parameters:
|
||||
- Delegator's entitlement to `gs.FeePool`:
|
||||
- entitled party voting power should be taken as the effective voting power
|
||||
after commission is retrieved,
|
||||
`bond.Shares/validator.TotalDelegatorShares * validator.VotingPower * (1 - validator.Commission)`
|
||||
- Delegator's entitlement to `validator.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`validator.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the effective shares after
|
||||
commission is retrieved, `bond.Shares * (1 - validator.Commission)`
|
||||
- Validator's commission entitlement to `gs.FeePool`
|
||||
- entitled party voting power should be taken as the effective voting power
|
||||
of commission portion of total voting power,
|
||||
`validator.VotingPower * validator.Commission`
|
||||
- Validator's commission entitlement to `validator.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`validator.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the of commission portion
|
||||
of total delegators shares,
|
||||
`validator.TotalDelegatorShares * validator.Commission`
|
||||
|
||||
For more implementation ideas see spreadsheet `spec/AbsoluteFeeDistrModel.xlsx`
|
||||
|
||||
As mentioned earlier, every time the voting power of a delegator bond is
|
||||
changing either by unbonding or further bonding, all fees must be
|
||||
simultaneously withdrawn. Similarly if the validator changes the commission
|
||||
rate, all commission on fees must be simultaneously withdrawn.
|
||||
|
||||
### Other general notes on fees accounting
|
||||
|
||||
- When a delegator chooses to re-delegate shares, fees continue to accumulate
|
||||
until the re-delegation queue reaches maturity. At the block which the queue
|
||||
reaches maturity and shares are re-delegated all available fees are
|
||||
simultaneously withdrawn.
|
||||
- Whenever a totally new validator is added to the validator set, the `accum`
|
||||
of the entire validator must be 0, meaning that the initial value for
|
||||
`validator.Adjustment` must be set to the value of `canidate.Count` for the
|
||||
height which the validator is added on the validator set.
|
||||
- The feePool of a new delegator bond will be 0 for the height at which the bond
|
||||
was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to
|
||||
the height which the bond was added.
|
||||
|
||||
### Atom 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
|
||||
Inflation(0) = 0.07
|
||||
|
||||
bondedRatio = Pool.BondedTokens / Pool.TotalSupplyTokens
|
||||
AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13
|
||||
|
||||
annualInflation += AnnualInflationRateChange
|
||||
|
||||
if annualInflation > 0.20 then Inflation = 0.20
|
||||
if annualInflation < 0.07 then Inflation = 0.07
|
||||
|
||||
provisionTokensHourly = Pool.TotalSupplyTokens * 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
|
||||
Pool.BondedPool += provisionTokensHourly
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
Validator
|
||||
|
||||
* Adjustment factor used to passively calculate each validators entitled fees
|
||||
from `GlobalState.FeePool`
|
||||
|
||||
Delegation Shares
|
||||
|
||||
* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds
|
||||
entitled fees from `GlobalState.FeePool`
|
||||
* AdjustmentRewardPool: Adjustment factor used to passively calculate each
|
||||
bonds entitled fees from `Validator.ProposerRewardPool`
|
||||
@@ -0,0 +1,115 @@
|
||||
# End-Block
|
||||
|
||||
## Slashing
|
||||
|
||||
Tendermint blocks can include
|
||||
[Evidence](https://github.com/tendermint/tendermint/blob/develop/docs/spec/blockchain/blockchain.md#evidence), which indicates that a validator
|
||||
committed malicious behaviour. The relevant information is forwarded to the
|
||||
application as [ABCI
|
||||
Evidence](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto#L259), so the validator an be accordingly punished.
|
||||
|
||||
For some `evidence` to be valid, it must satisfy:
|
||||
|
||||
`evidence.Timestamp >= block.Timestamp - MAX_EVIDENCE_AGE`
|
||||
|
||||
where `evidence.Timestamp` is the timestamp in the block at height
|
||||
`evidence.Height` and `block.Timestamp` is the current block timestamp.
|
||||
|
||||
If valid evidence is included in a block, the validator's stake is reduced by `SLASH_PROPORTION` of
|
||||
what their stake was when the infraction occurred (rather than when the evidence was discovered).
|
||||
We want to "follow the stake": the stake which contributed to the infraction should be
|
||||
slashed, even if it has since been redelegated or started unbonding.
|
||||
|
||||
We first need to loop through the unbondings and redelegations from the slashed validator
|
||||
and track how much stake has since moved:
|
||||
|
||||
```
|
||||
slashAmountUnbondings := 0
|
||||
slashAmountRedelegations := 0
|
||||
|
||||
unbondings := getUnbondings(validator.Address)
|
||||
for unbond in unbondings {
|
||||
|
||||
if was not bonded before evidence.Height or started unbonding before unbonding period ago {
|
||||
continue
|
||||
}
|
||||
|
||||
burn := unbond.InitialTokens * SLASH_PROPORTION
|
||||
slashAmountUnbondings += burn
|
||||
|
||||
unbond.Tokens = max(0, unbond.Tokens - burn)
|
||||
}
|
||||
|
||||
// only care if source gets slashed because we're already bonded to destination
|
||||
// so if destination validator gets slashed our delegation just has same shares
|
||||
// of smaller pool.
|
||||
redels := getRedelegationsBySource(validator.Address)
|
||||
for redel in redels {
|
||||
|
||||
if was not bonded before evidence.Height or started redelegating before unbonding period ago {
|
||||
continue
|
||||
}
|
||||
|
||||
burn := redel.InitialTokens * SLASH_PROPORTION
|
||||
slashAmountRedelegations += burn
|
||||
|
||||
amount := unbondFromValidator(redel.Destination, burn)
|
||||
destroy(amount)
|
||||
}
|
||||
```
|
||||
|
||||
We then slash the validator:
|
||||
|
||||
```
|
||||
curVal := validator
|
||||
oldVal := loadValidator(evidence.Height, evidence.Address)
|
||||
|
||||
slashAmount := SLASH_PROPORTION * oldVal.Shares
|
||||
slashAmount -= slashAmountUnbondings
|
||||
slashAmount -= slashAmountRedelegations
|
||||
|
||||
curVal.Shares = max(0, curVal.Shares - slashAmount)
|
||||
```
|
||||
|
||||
This ensures that offending validators are punished the same amount whether they
|
||||
act as a single validator with X stake or as N validators with collectively X
|
||||
stake.
|
||||
|
||||
## Automatic Unbonding
|
||||
|
||||
At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded:
|
||||
|
||||
```
|
||||
height := block.Height
|
||||
|
||||
for val in block.Validators:
|
||||
signInfo = SigningInfo.Get(val.Address)
|
||||
if signInfo == nil{
|
||||
signInfo.StartHeight = height
|
||||
}
|
||||
|
||||
index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW
|
||||
signInfo.IndexOffset++
|
||||
previous = SigningBitArray.Get(val.Address, index)
|
||||
|
||||
// update counter if array has changed
|
||||
if previous and val in block.AbsentValidators:
|
||||
SigningBitArray.Set(val.Address, index, false)
|
||||
signInfo.SignedBlocksCounter--
|
||||
else if !previous and val not in block.AbsentValidators:
|
||||
SigningBitArray.Set(val.Address, index, true)
|
||||
signInfo.SignedBlocksCounter++
|
||||
// else previous == val not in block.AbsentValidators, no change
|
||||
|
||||
// validator must be active for at least SIGNED_BLOCKS_WINDOW
|
||||
// before they can be automatically unbonded for failing to be
|
||||
// included in 50% of the recent LastCommits
|
||||
minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW
|
||||
minSigned = SIGNED_BLOCKS_WINDOW / 2
|
||||
if height > minHeight AND signInfo.SignedBlocksCounter < minSigned:
|
||||
signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION
|
||||
|
||||
slash & unbond the validator
|
||||
|
||||
SigningInfo.Set(val.Address, signInfo)
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
## State
|
||||
|
||||
### Signing Info
|
||||
|
||||
Every block includes a set of precommits by the validators for the previous block,
|
||||
known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power.
|
||||
|
||||
Proposers are incentivized to include precommits from all
|
||||
validators in the LastCommit by receiving additional fees
|
||||
proportional to the difference between the voting power included in the
|
||||
LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)).
|
||||
|
||||
Validators are penalized for failing to be included in the LastCommit for some
|
||||
number of blocks by being automatically unbonded.
|
||||
|
||||
Information about validator activity is tracked in a `ValidatorSigningInfo`.
|
||||
It is indexed in the store as follows:
|
||||
|
||||
- SigningInfo: ` 0x01 | ValTendermintAddr -> amino(valSigningInfo)`
|
||||
- SigningBitArray: ` 0x02 | ValTendermintAddr | LittleEndianUint64(signArrayIndex) -> VarInt(didSign)`
|
||||
|
||||
The first map allows us to easily lookup the recent signing info for a
|
||||
validator, according to the Tendermint validator address. The second map acts as
|
||||
a bit-array of size `SIGNED_BLOCKS_WINDOW` that tells us if the validator signed for a given index in the bit-array.
|
||||
|
||||
The index in the bit-array is given as little endian uint64.
|
||||
|
||||
The result is a `varint` that takes on `0` or `1`, where `0` indicates the
|
||||
validator did not sign the corresponding block, and `1` indicates they did.
|
||||
|
||||
Note that the SigningBitArray is not explicitly initialized up-front. Keys are
|
||||
added as we progress through the first `SIGNED_BLOCKS_WINDOW` blocks for a newly
|
||||
bonded validator.
|
||||
|
||||
The information stored for tracking validator liveness is as follows:
|
||||
|
||||
```go
|
||||
type ValidatorSigningInfo struct {
|
||||
StartHeight int64
|
||||
IndexOffset int64
|
||||
JailedUntil int64
|
||||
SignedBlocksCounter int64
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Where:
|
||||
* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power).
|
||||
* `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not).
|
||||
* `JailedUntil` is set whenever the candidate is revoked due to downtime
|
||||
* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always.
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
### TxProveLive
|
||||
|
||||
If a validator was automatically unbonded due to liveness issues and wishes to
|
||||
assert it is still online, it can send `TxProveLive`:
|
||||
|
||||
```golang
|
||||
type TxProveLive struct {
|
||||
PubKey crypto.PubKey
|
||||
}
|
||||
```
|
||||
|
||||
All delegators in the temporary unbonding pool which have not
|
||||
transacted to move will be bonded back to the now-live validator and begin to
|
||||
once again collect provisions and rewards.
|
||||
|
||||
```
|
||||
TODO: pseudo-code
|
||||
```
|
||||
+22
-14
@@ -2,30 +2,38 @@
|
||||
|
||||
## Abstract
|
||||
|
||||
This paper specifies the Staking module of the Cosmos-SDK, which was first described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in June 2016.
|
||||
This paper specifies the Staking module of the Cosmos-SDK, which was first
|
||||
described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper)
|
||||
in June 2016.
|
||||
|
||||
The module enables Cosmos-SDK based blockchain to support an advanced Proof-of-Stake system. In this system, holders of the native staking token of the chain can become candidate validators and can delegate tokens to candidate validators, ultimately determining the effective validator set for the system.
|
||||
The module enables Cosmos-SDK based blockchain to support an advanced
|
||||
Proof-of-Stake system. In this system, holders of the native staking token of
|
||||
the chain can become validators and can delegate tokens to validator
|
||||
validators, ultimately determining the effective validator set for the system.
|
||||
|
||||
This module will be used in the Cosmos Hub, the first Hub in the Cosmos network.
|
||||
This module will be used in the Cosmos Hub, the first Hub in the Cosmos
|
||||
network.
|
||||
|
||||
## Contents
|
||||
|
||||
The following specification uses *Atom* as the native staking token. The module can be adapted to any Proof-Of-Stake blockchain by replacing *Atom* with the native staking token of the chain.
|
||||
The following specification uses *Atom* as the native staking token. The module
|
||||
can be adapted to any Proof-Of-Stake blockchain by replacing *Atom* with the
|
||||
native staking token of the chain.
|
||||
|
||||
1. **[Design overview](overview.md)**
|
||||
2. **Implementation**
|
||||
1. **[State](state.md)**
|
||||
1. Global State
|
||||
2. Validator Candidates
|
||||
3. Delegator Bonds
|
||||
4. Unbond and Rebond Queue
|
||||
1. Params
|
||||
1. Pool
|
||||
2. Validators
|
||||
3. Delegations
|
||||
2. **[Transactions](transactions.md)**
|
||||
1. Declare Candidacy
|
||||
2. Edit Candidacy
|
||||
3. Delegate
|
||||
4. Unbond
|
||||
5. Redelegate
|
||||
6. ProveLive
|
||||
1. Create-Validator
|
||||
2. Edit-Validator
|
||||
3. Repeal-Revocation
|
||||
4. Delegate
|
||||
5. Unbond
|
||||
6. Redelegate
|
||||
3. **[Validator Set Changes](valset-changes.md)**
|
||||
1. Validator set updates
|
||||
2. Slashing
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# End-Block
|
||||
|
||||
Two staking activities are intended to be processed in the application end-block.
|
||||
- inform Tendermint of validator set changes
|
||||
- process and set atom inflation
|
||||
|
||||
# Validator Set Changes
|
||||
|
||||
The Tendermint validator set may be updated by state transitions that run at
|
||||
the end of every block. The Tendermint validator set may be changed by
|
||||
validators either being revoked due to inactivity/unexpected behaviour (covered
|
||||
in slashing) or changed in validator power. Determining which validator set
|
||||
changes must be made occurs during staking transactions (and slashing
|
||||
transactions) - during end-block the already accounted changes are applied and
|
||||
the changes cleared
|
||||
|
||||
```golang
|
||||
EndBlock() ValidatorSetChanges
|
||||
vsc = GetTendermintUpdates()
|
||||
ClearTendermintUpdates()
|
||||
return vsc
|
||||
```
|
||||
|
||||
# Inflation
|
||||
|
||||
The atom inflation rate is changed once per hour based on the current and
|
||||
historic bond ratio
|
||||
|
||||
```golang
|
||||
processProvisions():
|
||||
hrsPerYr = 8766 // as defined by a julian year of 365.25 days
|
||||
|
||||
time = BFTTime()
|
||||
if time > pool.InflationLastTime + ProvisionTimeout
|
||||
pool.InflationLastTime = time
|
||||
pool.Inflation = nextInflation(hrsPerYr).Round(1000000000)
|
||||
|
||||
provisions = pool.Inflation * (pool.TotalSupply / hrsPerYr)
|
||||
|
||||
pool.LooseTokens += provisions
|
||||
feePool += LooseTokens
|
||||
|
||||
setPool(pool)
|
||||
|
||||
nextInflation(hrsPerYr rational.Rat):
|
||||
if pool.TotalSupply > 0
|
||||
bondedRatio = pool.BondedPool / pool.TotalSupply
|
||||
else
|
||||
bondedRation = 0
|
||||
|
||||
inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange
|
||||
inflationRateChange = inflationRateChangePerYear / hrsPerYr
|
||||
|
||||
inflation = pool.Inflation + inflationRateChange
|
||||
if inflation > params.InflationMax then inflation = params.InflationMax
|
||||
|
||||
if inflation < params.InflationMin then inflation = params.InflationMin
|
||||
|
||||
return inflation
|
||||
```
|
||||
|
||||
@@ -1,675 +0,0 @@
|
||||
# Stake Module
|
||||
|
||||
## Overview
|
||||
|
||||
The stake module is tasked with various core staking functionality. Through the
|
||||
stake module atoms may be bonded, delegated, and provisions/rewards are
|
||||
distributed. Atom provisions are distributed to validators and their delegators
|
||||
through share distribution of a collective pool of all staked atoms. As atoms
|
||||
are created they are added to the common pool and each share become
|
||||
proportionally worth more atoms. Fees are distributed through a similar pooling
|
||||
mechanism but where each validator and delegator maintains an adjustment factor
|
||||
to determine the true proportion of fees they are entitled too. This adjustment
|
||||
factor is updated for each delegator and validator for each block where changes
|
||||
to the voting power occurs in the network. Broken down, the stake module at a
|
||||
high level is responsible for:
|
||||
- Declaration of candidacy for becoming a validator
|
||||
- Updating Tendermint validating power to reflect slashable stake
|
||||
- Delegation and unbonding transactions
|
||||
- Implementing unbonding period
|
||||
- Provisioning Atoms
|
||||
- Managing and distributing transaction fees
|
||||
- Providing the framework for validator commission on delegators
|
||||
|
||||
### Transaction Overview
|
||||
|
||||
Available Transactions:
|
||||
- TxDeclareCandidacy
|
||||
- TxEditCandidacy
|
||||
- TxLivelinessCheck
|
||||
- TxProveLive
|
||||
- TxDelegate
|
||||
- TxUnbond
|
||||
- TxRedelegate
|
||||
|
||||
## Global State
|
||||
|
||||
`Params` and `GlobalState` represent the global persistent state of Gaia.
|
||||
`Params` is intended to remain static whereas `GlobalState` is anticipated to
|
||||
change each block.
|
||||
|
||||
``` golang
|
||||
type Params struct {
|
||||
HoldBonded Address // account where all bonded coins are held
|
||||
HoldUnbonded Address // account where all delegated but unbonded coins are held
|
||||
|
||||
InflationRateChange rational.Rational // maximum annual change in inflation rate
|
||||
InflationMax rational.Rational // maximum inflation rate
|
||||
InflationMin rational.Rational // minimum inflation rate
|
||||
GoalBonded rational.Rational // Goal of percent bonded atoms
|
||||
ReserveTax rational.Rational // Tax collected on all fees
|
||||
|
||||
MaxVals uint16 // maximum number of validators
|
||||
AllowedBondDenom string // bondable coin denomination
|
||||
|
||||
// gas costs for txs
|
||||
GasDeclareCandidacy int64
|
||||
GasEditCandidacy int64
|
||||
GasDelegate int64
|
||||
GasRedelegate int64
|
||||
GasUnbond int64
|
||||
}
|
||||
```
|
||||
|
||||
``` golang
|
||||
type GlobalState struct {
|
||||
TotalSupply int64 // total supply of atom tokens
|
||||
BondedShares rational.Rat // sum of all shares distributed for the BondedPool
|
||||
UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool
|
||||
BondedPool int64 // reserve of bonded tokens
|
||||
UnbondedPool int64 // reserve of unbonded tokens held with candidates
|
||||
InflationLastTime int64 // timestamp of last processing of inflation
|
||||
Inflation rational.Rat // current annual inflation rate
|
||||
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset
|
||||
FeePool coin.Coins // fee pool for all the fee shares which have already been distributed
|
||||
ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use
|
||||
Adjustment rational.Rat // Adjustment factor for calculating global fee accum
|
||||
}
|
||||
```
|
||||
|
||||
### The Queue
|
||||
|
||||
The queue is ordered so the next to unbond/re-delegate is at the head. Every
|
||||
tick the head of the queue is checked and if the unbonding period has passed
|
||||
since `InitHeight` commence with final settlement of the unbonding and pop the
|
||||
queue. All queue elements used for unbonding share a common struct:
|
||||
|
||||
``` golang
|
||||
type QueueElem struct {
|
||||
Candidate crypto.PubKey
|
||||
InitHeight int64 // when the queue was initiated
|
||||
}
|
||||
```
|
||||
|
||||
Each `QueueElem` is persisted in the store until it is popped from the queue.
|
||||
|
||||
## Validator-Candidate
|
||||
|
||||
The `Candidate` struct holds the current state and some historical actions of
|
||||
validators or candidate-validators.
|
||||
|
||||
``` golang
|
||||
type Candidate struct {
|
||||
Status CandidateStatus
|
||||
PubKey crypto.PubKey
|
||||
GovernancePubKey crypto.PubKey
|
||||
Owner Address
|
||||
GlobalStakeShares rational.Rat
|
||||
IssuedDelegatorShares rational.Rat
|
||||
RedelegatingShares rational.Rat
|
||||
VotingPower rational.Rat
|
||||
Commission rational.Rat
|
||||
CommissionMax rational.Rat
|
||||
CommissionChangeRate rational.Rat
|
||||
CommissionChangeToday rational.Rat
|
||||
ProposerRewardPool coin.Coins
|
||||
Adjustment rational.Rat
|
||||
Description Description
|
||||
}
|
||||
|
||||
type CandidateStatus byte
|
||||
const (
|
||||
VyingUnbonded CandidateStatus = 0x00
|
||||
VyingUnbonding CandidateStatus = 0x01
|
||||
Bonded CandidateStatus = 0x02
|
||||
KickUnbonding CandidateStatus = 0x03
|
||||
KickUnbonded CandidateStatus = 0x04
|
||||
)
|
||||
|
||||
type Description struct {
|
||||
Name string
|
||||
DateBonded string
|
||||
Identity string
|
||||
Website string
|
||||
Details string
|
||||
}
|
||||
```
|
||||
|
||||
Candidate parameters are described:
|
||||
- Status: signal that the candidate is either vying for validator status
|
||||
either unbonded or unbonding, an active validator, or a kicked validator
|
||||
either unbonding or unbonded.
|
||||
- PubKey: separated key from the owner of the candidate as is used strictly
|
||||
for participating in consensus.
|
||||
- Owner: Address where coins are bonded from and unbonded to
|
||||
- GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
|
||||
`Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` if
|
||||
`Candidate.Status` is otherwise
|
||||
- IssuedDelegatorShares: Sum of all shares issued to delegators (which
|
||||
includes the candidate's self-bond) which represent each of their stake in
|
||||
the Candidate's `GlobalStakeShares`
|
||||
- RedelegatingShares: The portion of `IssuedDelegatorShares` which are
|
||||
currently re-delegating to a new validator
|
||||
- VotingPower: Proportional to the amount of bonded tokens which the validator
|
||||
has if the validator is within the top 100 validators.
|
||||
- Commission: The commission rate of fees charged to any delegators
|
||||
- CommissionMax: The maximum commission rate which this candidate can charge
|
||||
each day from the date `GlobalState.DateLastCommissionReset`
|
||||
- CommissionChangeRate: The maximum daily increase of the candidate commission
|
||||
- CommissionChangeToday: Counter for the amount of change to commission rate
|
||||
which has occurred today, reset on the first block of each day (UTC time)
|
||||
- ProposerRewardPool: reward pool for extra fees collected when this candidate
|
||||
is the proposer of a block
|
||||
- Adjustment factor used to passively calculate each validators entitled fees
|
||||
from `GlobalState.FeePool`
|
||||
- Description
|
||||
- Name: moniker
|
||||
- DateBonded: date determined which the validator was bonded
|
||||
- Identity: optional field to provide a signature which verifies the
|
||||
validators identity (ex. UPort or Keybase)
|
||||
- Website: optional website link
|
||||
- Details: optional details
|
||||
|
||||
validator candidacy can be declared using the `TxDeclareCandidacy` transaction.
|
||||
During this transaction a self-delegation transaction is executed to bond
|
||||
tokens which are sent in with the transaction.
|
||||
|
||||
``` golang
|
||||
type TxDeclareCandidacy struct {
|
||||
PubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission rational.Rat
|
||||
CommissionMax int64
|
||||
CommissionMaxChange int64
|
||||
Description Description
|
||||
}
|
||||
```
|
||||
|
||||
For all subsequent self-bonding, whether self-bonding or delegation the
|
||||
`TxDelegate` function should be used. In this context `TxUnbond` is used to
|
||||
unbond either delegation bonds or validator self-bonds.
|
||||
|
||||
If either the `Description` (excluding `DateBonded` which is constant),
|
||||
`Commission`, or the `GovernancePubKey` need to be updated, the
|
||||
`TxEditCandidacy` transaction should be sent from the owner account:
|
||||
|
||||
``` golang
|
||||
type TxEditCandidacy struct {
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission int64
|
||||
Description Description
|
||||
}
|
||||
```
|
||||
|
||||
### Persistent State
|
||||
|
||||
Within the store, each `Candidate` is stored by validator-pubkey.
|
||||
|
||||
- key: validator-pubkey
|
||||
- value: `Candidate` object
|
||||
|
||||
A second key-value pair is also persisted in order to quickly sort though the
|
||||
group of all candidates, this second index is however not persisted through the
|
||||
merkle store.
|
||||
|
||||
- key: `Candidate.GlobalStakeShares`
|
||||
- value: `Candidate.PubKey`
|
||||
|
||||
When the set of all validators needs to be determined from the group of all
|
||||
candidates, the top candidates, sorted by GlobalStakeShares can be retrieved
|
||||
from this sorting without the need to retrieve the entire group of candidates.
|
||||
When validators are kicked from the validator set they are removed from this
|
||||
list.
|
||||
|
||||
### New Validators
|
||||
|
||||
The validator set is updated in the first block of every hour. Validators are
|
||||
taken as the first `GlobalState.MaxValidators` number of candidates with the
|
||||
greatest amount of staked atoms who have not been kicked from the validator
|
||||
set.
|
||||
|
||||
### Kicked Validators
|
||||
|
||||
Unbonding of an entire validator-candidate to a temporary liquid account occurs
|
||||
under the scenarios:
|
||||
- not enough stake to be within the validator set
|
||||
- the owner unbonds all of their staked tokens
|
||||
- validator liveliness issues
|
||||
- crosses a self-imposed safety threshold
|
||||
- minimum number of tokens staked by owner
|
||||
- minimum ratio of tokens staked by owner to delegator tokens
|
||||
|
||||
When this occurs delegator's tokens do not unbond to their personal wallets but
|
||||
begin the unbonding process to a pool where they must then transact in order to
|
||||
withdraw to their respective wallets. The following unbonding will use the
|
||||
following queue element
|
||||
|
||||
``` golang
|
||||
type QueueElemUnbondCandidate struct {
|
||||
QueueElem
|
||||
}
|
||||
```
|
||||
|
||||
If a delegator chooses to initiate an unbond or re-delegation of their shares
|
||||
while a candidate-unbond is commencing, then that unbond/re-delegation is
|
||||
subject to a reduced unbonding period based on how much time those funds have
|
||||
already spent in the unbonding queue.
|
||||
|
||||
#### Liveliness issues
|
||||
|
||||
Liveliness issues are calculated by keeping track of the block precommits in
|
||||
the block header. A queue is persisted which contains the block headers from
|
||||
all recent blocks for the duration of the unbonding period. A validator is
|
||||
defined as having livliness issues if they have not been included in more than
|
||||
33% of the blocks over:
|
||||
- The most recent 24 Hours if they have >= 20% of global stake
|
||||
- The most recent week if they have = 0% of global stake
|
||||
- Linear interpolation of the above two scenarios
|
||||
|
||||
Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is
|
||||
submitted.
|
||||
|
||||
``` golang
|
||||
type TxLivelinessCheck struct {
|
||||
PubKey crypto.PubKey
|
||||
RewardAccount Addresss
|
||||
}
|
||||
```
|
||||
|
||||
If the `TxLivelinessCheck is successful in kicking a validator, 5% of the
|
||||
liveliness punishment is provided as a reward to `RewardAccount`.
|
||||
|
||||
#### Validator Liveliness Proof
|
||||
|
||||
If the validator was kicked for liveliness issues and is able to regain
|
||||
liveliness then all delegators in the temporary unbonding pool which have not
|
||||
transacted to move will be bonded back to the now-live validator and begin to
|
||||
once again collect provisions and rewards. Regaining livliness is demonstrated
|
||||
by sending in a `TxProveLive` transaction:
|
||||
|
||||
``` golang
|
||||
type TxProveLive struct {
|
||||
PubKey crypto.PubKey
|
||||
}
|
||||
```
|
||||
|
||||
## Delegator bond
|
||||
|
||||
Atom holders may delegate coins to validators, under this circumstance their
|
||||
funds are held in a `DelegatorBond`. It is owned by one delegator, and is
|
||||
associated with the shares for one validator. The sender of the transaction is
|
||||
considered to be the owner of the bond,
|
||||
|
||||
``` golang
|
||||
type DelegatorBond struct {
|
||||
Candidate crypto.PubKey
|
||||
Shares rational.Rat
|
||||
AdjustmentFeePool coin.Coins
|
||||
AdjustmentRewardPool coin.Coins
|
||||
}
|
||||
```
|
||||
|
||||
Description:
|
||||
- Candidate: pubkey of the validator candidate: bonding too
|
||||
- Shares: the number of shares received from the validator candidate
|
||||
- AdjustmentFeePool: Adjustment factor used to passively calculate each bonds
|
||||
entitled fees from `GlobalState.FeePool`
|
||||
- AdjustmentRewardPool: Adjustment factor used to passively calculate each
|
||||
bonds entitled fees from `Candidate.ProposerRewardPool``
|
||||
|
||||
Each `DelegatorBond` is individually indexed within the store by delegator
|
||||
address and candidate pubkey.
|
||||
|
||||
- key: Delegator and Candidate-Pubkey
|
||||
- value: DelegatorBond
|
||||
|
||||
|
||||
### Delegating
|
||||
|
||||
Delegator bonds are created using the TxDelegate transaction. Within this
|
||||
transaction the validator candidate queried with an amount of coins, whereby
|
||||
given the current exchange rate of candidate's delegator-shares-to-atoms the
|
||||
candidate will return shares which are assigned in `DelegatorBond.Shares`.
|
||||
|
||||
``` golang
|
||||
type TxDelegate struct {
|
||||
PubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
}
|
||||
```
|
||||
|
||||
### Unbonding
|
||||
|
||||
Delegator unbonding is defined by the following transaction type:
|
||||
|
||||
``` golang
|
||||
type TxUnbond struct {
|
||||
PubKey crypto.PubKey
|
||||
Shares rational.Rat
|
||||
}
|
||||
```
|
||||
|
||||
When unbonding is initiated, delegator shares are immediately removed from the
|
||||
candidate and added to a queue object.
|
||||
|
||||
``` golang
|
||||
type QueueElemUnbondDelegation struct {
|
||||
QueueElem
|
||||
Payout Address // account to pay out to
|
||||
Shares rational.Rat // amount of shares which are unbonding
|
||||
StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation
|
||||
}
|
||||
```
|
||||
|
||||
In the unbonding queue - the fraction of all historical slashings on
|
||||
that validator are recorded (`StartSlashRatio`). When this queue reaches maturity
|
||||
if that total slashing applied is greater on the validator then the
|
||||
difference (amount that should have been slashed from the first validator) is
|
||||
assigned to the amount being paid out.
|
||||
|
||||
|
||||
### Re-Delegation
|
||||
|
||||
The re-delegation command allows delegators to switch validators while still
|
||||
receiving equal reward to as if you had never unbonded.
|
||||
|
||||
``` golang
|
||||
type TxRedelegate struct {
|
||||
PubKeyFrom crypto.PubKey
|
||||
PubKeyTo crypto.PubKey
|
||||
Shares rational.Rat
|
||||
}
|
||||
```
|
||||
|
||||
When re-delegation is initiated, delegator shares remain accounted for within
|
||||
the `Candidate.Shares`, the term `RedelegatingShares` is incremented and a
|
||||
queue element is created.
|
||||
|
||||
``` golang
|
||||
type QueueElemReDelegate struct {
|
||||
QueueElem
|
||||
Payout Address // account to pay out to
|
||||
Shares rational.Rat // amount of shares which are unbonding
|
||||
NewCandidate crypto.PubKey // validator to bond to after unbond
|
||||
}
|
||||
```
|
||||
|
||||
During the unbonding period all unbonding shares do not count towards the
|
||||
voting power of a validator. Once the `QueueElemReDelegation` has reached
|
||||
maturity, the appropriate unbonding shares are removed from the `Shares` and
|
||||
`RedelegatingShares` term.
|
||||
|
||||
Note that with the current menchanism a delegator cannot redelegate funds which
|
||||
are currently redelegating.
|
||||
|
||||
### Cancel Unbonding
|
||||
|
||||
A delegator who is in the process of unbonding from a validator may use the
|
||||
re-delegate transaction to bond back to the original validator they're
|
||||
currently unbonding from (and only that validator). If initiated, the delegator
|
||||
will immediately begin to one again collect rewards from their validator.
|
||||
|
||||
|
||||
## Provision Calculations
|
||||
|
||||
Every hour atom provisions are assigned proportionally to the each slashable
|
||||
bonded token which includes re-delegating atoms but not unbonding tokens.
|
||||
|
||||
Validation provisions are payed directly to a global hold account
|
||||
(`BondedTokenPool`) and proportions of that hold account owned by each
|
||||
validator is defined as the `GlobalStakeBonded`. The tokens are payed as bonded
|
||||
tokens.
|
||||
|
||||
Here, the bonded tokens that a candidate has can be calculated as:
|
||||
|
||||
```
|
||||
globalStakeExRate = params.BondedTokenPool / params.IssuedGlobalStakeShares
|
||||
candidateCoins = candidate.GlobalStakeShares * globalStakeExRate
|
||||
```
|
||||
|
||||
If a delegator chooses to add more tokens to a validator then the amount of
|
||||
validator shares distributed is calculated on exchange rate (aka every
|
||||
delegators shares do not change value at that moment. The validator's
|
||||
accounting of distributed shares to delegators must also increased at every
|
||||
deposit.
|
||||
|
||||
```
|
||||
delegatorExRate = validatorCoins / candidate.IssuedDelegatorShares
|
||||
createShares = coinsDeposited / delegatorExRate
|
||||
candidate.IssuedDelegatorShares += createShares
|
||||
```
|
||||
|
||||
Whenever a validator has new tokens added to it, the `BondedTokenPool` is
|
||||
increased and must be reflected in the global parameter as well as the
|
||||
validators `GlobalStakeShares`. This calculation ensures that the worth of the
|
||||
`GlobalStakeShares` of other validators remains worth a constant absolute
|
||||
amount of the `BondedTokenPool`
|
||||
|
||||
```
|
||||
createdGlobalStakeShares = coinsDeposited / globalStakeExRate
|
||||
validator.GlobalStakeShares += createdGlobalStakeShares
|
||||
params.IssuedGlobalStakeShares += createdGlobalStakeShares
|
||||
|
||||
params.BondedTokenPool += coinsDeposited
|
||||
```
|
||||
|
||||
Similarly, if a delegator wanted to unbond coins:
|
||||
|
||||
```
|
||||
coinsWithdrawn = withdrawlShares * delegatorExRate
|
||||
|
||||
destroyedGlobalStakeShares = coinsWithdrawn / globalStakeExRate
|
||||
validator.GlobalStakeShares -= destroyedGlobalStakeShares
|
||||
params.IssuedGlobalStakeShares -= destroyedGlobalStakeShares
|
||||
params.BondedTokenPool -= coinsWithdrawn
|
||||
```
|
||||
|
||||
Note that when an re-delegation occurs the shares to move are placed in an
|
||||
re-delegation queue where they continue to collect validator provisions until
|
||||
queue element matures. Although provisions are collected during re-delegation,
|
||||
re-delegation tokens do not contribute to the voting power of a validator.
|
||||
|
||||
Validator provisions are minted on an hourly basis (the first block of a new
|
||||
hour). The annual target of between 7% and 20%. The long-term target ratio of
|
||||
bonded tokens to unbonded tokens is 67%.
|
||||
|
||||
The target annual inflation rate is recalculated for each previsions cycle. The
|
||||
inflation is also subject to a rate change (positive of negative) depending or
|
||||
the distance from the desired ratio (67%). The maximum rate change possible is
|
||||
defined to be 13% per year, however the annual inflation is capped as between
|
||||
7% and 20%.
|
||||
|
||||
```
|
||||
inflationRateChange(0) = 0
|
||||
annualInflation(0) = 0.07
|
||||
|
||||
bondedRatio = bondedTokenPool / totalTokenSupply
|
||||
AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13
|
||||
|
||||
annualInflation += AnnualInflationRateChange
|
||||
|
||||
if annualInflation > 0.20 then annualInflation = 0.20
|
||||
if annualInflation < 0.07 then annualInflation = 0.07
|
||||
|
||||
provisionTokensHourly = totalTokenSupply * annualInflation / (365.25*24)
|
||||
```
|
||||
|
||||
Because the validators hold a relative bonded share (`GlobalStakeShare`), when
|
||||
more bonded tokens are added proportionally to all validators the only term
|
||||
which needs to be updated is the `BondedTokenPool`. So for each previsions
|
||||
cycle:
|
||||
|
||||
```
|
||||
params.BondedTokenPool += provisionTokensHourly
|
||||
```
|
||||
|
||||
## Fee Calculations
|
||||
|
||||
Collected fees are pooled globally and divided out passively to validators and
|
||||
delegators. Each validator has the opportunity to charge commission to the
|
||||
delegators on the fees collected on behalf of the delegators by the validators.
|
||||
Fees are paid directly into a global fee pool. Due to the nature of of passive
|
||||
accounting whenever changes to parameters which affect the rate of fee
|
||||
distribution occurs, withdrawal of fees must also occur.
|
||||
|
||||
- when withdrawing one must withdrawal the maximum amount they are entitled
|
||||
too, leaving nothing in the pool,
|
||||
- when bonding, unbonding, or re-delegating tokens to an existing account a
|
||||
full withdrawal of the fees must occur (as the rules for lazy accounting
|
||||
change),
|
||||
- when a candidate chooses to change the commission on fees, all accumulated
|
||||
commission fees must be simultaneously withdrawn.
|
||||
|
||||
When the validator is the proposer of the round, that validator (and their
|
||||
delegators) receives between 1% and 5% of fee rewards, the reserve tax is then
|
||||
charged, then the remainder is distributed socially by voting power to all
|
||||
validators including the proposer validator. The amount of proposer reward is
|
||||
calculated from pre-commits Tendermint messages. All provision rewards are
|
||||
added to a provision reward pool which validator holds individually. Here note
|
||||
that `BondedShares` represents the sum of all voting power saved in the
|
||||
`GlobalState` (denoted `gs`).
|
||||
|
||||
```
|
||||
proposerReward = feesCollected * (0.01 + 0.04
|
||||
* sumOfVotingPowerOfPrecommitValidators / gs.BondedShares)
|
||||
candidate.ProposerRewardPool += proposerReward
|
||||
|
||||
reserveTaxed = feesCollected * params.ReserveTax
|
||||
gs.ReservePool += reserveTaxed
|
||||
|
||||
distributedReward = feesCollected - proposerReward - reserveTaxed
|
||||
gs.FeePool += distributedReward
|
||||
gs.SumFeesReceived += distributedReward
|
||||
gs.RecentFee = distributedReward
|
||||
```
|
||||
|
||||
The entitlement to the fee pool held by the each validator can be accounted for
|
||||
lazily. First we must account for a candidate's `count` and `adjustment`. The
|
||||
`count` represents a lazy accounting of what that candidates entitlement to the
|
||||
fee pool would be if there `VotingPower` was to never change and they were to
|
||||
never withdraw fees.
|
||||
|
||||
```
|
||||
candidate.count = candidate.VotingPower * BlockHeight
|
||||
```
|
||||
|
||||
Similarly the GlobalState count can be passively calculated whenever needed,
|
||||
where `BondedShares` is the updated sum of voting powers from all validators.
|
||||
|
||||
```
|
||||
gs.count = gs.BondedShares * BlockHeight
|
||||
```
|
||||
|
||||
The `adjustment` term accounts for changes in voting power and withdrawals of
|
||||
fees. The adjustment factor must be persisted with the candidate and modified
|
||||
whenever fees are withdrawn from the candidate or the voting power of the
|
||||
candidate changes. When the voting power of the candidate changes the
|
||||
`Adjustment` factor is increased/decreased by the cumulative difference in the
|
||||
voting power if the voting power has been the new voting power as opposed to
|
||||
the old voting power for the entire duration of the blockchain up the previous
|
||||
block. Each time there is an adjustment change the GlobalState (denoted `gs`)
|
||||
`Adjustment` must also be updated.
|
||||
|
||||
```
|
||||
simplePool = candidate.count / gs.count * gs.SumFeesReceived
|
||||
projectedPool = candidate.PrevPower * (height-1)
|
||||
/ (gs.PrevPower * (height-1)) * gs.PrevFeesReceived
|
||||
+ candidate.Power / gs.Power * gs.RecentFee
|
||||
|
||||
AdjustmentChange = simplePool - projectedPool
|
||||
candidate.AdjustmentRewardPool += AdjustmentChange
|
||||
gs.Adjustment += AdjustmentChange
|
||||
```
|
||||
|
||||
Every instance that the voting power changes, information about the state of
|
||||
the validator set during the change must be recorded as a `powerChange` for
|
||||
other validators to run through. Before any validator modifies its voting power
|
||||
it must first run through the above calculation to determine the change in
|
||||
their `caandidate.AdjustmentRewardPool` for all historical changes in the set
|
||||
of `powerChange` which they have not yet synced to. The set of all
|
||||
`powerChange` may be trimmed from its oldest members once all validators have
|
||||
synced past the height of the oldest `powerChange`. This trim procedure will
|
||||
occur on an epoch basis.
|
||||
|
||||
```golang
|
||||
type powerChange struct {
|
||||
height int64 // block height at change
|
||||
power rational.Rat // total power at change
|
||||
prevpower rational.Rat // total power at previous height-1
|
||||
feesin coins.Coin // fees in at block height
|
||||
prevFeePool coins.Coin // total fees in at previous block height
|
||||
}
|
||||
```
|
||||
|
||||
Note that the adjustment factor may result as negative if the voting power of a
|
||||
different candidate has decreased.
|
||||
|
||||
```
|
||||
candidate.AdjustmentRewardPool += withdrawn
|
||||
gs.Adjustment += withdrawn
|
||||
```
|
||||
|
||||
Now the entitled fee pool of each candidate can be lazily accounted for at
|
||||
any given block:
|
||||
|
||||
```
|
||||
candidate.feePool = candidate.simplePool - candidate.Adjustment
|
||||
```
|
||||
|
||||
So far we have covered two sources fees which can be withdrawn from: Fees from
|
||||
proposer rewards (`candidate.ProposerRewardPool`), and fees from the fee pool
|
||||
(`candidate.feePool`). However we should note that all fees from fee pool are
|
||||
subject to commission rate from the owner of the candidate. These next
|
||||
calculations outline the math behind withdrawing fee rewards as either a
|
||||
delegator to a candidate providing commission, or as the owner of a candidate
|
||||
who is receiving commission.
|
||||
|
||||
### Calculations For Delegators and Candidates
|
||||
|
||||
The same mechanism described to calculate the fees which an entire validator is
|
||||
entitled to is be applied to delegator level to determine the entitled fees for
|
||||
each delegator and the candidates entitled commission from `gs.FeesPool` and
|
||||
`candidate.ProposerRewardPool`.
|
||||
|
||||
The calculations are identical with a few modifications to the parameters:
|
||||
- Delegator's entitlement to `gs.FeePool`:
|
||||
- entitled party voting power should be taken as the effective voting power
|
||||
after commission is retrieved,
|
||||
`bond.Shares/candidate.TotalDelegatorShares * candidate.VotingPower * (1 - candidate.Commission)`
|
||||
- Delegator's entitlement to `candidate.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`candidate.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the effective shares after
|
||||
commission is retrieved, `bond.Shares * (1 - candidate.Commission)`
|
||||
- Candidate's commission entitlement to `gs.FeePool`
|
||||
- entitled party voting power should be taken as the effective voting power
|
||||
of commission portion of total voting power,
|
||||
`candidate.VotingPower * candidate.Commission`
|
||||
- Candidate's commission entitlement to `candidate.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`candidate.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the of commission portion
|
||||
of total delegators shares,
|
||||
`candidate.TotalDelegatorShares * candidate.Commission`
|
||||
|
||||
For more implementation ideas see spreadsheet `spec/AbsoluteFeeDistrModel.xlsx`
|
||||
|
||||
As mentioned earlier, every time the voting power of a delegator bond is
|
||||
changing either by unbonding or further bonding, all fees must be
|
||||
simultaneously withdrawn. Similarly if the validator changes the commission
|
||||
rate, all commission on fees must be simultaneously withdrawn.
|
||||
|
||||
### Other general notes on fees accounting
|
||||
|
||||
- When a delegator chooses to re-delegate shares, fees continue to accumulate
|
||||
until the re-delegation queue reaches maturity. At the block which the queue
|
||||
reaches maturity and shares are re-delegated all available fees are
|
||||
simultaneously withdrawn.
|
||||
- Whenever a totally new validator is added to the validator set, the `accum`
|
||||
of the entire candidate must be 0, meaning that the initial value for
|
||||
`candidate.Adjustment` must be set to the value of `canidate.Count` for the
|
||||
height which the candidate is added on the validator set.
|
||||
- The feePool of a new delegator bond will be 0 for the height at which the bond
|
||||
was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to
|
||||
the height which the bond was added.
|
||||
@@ -1,698 +0,0 @@
|
||||
# Stake Module
|
||||
|
||||
## Overview
|
||||
|
||||
The stake module is tasked with various core staking functionality,
|
||||
including validator set rotation, unbonding periods, and the
|
||||
distribution of inflationary provisions and transaction fees.
|
||||
It is designed to efficiently facilitate small numbers of
|
||||
validators (hundreds), and large numbers of delegators (tens of thousands).
|
||||
|
||||
Bonded Atoms are pooled globally and for each validator.
|
||||
Validators have shares in the global pool, and delegators
|
||||
have shares in the pool of every validator they delegate to.
|
||||
Atom provisions simply accumulate in the global pool, making
|
||||
each share worth proportionally more.
|
||||
|
||||
Validator shares can be redeemed for Atoms, but the Atoms will be locked in a queue
|
||||
for an unbonding period before they can be withdrawn to an account.
|
||||
Delegators can exchange one validator's shares for another immediately
|
||||
(ie. they can re-delegate to another validator), but must then wait the
|
||||
unbonding period before they can do it again.
|
||||
|
||||
Fees are pooled separately and withdrawn lazily, at any time.
|
||||
They are not bonded, and can be paid in multiple tokens.
|
||||
An adjustment factor is maintained for each validator
|
||||
and delegator to determine the true proportion of fees in the pool they are entitled too.
|
||||
Adjustment factors are updated every time a validator or delegator's voting power changes.
|
||||
Validators and delegators must withdraw all fees they are entitled too before they can bond or
|
||||
unbond Atoms.
|
||||
|
||||
## State
|
||||
|
||||
The staking module persists the following to the store:
|
||||
- `GlobalState`, describing the global pools
|
||||
- a `Candidate` for each candidate validator, indexed by public key
|
||||
- a `Candidate` for each candidate validator, indexed by shares in the global pool (ie. ordered)
|
||||
- a `DelegatorBond` for each delegation to a candidate by a delegator, indexed by delegator and candidate
|
||||
public keys
|
||||
- a `Queue` of unbonding delegations (TODO)
|
||||
|
||||
### Global State
|
||||
|
||||
``` golang
|
||||
type GlobalState struct {
|
||||
TotalSupply int64 // total supply of atom tokens
|
||||
BondedShares rational.Rat // sum of all shares distributed for the BondedPool
|
||||
UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool
|
||||
BondedPool int64 // reserve of bonded tokens
|
||||
UnbondedPool int64 // reserve of unbonded tokens held with candidates
|
||||
InflationLastTime int64 // timestamp of last processing of inflation
|
||||
Inflation rational.Rat // current annual inflation rate
|
||||
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset
|
||||
FeePool coin.Coins // fee pool for all the fee shares which have already been distributed
|
||||
ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use
|
||||
Adjustment rational.Rat // Adjustment factor for calculating global fee accum
|
||||
}
|
||||
```
|
||||
|
||||
### Candidate
|
||||
|
||||
The `Candidate` struct holds the current state and some historical actions of
|
||||
validators or candidate-validators.
|
||||
|
||||
``` golang
|
||||
type Candidate struct {
|
||||
Status CandidateStatus
|
||||
PubKey crypto.PubKey
|
||||
GovernancePubKey crypto.PubKey
|
||||
Owner Address
|
||||
GlobalStakeShares rational.Rat
|
||||
IssuedDelegatorShares rational.Rat
|
||||
RedelegatingShares rational.Rat
|
||||
VotingPower rational.Rat
|
||||
Commission rational.Rat
|
||||
CommissionMax rational.Rat
|
||||
CommissionChangeRate rational.Rat
|
||||
CommissionChangeToday rational.Rat
|
||||
ProposerRewardPool coin.Coins
|
||||
Adjustment rational.Rat
|
||||
Description Description
|
||||
}
|
||||
|
||||
type CandidateStatus byte
|
||||
const (
|
||||
VyingUnbonded CandidateStatus = 0x00
|
||||
VyingUnbonding CandidateStatus = 0x01
|
||||
Bonded CandidateStatus = 0x02
|
||||
KickUnbonding CandidateStatus = 0x03
|
||||
KickUnbonded CandidateStatus = 0x04
|
||||
)
|
||||
|
||||
type Description struct {
|
||||
Name string
|
||||
DateBonded string
|
||||
Identity string
|
||||
Website string
|
||||
Details string
|
||||
}
|
||||
```
|
||||
|
||||
Candidate parameters are described:
|
||||
- Status: signal that the candidate is either vying for validator status
|
||||
either unbonded or unbonding, an active validator, or a kicked validator
|
||||
either unbonding or unbonded.
|
||||
- PubKey: separated key from the owner of the candidate as is used strictly
|
||||
for participating in consensus.
|
||||
- Owner: Address where coins are bonded from and unbonded to
|
||||
- GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
|
||||
`Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` if
|
||||
`Candidate.Status` is otherwise
|
||||
- IssuedDelegatorShares: Sum of all shares issued to delegators (which
|
||||
includes the candidate's self-bond) which represent each of their stake in
|
||||
the Candidate's `GlobalStakeShares`
|
||||
- RedelegatingShares: The portion of `IssuedDelegatorShares` which are
|
||||
currently re-delegating to a new validator
|
||||
- VotingPower: Proportional to the amount of bonded tokens which the validator
|
||||
has if the validator is within the top 100 validators.
|
||||
- Commission: The commission rate of fees charged to any delegators
|
||||
- CommissionMax: The maximum commission rate which this candidate can charge
|
||||
each day from the date `GlobalState.DateLastCommissionReset`
|
||||
- CommissionChangeRate: The maximum daily increase of the candidate commission
|
||||
- CommissionChangeToday: Counter for the amount of change to commission rate
|
||||
which has occurred today, reset on the first block of each day (UTC time)
|
||||
- ProposerRewardPool: reward pool for extra fees collected when this candidate
|
||||
is the proposer of a block
|
||||
- Adjustment factor used to passively calculate each validators entitled fees
|
||||
from `GlobalState.FeePool`
|
||||
- Description
|
||||
- Name: moniker
|
||||
- DateBonded: date determined which the validator was bonded
|
||||
- Identity: optional field to provide a signature which verifies the
|
||||
validators identity (ex. UPort or Keybase)
|
||||
- Website: optional website link
|
||||
- Details: optional details
|
||||
|
||||
|
||||
Candidates are indexed by their `Candidate.PubKey`.
|
||||
Additionally, we index empty values by the candidates global stake shares concatenated with the public key.
|
||||
|
||||
TODO: be more precise.
|
||||
|
||||
When the set of all validators needs to be determined from the group of all
|
||||
candidates, the top candidates, sorted by GlobalStakeShares can be retrieved
|
||||
from this sorting without the need to retrieve the entire group of candidates.
|
||||
When validators are kicked from the validator set they are removed from this
|
||||
list.
|
||||
|
||||
|
||||
### DelegatorBond
|
||||
|
||||
Atom holders may delegate coins to validators, under this circumstance their
|
||||
funds are held in a `DelegatorBond`. It is owned by one delegator, and is
|
||||
associated with the shares for one validator. The sender of the transaction is
|
||||
considered to be the owner of the bond,
|
||||
|
||||
``` golang
|
||||
type DelegatorBond struct {
|
||||
Candidate crypto.PubKey
|
||||
Shares rational.Rat
|
||||
AdjustmentFeePool coin.Coins
|
||||
AdjustmentRewardPool coin.Coins
|
||||
}
|
||||
```
|
||||
|
||||
Description:
|
||||
- Candidate: pubkey of the validator candidate: bonding too
|
||||
- Shares: the number of shares received from the validator candidate
|
||||
- AdjustmentFeePool: Adjustment factor used to passively calculate each bonds
|
||||
entitled fees from `GlobalState.FeePool`
|
||||
- AdjustmentRewardPool: Adjustment factor used to passively calculate each
|
||||
bonds entitled fees from `Candidate.ProposerRewardPool``
|
||||
|
||||
Each `DelegatorBond` is individually indexed within the store by delegator
|
||||
address and candidate pubkey.
|
||||
|
||||
- key: Delegator and Candidate-Pubkey
|
||||
- value: DelegatorBond
|
||||
|
||||
|
||||
### Unbonding Queue
|
||||
|
||||
|
||||
- main unbonding queue contains both UnbondElem and RedelegateElem
|
||||
- "queue" + <i>
|
||||
- new unbonding queue every time a val leaves the validator set
|
||||
- "queue"+ <candidate.pubkey > + <i>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
The queue is ordered so the next to unbond/re-delegate is at the head. Every
|
||||
tick the head of the queue is checked and if the unbonding period has passed
|
||||
since `InitHeight` commence with final settlement of the unbonding and pop the
|
||||
queue. All queue elements used for unbonding share a common struct:
|
||||
|
||||
``` golang
|
||||
type QueueElem struct {
|
||||
Candidate crypto.PubKey
|
||||
InitHeight int64 // when the queue was initiated
|
||||
}
|
||||
```
|
||||
|
||||
``` golang
|
||||
type QueueElemUnbondCandidate struct {
|
||||
QueueElem
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
``` golang
|
||||
type QueueElemUnbondDelegation struct {
|
||||
QueueElem
|
||||
Payout Address // account to pay out to
|
||||
Shares rational.Rat // amount of shares which are unbonding
|
||||
StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
``` golang
|
||||
type QueueElemReDelegate struct {
|
||||
QueueElem
|
||||
Payout Address // account to pay out to
|
||||
Shares rational.Rat // amount of shares which are unbonding
|
||||
NewCandidate crypto.PubKey // validator to bond to after unbond
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Each `QueueElem` is persisted in the store until it is popped from the queue.
|
||||
|
||||
## Transactions
|
||||
|
||||
### TxDeclareCandidacy
|
||||
|
||||
Validator candidacy can be declared using the `TxDeclareCandidacy` transaction.
|
||||
During this transaction a self-delegation transaction is executed to bond
|
||||
tokens which are sent in with the transaction.
|
||||
|
||||
``` golang
|
||||
type TxDeclareCandidacy struct {
|
||||
PubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission rational.Rat
|
||||
CommissionMax int64
|
||||
CommissionMaxChange int64
|
||||
Description Description
|
||||
}
|
||||
```
|
||||
|
||||
### TxEditCandidacy
|
||||
|
||||
If either the `Description` (excluding `DateBonded` which is constant),
|
||||
`Commission`, or the `GovernancePubKey` need to be updated, the
|
||||
`TxEditCandidacy` transaction should be sent from the owner account:
|
||||
|
||||
``` golang
|
||||
type TxEditCandidacy struct {
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission int64
|
||||
Description Description
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### TxLivelinessCheck
|
||||
|
||||
Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is
|
||||
submitted.
|
||||
|
||||
``` golang
|
||||
type TxLivelinessCheck struct {
|
||||
PubKey crypto.PubKey
|
||||
RewardAccount Addresss
|
||||
}
|
||||
```
|
||||
|
||||
If the `TxLivelinessCheck is successful in kicking a validator, 5% of the
|
||||
liveliness punishment is provided as a reward to `RewardAccount`.
|
||||
|
||||
|
||||
### TxProveLive
|
||||
|
||||
If the validator was kicked for liveliness issues and is able to regain
|
||||
liveliness then all delegators in the temporary unbonding pool which have not
|
||||
transacted to move will be bonded back to the now-live validator and begin to
|
||||
once again collect provisions and rewards. Regaining livliness is demonstrated
|
||||
by sending in a `TxProveLive` transaction:
|
||||
|
||||
``` golang
|
||||
type TxProveLive struct {
|
||||
PubKey crypto.PubKey
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### TxDelegate
|
||||
|
||||
All bonding, whether self-bonding or delegation, is done via
|
||||
`TxDelegate`.
|
||||
|
||||
Delegator bonds are created using the TxDelegate transaction. Within this
|
||||
transaction the validator candidate queried with an amount of coins, whereby
|
||||
given the current exchange rate of candidate's delegator-shares-to-atoms the
|
||||
candidate will return shares which are assigned in `DelegatorBond.Shares`.
|
||||
|
||||
``` golang
|
||||
type TxDelegate struct {
|
||||
PubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
}
|
||||
```
|
||||
|
||||
### TxUnbond
|
||||
|
||||
|
||||
In this context `TxUnbond` is used to
|
||||
unbond either delegation bonds or validator self-bonds.
|
||||
|
||||
Delegator unbonding is defined by the following transaction type:
|
||||
|
||||
``` golang
|
||||
type TxUnbond struct {
|
||||
PubKey crypto.PubKey
|
||||
Shares rational.Rat
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### TxRedelegate
|
||||
|
||||
The re-delegation command allows delegators to switch validators while still
|
||||
receiving equal reward to as if you had never unbonded.
|
||||
|
||||
``` golang
|
||||
type TxRedelegate struct {
|
||||
PubKeyFrom crypto.PubKey
|
||||
PubKeyTo crypto.PubKey
|
||||
Shares rational.Rat
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
A delegator who is in the process of unbonding from a validator may use the
|
||||
re-delegate transaction to bond back to the original validator they're
|
||||
currently unbonding from (and only that validator). If initiated, the delegator
|
||||
will immediately begin to one again collect rewards from their validator.
|
||||
|
||||
### TxWithdraw
|
||||
|
||||
....
|
||||
|
||||
|
||||
## EndBlock
|
||||
|
||||
### Update Validators
|
||||
|
||||
The validator set is updated in the first block of every hour. Validators are
|
||||
taken as the first `GlobalState.MaxValidators` number of candidates with the
|
||||
greatest amount of staked atoms who have not been kicked from the validator
|
||||
set.
|
||||
|
||||
Unbonding of an entire validator-candidate to a temporary liquid account occurs
|
||||
under the scenarios:
|
||||
- not enough stake to be within the validator set
|
||||
- the owner unbonds all of their staked tokens
|
||||
- validator liveliness issues
|
||||
- crosses a self-imposed safety threshold
|
||||
- minimum number of tokens staked by owner
|
||||
- minimum ratio of tokens staked by owner to delegator tokens
|
||||
|
||||
When this occurs delegator's tokens do not unbond to their personal wallets but
|
||||
begin the unbonding process to a pool where they must then transact in order to
|
||||
withdraw to their respective wallets.
|
||||
|
||||
### Unbonding
|
||||
|
||||
When unbonding is initiated, delegator shares are immediately removed from the
|
||||
candidate and added to a queue object.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
#### Liveliness issues
|
||||
|
||||
Liveliness issues are calculated by keeping track of the block precommits in
|
||||
the block header. A queue is persisted which contains the block headers from
|
||||
all recent blocks for the duration of the unbonding period.
|
||||
|
||||
A validator is defined as having livliness issues if they have not been included in more than
|
||||
33% of the blocks over:
|
||||
- The most recent 24 Hours if they have >= 20% of global stake
|
||||
- The most recent week if they have = 0% of global stake
|
||||
- Linear interpolation of the above two scenarios
|
||||
|
||||
|
||||
## Invariants
|
||||
|
||||
-----------------------------
|
||||
|
||||
------------
|
||||
|
||||
|
||||
|
||||
|
||||
If a delegator chooses to initiate an unbond or re-delegation of their shares
|
||||
while a candidate-unbond is commencing, then that unbond/re-delegation is
|
||||
subject to a reduced unbonding period based on how much time those funds have
|
||||
already spent in the unbonding queue.
|
||||
|
||||
### Re-Delegation
|
||||
|
||||
When re-delegation is initiated, delegator shares remain accounted for within
|
||||
the `Candidate.Shares`, the term `RedelegatingShares` is incremented and a
|
||||
queue element is created.
|
||||
|
||||
During the unbonding period all unbonding shares do not count towards the
|
||||
voting power of a validator. Once the `QueueElemReDelegation` has reached
|
||||
maturity, the appropriate unbonding shares are removed from the `Shares` and
|
||||
`RedelegatingShares` term.
|
||||
|
||||
Note that with the current menchanism a delegator cannot redelegate funds which
|
||||
are currently redelegating.
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
## Provision Calculations
|
||||
|
||||
Every hour atom provisions are assigned proportionally to the each slashable
|
||||
bonded token which includes re-delegating atoms but not unbonding tokens.
|
||||
|
||||
Validation provisions are payed directly to a global hold account
|
||||
(`BondedTokenPool`) and proportions of that hold account owned by each
|
||||
validator is defined as the `GlobalStakeBonded`. The tokens are payed as bonded
|
||||
tokens.
|
||||
|
||||
Here, the bonded tokens that a candidate has can be calculated as:
|
||||
|
||||
```
|
||||
globalStakeExRate = params.BondedTokenPool / params.IssuedGlobalStakeShares
|
||||
candidateCoins = candidate.GlobalStakeShares * globalStakeExRate
|
||||
```
|
||||
|
||||
If a delegator chooses to add more tokens to a validator then the amount of
|
||||
validator shares distributed is calculated on exchange rate (aka every
|
||||
delegators shares do not change value at that moment. The validator's
|
||||
accounting of distributed shares to delegators must also increased at every
|
||||
deposit.
|
||||
|
||||
```
|
||||
delegatorExRate = validatorCoins / candidate.IssuedDelegatorShares
|
||||
createShares = coinsDeposited / delegatorExRate
|
||||
candidate.IssuedDelegatorShares += createShares
|
||||
```
|
||||
|
||||
Whenever a validator has new tokens added to it, the `BondedTokenPool` is
|
||||
increased and must be reflected in the global parameter as well as the
|
||||
validators `GlobalStakeShares`. This calculation ensures that the worth of the
|
||||
`GlobalStakeShares` of other validators remains worth a constant absolute
|
||||
amount of the `BondedTokenPool`
|
||||
|
||||
```
|
||||
createdGlobalStakeShares = coinsDeposited / globalStakeExRate
|
||||
validator.GlobalStakeShares += createdGlobalStakeShares
|
||||
params.IssuedGlobalStakeShares += createdGlobalStakeShares
|
||||
|
||||
params.BondedTokenPool += coinsDeposited
|
||||
```
|
||||
|
||||
Similarly, if a delegator wanted to unbond coins:
|
||||
|
||||
```
|
||||
coinsWithdrawn = withdrawlShares * delegatorExRate
|
||||
|
||||
destroyedGlobalStakeShares = coinsWithdrawn / globalStakeExRate
|
||||
validator.GlobalStakeShares -= destroyedGlobalStakeShares
|
||||
params.IssuedGlobalStakeShares -= destroyedGlobalStakeShares
|
||||
params.BondedTokenPool -= coinsWithdrawn
|
||||
```
|
||||
|
||||
Note that when an re-delegation occurs the shares to move are placed in an
|
||||
re-delegation queue where they continue to collect validator provisions until
|
||||
queue element matures. Although provisions are collected during re-delegation,
|
||||
re-delegation tokens do not contribute to the voting power of a validator.
|
||||
|
||||
Validator provisions are minted on an hourly basis (the first block of a new
|
||||
hour). The annual target of between 7% and 20%. The long-term target ratio of
|
||||
bonded tokens to unbonded tokens is 67%.
|
||||
|
||||
The target annual inflation rate is recalculated for each previsions cycle. The
|
||||
inflation is also subject to a rate change (positive of negative) depending or
|
||||
the distance from the desired ratio (67%). The maximum rate change possible is
|
||||
defined to be 13% per year, however the annual inflation is capped as between
|
||||
7% and 20%.
|
||||
|
||||
```
|
||||
inflationRateChange(0) = 0
|
||||
annualInflation(0) = 0.07
|
||||
|
||||
bondedRatio = bondedTokenPool / totalTokenSupply
|
||||
AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13
|
||||
|
||||
annualInflation += AnnualInflationRateChange
|
||||
|
||||
if annualInflation > 0.20 then annualInflation = 0.20
|
||||
if annualInflation < 0.07 then annualInflation = 0.07
|
||||
|
||||
provisionTokensHourly = totalTokenSupply * annualInflation / (365.25*24)
|
||||
```
|
||||
|
||||
Because the validators hold a relative bonded share (`GlobalStakeShare`), when
|
||||
more bonded tokens are added proportionally to all validators the only term
|
||||
which needs to be updated is the `BondedTokenPool`. So for each previsions
|
||||
cycle:
|
||||
|
||||
```
|
||||
params.BondedTokenPool += provisionTokensHourly
|
||||
```
|
||||
|
||||
## Fee Calculations
|
||||
|
||||
Collected fees are pooled globally and divided out passively to validators and
|
||||
delegators. Each validator has the opportunity to charge commission to the
|
||||
delegators on the fees collected on behalf of the delegators by the validators.
|
||||
Fees are paid directly into a global fee pool. Due to the nature of of passive
|
||||
accounting whenever changes to parameters which affect the rate of fee
|
||||
distribution occurs, withdrawal of fees must also occur.
|
||||
|
||||
- when withdrawing one must withdrawal the maximum amount they are entitled
|
||||
too, leaving nothing in the pool,
|
||||
- when bonding, unbonding, or re-delegating tokens to an existing account a
|
||||
full withdrawal of the fees must occur (as the rules for lazy accounting
|
||||
change),
|
||||
- when a candidate chooses to change the commission on fees, all accumulated
|
||||
commission fees must be simultaneously withdrawn.
|
||||
|
||||
When the validator is the proposer of the round, that validator (and their
|
||||
delegators) receives between 1% and 5% of fee rewards, the reserve tax is then
|
||||
charged, then the remainder is distributed socially by voting power to all
|
||||
validators including the proposer validator. The amount of proposer reward is
|
||||
calculated from pre-commits Tendermint messages. All provision rewards are
|
||||
added to a provision reward pool which validator holds individually. Here note
|
||||
that `BondedShares` represents the sum of all voting power saved in the
|
||||
`GlobalState` (denoted `gs`).
|
||||
|
||||
```
|
||||
proposerReward = feesCollected * (0.01 + 0.04
|
||||
* sumOfVotingPowerOfPrecommitValidators / gs.BondedShares)
|
||||
candidate.ProposerRewardPool += proposerReward
|
||||
|
||||
reserveTaxed = feesCollected * params.ReserveTax
|
||||
gs.ReservePool += reserveTaxed
|
||||
|
||||
distributedReward = feesCollected - proposerReward - reserveTaxed
|
||||
gs.FeePool += distributedReward
|
||||
gs.SumFeesReceived += distributedReward
|
||||
gs.RecentFee = distributedReward
|
||||
```
|
||||
|
||||
The entitlement to the fee pool held by the each validator can be accounted for
|
||||
lazily. First we must account for a candidate's `count` and `adjustment`. The
|
||||
`count` represents a lazy accounting of what that candidates entitlement to the
|
||||
fee pool would be if there `VotingPower` was to never change and they were to
|
||||
never withdraw fees.
|
||||
|
||||
```
|
||||
candidate.count = candidate.VotingPower * BlockHeight
|
||||
```
|
||||
|
||||
Similarly the GlobalState count can be passively calculated whenever needed,
|
||||
where `BondedShares` is the updated sum of voting powers from all validators.
|
||||
|
||||
```
|
||||
gs.count = gs.BondedShares * BlockHeight
|
||||
```
|
||||
|
||||
The `adjustment` term accounts for changes in voting power and withdrawals of
|
||||
fees. The adjustment factor must be persisted with the candidate and modified
|
||||
whenever fees are withdrawn from the candidate or the voting power of the
|
||||
candidate changes. When the voting power of the candidate changes the
|
||||
`Adjustment` factor is increased/decreased by the cumulative difference in the
|
||||
voting power if the voting power has been the new voting power as opposed to
|
||||
the old voting power for the entire duration of the blockchain up the previous
|
||||
block. Each time there is an adjustment change the GlobalState (denoted `gs`)
|
||||
`Adjustment` must also be updated.
|
||||
|
||||
```
|
||||
simplePool = candidate.count / gs.count * gs.SumFeesReceived
|
||||
projectedPool = candidate.PrevPower * (height-1)
|
||||
/ (gs.PrevPower * (height-1)) * gs.PrevFeesReceived
|
||||
+ candidate.Power / gs.Power * gs.RecentFee
|
||||
|
||||
AdjustmentChange = simplePool - projectedPool
|
||||
candidate.AdjustmentRewardPool += AdjustmentChange
|
||||
gs.Adjustment += AdjustmentChange
|
||||
```
|
||||
|
||||
Every instance that the voting power changes, information about the state of
|
||||
the validator set during the change must be recorded as a `powerChange` for
|
||||
other validators to run through. Before any validator modifies its voting power
|
||||
it must first run through the above calculation to determine the change in
|
||||
their `caandidate.AdjustmentRewardPool` for all historical changes in the set
|
||||
of `powerChange` which they have not yet synced to. The set of all
|
||||
`powerChange` may be trimmed from its oldest members once all validators have
|
||||
synced past the height of the oldest `powerChange`. This trim procedure will
|
||||
occur on an epoch basis.
|
||||
|
||||
```golang
|
||||
type powerChange struct {
|
||||
height int64 // block height at change
|
||||
power rational.Rat // total power at change
|
||||
prevpower rational.Rat // total power at previous height-1
|
||||
feesin coins.Coin // fees in at block height
|
||||
prevFeePool coins.Coin // total fees in at previous block height
|
||||
}
|
||||
```
|
||||
|
||||
Note that the adjustment factor may result as negative if the voting power of a
|
||||
different candidate has decreased.
|
||||
|
||||
```
|
||||
candidate.AdjustmentRewardPool += withdrawn
|
||||
gs.Adjustment += withdrawn
|
||||
```
|
||||
|
||||
Now the entitled fee pool of each candidate can be lazily accounted for at
|
||||
any given block:
|
||||
|
||||
```
|
||||
candidate.feePool = candidate.simplePool - candidate.Adjustment
|
||||
```
|
||||
|
||||
So far we have covered two sources fees which can be withdrawn from: Fees from
|
||||
proposer rewards (`candidate.ProposerRewardPool`), and fees from the fee pool
|
||||
(`candidate.feePool`). However we should note that all fees from fee pool are
|
||||
subject to commission rate from the owner of the candidate. These next
|
||||
calculations outline the math behind withdrawing fee rewards as either a
|
||||
delegator to a candidate providing commission, or as the owner of a candidate
|
||||
who is receiving commission.
|
||||
|
||||
### Calculations For Delegators and Candidates
|
||||
|
||||
The same mechanism described to calculate the fees which an entire validator is
|
||||
entitled to is be applied to delegator level to determine the entitled fees for
|
||||
each delegator and the candidates entitled commission from `gs.FeesPool` and
|
||||
`candidate.ProposerRewardPool`.
|
||||
|
||||
The calculations are identical with a few modifications to the parameters:
|
||||
- Delegator's entitlement to `gs.FeePool`:
|
||||
- entitled party voting power should be taken as the effective voting power
|
||||
after commission is retrieved,
|
||||
`bond.Shares/candidate.TotalDelegatorShares * candidate.VotingPower * (1 - candidate.Commission)`
|
||||
- Delegator's entitlement to `candidate.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`candidate.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the effective shares after
|
||||
commission is retrieved, `bond.Shares * (1 - candidate.Commission)`
|
||||
- Candidate's commission entitlement to `gs.FeePool`
|
||||
- entitled party voting power should be taken as the effective voting power
|
||||
of commission portion of total voting power,
|
||||
`candidate.VotingPower * candidate.Commission`
|
||||
- Candidate's commission entitlement to `candidate.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`candidate.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the of commission portion
|
||||
of total delegators shares,
|
||||
`candidate.TotalDelegatorShares * candidate.Commission`
|
||||
|
||||
For more implementation ideas see spreadsheet `spec/AbsoluteFeeDistrModel.xlsx`
|
||||
|
||||
As mentioned earlier, every time the voting power of a delegator bond is
|
||||
changing either by unbonding or further bonding, all fees must be
|
||||
simultaneously withdrawn. Similarly if the validator changes the commission
|
||||
rate, all commission on fees must be simultaneously withdrawn.
|
||||
|
||||
### Other general notes on fees accounting
|
||||
|
||||
- When a delegator chooses to re-delegate shares, fees continue to accumulate
|
||||
until the re-delegation queue reaches maturity. At the block which the queue
|
||||
reaches maturity and shares are re-delegated all available fees are
|
||||
simultaneously withdrawn.
|
||||
- Whenever a totally new validator is added to the validator set, the `accum`
|
||||
of the entire candidate must be 0, meaning that the initial value for
|
||||
`candidate.Adjustment` must be set to the value of `canidate.Count` for the
|
||||
height which the candidate is added on the validator set.
|
||||
- The feePool of a new delegator bond will be 0 for the height at which the bond
|
||||
was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to
|
||||
the height which the bond was added.
|
||||
@@ -1,214 +0,0 @@
|
||||
# Staking Module
|
||||
|
||||
## Overview
|
||||
|
||||
The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that
|
||||
serves as a backbone of the Cosmos ecosystem. It is operated and secured by an
|
||||
open and globally decentralized set of validators. Tendermint consensus is a
|
||||
Byzantine fault-tolerant distributed protocol that involves all validators in
|
||||
the process of exchanging protocol messages in the production of each block. To
|
||||
avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up
|
||||
coins in a bond deposit. Tendermint protocol messages are signed by the
|
||||
validator's private key, and this is a basis for Tendermint strict
|
||||
accountability that allows punishing misbehaving validators by slashing
|
||||
(burning) their bonded Atoms. On the other hand, validators are rewarded for
|
||||
their service of securing blockchain network by the inflationary provisions and
|
||||
transactions fees. This incentives correct behavior of the validators and
|
||||
provides the economic security of the network.
|
||||
|
||||
The native token of the Cosmos Hub is called Atom; becoming a validator of the
|
||||
Cosmos Hub requires holding Atoms. However, not all Atom holders are validators
|
||||
of the Cosmos Hub. More precisely, there is a selection process that determines
|
||||
the validator set as a subset of all validator candidates (Atom holders that
|
||||
wants to become a validator). The other option for Atom holder is to delegate
|
||||
their atoms to validators, i.e., being a delegator. A delegator is an Atom
|
||||
holder that has bonded its Atoms by delegating it to a validator (or validator
|
||||
candidate). By bonding Atoms to secure the network (and taking a risk of being
|
||||
slashed in case of misbehaviour), a user is rewarded with inflationary
|
||||
provisions and transaction fees proportional to the amount of its bonded Atoms.
|
||||
The Cosmos Hub is designed to efficiently facilitate a small numbers of
|
||||
validators (hundreds), and large numbers of delegators (tens of thousands).
|
||||
More precisely, it is the role of the Staking module of the Cosmos Hub to
|
||||
support various staking functionality including validator set selection,
|
||||
delegating, bonding and withdrawing Atoms, and the distribution of inflationary
|
||||
provisions and transaction fees.
|
||||
|
||||
## Basic Terms and Definitions
|
||||
|
||||
* Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system
|
||||
* Atom - native token of the Cosmsos Hub
|
||||
* Atom holder - an entity that holds some amount of Atoms
|
||||
* Candidate - an Atom holder that is actively involved in the Tendermint
|
||||
blockchain protocol (running Tendermint Full Node (TODO: add link to Full
|
||||
Node definition) and is competing with other candidates to be elected as a
|
||||
validator (TODO: add link to Validator definition))
|
||||
* Validator - a candidate that is currently selected among a set of candidates
|
||||
to be able to sign protocol messages in the Tendermint consensus protocol
|
||||
* Delegator - an Atom holder that has bonded some of its Atoms by delegating
|
||||
them to a validator (or a candidate)
|
||||
* Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms
|
||||
under protocol control). Atoms are always bonded through a validator (or
|
||||
candidate) process. Bonded atoms can be slashed (burned) in case a validator
|
||||
process misbehaves (does not behave according to the protocol specification).
|
||||
Atom holders can regain access to their bonded Atoms if they have not been
|
||||
slashed by waiting an Unbonding period.
|
||||
* Unbonding period - a period of time after which Atom holder gains access to
|
||||
its bonded Atoms (they can be withdrawn to a user account) or they can be
|
||||
re-delegated.
|
||||
* Inflationary provisions - inflation is the process of increasing the Atom supply.
|
||||
Atoms are periodically created on the Cosmos Hub and issued to bonded Atom holders.
|
||||
The goal of inflation is to incentize most of the Atoms in existence to be bonded.
|
||||
* Transaction fees - transaction fee is a fee that is included in a Cosmsos Hub
|
||||
transaction. The fees are collected by the current validator set and
|
||||
distributed among validators and delegators in proportion to their bonded
|
||||
Atom share.
|
||||
* Commission fee - a fee taken from the transaction fees by a validator for
|
||||
their service
|
||||
|
||||
## The pool and the share
|
||||
|
||||
At the core of the Staking module is the concept of a pool which denotes a
|
||||
collection of Atoms contributed by different Atom holders. There are two global
|
||||
pools in the Staking module: the bonded pool and unbonding pool. Bonded Atoms
|
||||
are part of the global bonded pool. If a candidate or delegator wants to unbond
|
||||
its Atoms, those Atoms are moved to the the unbonding pool for the duration of
|
||||
the unbonding period. In the Staking module, a pool is a logical concept, i.e.,
|
||||
there is no pool data structure that would be responsible for managing pool
|
||||
resources. Instead, it is managed in a distributed way. More precisely, at the
|
||||
global level, for each pool, we track only the total amount of bonded or unbonded
|
||||
Atoms and the current amount of issued shares. A share is a unit of Atom distribution
|
||||
and the value of the share (share-to-atom exchange rate) changes during
|
||||
system execution. The share-to-atom exchange rate can be computed as:
|
||||
|
||||
`share-to-atom-exchange-rate = size of the pool / ammount of issued shares`
|
||||
|
||||
Then for each validator candidate (in a per candidate data structure) we keep track of
|
||||
the amount of shares the candidate owns in a pool. At any point in time,
|
||||
the exact amount of Atoms a candidate has in the pool can be computed as the
|
||||
number of shares it owns multiplied with the current share-to-atom exchange rate:
|
||||
|
||||
`candidate-coins = candidate.Shares * share-to-atom-exchange-rate`
|
||||
|
||||
The benefit of such accounting of the pool resources is the fact that a
|
||||
modification to the pool from bonding/unbonding/slashing/provisioning of
|
||||
Atoms affects only global data (size of the pool and the number of shares) and
|
||||
not the related validator/candidate data structure, i.e., the data structure of
|
||||
other validators do not need to be modified. This has the advantage that
|
||||
modifying global data is much cheaper computationally than modifying data of
|
||||
every validator. Let's explain this further with several small examples:
|
||||
|
||||
We consider initially 4 validators p1, p2, p3 and p4, and that each validator
|
||||
has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
|
||||
issued initially 40 shares (note that the initial distribution of the shares,
|
||||
i.e., share-to-atom exchange rate can be set to any meaningful value), i.e.,
|
||||
share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we
|
||||
have, the size of the pool is 40 Atoms, and the amount of issued shares is
|
||||
equal to 40. And for each validator we store in their corresponding data
|
||||
structure that each has 10 shares of the bonded pool. Now lets assume that the
|
||||
validator p4 starts process of unbonding of 5 shares. Then the total size of
|
||||
the pool is decreased and now it will be 35 shares and the amount of Atoms is
|
||||
35 . Note that the only change in other data structures needed is reducing the
|
||||
number of shares for a validator p4 from 10 to 5.
|
||||
|
||||
Let's consider now the case where a validator p1 wants to bond 15 more atoms to
|
||||
the pool. Now the size of the pool is 50, and as the exchange rate hasn't
|
||||
changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we
|
||||
now have 50 shares in the pool in total. Validators p2, p3 and p4 still have
|
||||
(correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we
|
||||
don't need to modify anything in their corresponding data structures. But p1
|
||||
now has 25 shares, so we update the amount of shares owned by p1 in its
|
||||
data structure. Note that apart from the size of the pool that is in Atoms, all
|
||||
other data structures refer only to shares.
|
||||
|
||||
Finally, let's consider what happens when new Atoms are created and added to
|
||||
the pool due to inflation. Let's assume that the inflation rate is 10 percent
|
||||
and that it is applied to the current state of the pool. This means that 5
|
||||
Atoms are created and added to the pool and that each validator now
|
||||
proportionally increase it's Atom count. Let's analyse how this change is
|
||||
reflected in the data structures. First, the size of the pool is increased and
|
||||
is now 55 atoms. As a share of each validator in the pool hasn't changed, this
|
||||
means that the total number of shares stay the same (50) and that the amount of
|
||||
shares of each validator stays the same (correspondingly 25, 10, 10, 5). But
|
||||
the exchange rate has changed and each share is now worth 55/50 Atoms per
|
||||
share, so each validator has effectively increased amount of Atoms it has. So
|
||||
validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms.
|
||||
|
||||
The concepts of the pool and its shares is at the core of the accounting in the
|
||||
Staking module. It is used for managing the global pools (such as bonding and
|
||||
unbonding pool), but also for distribution of Atoms between validator and its
|
||||
delegators (we will explain this in section X).
|
||||
|
||||
#### Delegator shares
|
||||
|
||||
A candidate is, depending on it's status, contributing Atoms to either the
|
||||
bonded or unbonding pool, and in return gets some amount of (global) pool
|
||||
shares. Note that not all those Atoms (and respective shares) are owned by the
|
||||
candidate as some Atoms could be delegated to a candidate. The mechanism for
|
||||
distribution of Atoms (and shares) between a candidate and it's delegators is
|
||||
based on a notion of delegator shares. More precisely, every candidate is
|
||||
issuing (local) delegator shares (`Candidate.IssuedDelegatorShares`) that
|
||||
represents some portion of global shares managed by the candidate
|
||||
(`Candidate.GlobalStakeShares`). The principle behind managing delegator shares
|
||||
is the same as described in [Section](#The pool and the share). We now
|
||||
illustrate it with an example.
|
||||
|
||||
Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator
|
||||
has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
|
||||
issued initially 40 global shares, i.e., that
|
||||
`share-to-atom-exchange-rate = 1 atom per share`. So we will set
|
||||
`GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the
|
||||
Candidate data structure of each validator `Candidate.GlobalStakeShares = 10`.
|
||||
Furthermore, each validator issued 10 delegator shares which are initially
|
||||
owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where
|
||||
`delegator-share-to-global-share-ex-rate = 1 global share per delegator share`.
|
||||
Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and
|
||||
consider what are the updates we need to make to the data structures. First,
|
||||
`GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for
|
||||
validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to
|
||||
issue also additional delegator shares, i.e.,
|
||||
`Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator
|
||||
shares of validator p1, where each delegator share is worth 1 global shares,
|
||||
i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due to
|
||||
inflation. In that case, we only need to update `GlobalState.BondedPool` which
|
||||
is now equal to 50 Atoms as created Atoms are added to the bonded pool. Note
|
||||
that the amount of global and delegator shares stay the same but they are now
|
||||
worth more as share-to-atom-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
|
||||
```
|
||||
+161
-164
@@ -1,204 +1,201 @@
|
||||
|
||||
## State
|
||||
|
||||
The staking module persists the following information to the store:
|
||||
* `GlobalState`, a struct describing the global pools, inflation, and
|
||||
fees
|
||||
* `ValidatorCandidates: <pubkey | shares> => <candidate>`, a map of all candidates (including current validators) in the store,
|
||||
indexed by their public key and shares in the global pool.
|
||||
* `DelegatorBonds: < delegator-address | candidate-pubkey > => <delegator-bond>`. a map of all delegations by a delegator to a candidate,
|
||||
indexed by delegator address and candidate pubkey.
|
||||
public key
|
||||
* `UnbondQueue`, the queue of unbonding delegations
|
||||
* `RedelegateQueue`, the queue of re-delegations
|
||||
### Pool
|
||||
|
||||
### Global State
|
||||
- key: `01`
|
||||
- value: `amino(pool)`
|
||||
|
||||
The GlobalState contains information about the total amount of Atoms, the
|
||||
global bonded/unbonded position, the Atom inflation rate, and the fees.
|
||||
The pool is a space for all dynamic global state of the Cosmos Hub. It tracks
|
||||
information about the total amounts of Atoms in all states, representative
|
||||
validator shares for stake in the global pools, moving Atom inflation
|
||||
information, etc.
|
||||
|
||||
`Params` is global data structure that stores system parameters and defines overall functioning of the
|
||||
module.
|
||||
|
||||
``` go
|
||||
type GlobalState struct {
|
||||
TotalSupply int64 // total supply of Atoms
|
||||
BondedPool int64 // reserve of bonded tokens
|
||||
BondedShares rational.Rat // sum of all shares distributed for the BondedPool
|
||||
UnbondedPool int64 // reserve of unbonding tokens held with candidates
|
||||
UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool
|
||||
InflationLastTime int64 // timestamp of last processing of inflation
|
||||
Inflation rational.Rat // current annual inflation rate
|
||||
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset
|
||||
FeePool coin.Coins // fee pool for all the fee shares which have already been distributed
|
||||
ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use
|
||||
Adjustment rational.Rat // Adjustment factor for calculating global fee accum
|
||||
```golang
|
||||
type Pool struct {
|
||||
LooseTokens int64 // tokens not associated with any validator
|
||||
UnbondedTokens int64 // reserve of unbonded tokens held with validators
|
||||
UnbondingTokens int64 // tokens moving from bonded to unbonded pool
|
||||
BondedTokens int64 // reserve of bonded tokens
|
||||
UnbondedShares sdk.Rat // sum of all shares distributed for the Unbonded Pool
|
||||
UnbondingShares sdk.Rat // shares moving from Bonded to Unbonded Pool
|
||||
BondedShares sdk.Rat // sum of all shares distributed for the Bonded Pool
|
||||
InflationLastTime int64 // block which the last inflation was processed // TODO make time
|
||||
Inflation sdk.Rat // current annual inflation rate
|
||||
|
||||
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset (daily)
|
||||
}
|
||||
|
||||
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
|
||||
type PoolShares struct {
|
||||
Status sdk.BondStatus // either: unbonded, unbonding, or bonded
|
||||
Amount sdk.Rat // total shares of type ShareKind
|
||||
}
|
||||
```
|
||||
|
||||
### Candidate
|
||||
### Params
|
||||
- key: `00`
|
||||
- value: `amino(params)`
|
||||
|
||||
The `Candidate` holds the current state and some historical
|
||||
actions of validators or candidate-validators.
|
||||
Params is global data structure that stores system parameters and defines
|
||||
overall functioning of the stake module.
|
||||
|
||||
``` go
|
||||
type CandidateStatus byte
|
||||
```golang
|
||||
type Params struct {
|
||||
InflationRateChange sdk.Rat // maximum annual change in inflation rate
|
||||
InflationMax sdk.Rat // maximum inflation rate
|
||||
InflationMin sdk.Rat // minimum inflation rate
|
||||
GoalBonded sdk.Rat // Goal of percent bonded atoms
|
||||
|
||||
const (
|
||||
Bonded CandidateStatus = 0x01
|
||||
Unbonded CandidateStatus = 0x02
|
||||
Revoked CandidateStatus = 0x03
|
||||
)
|
||||
MaxValidators uint16 // maximum number of validators
|
||||
BondDenom string // bondable coin denomination
|
||||
}
|
||||
```
|
||||
|
||||
type Candidate struct {
|
||||
Status CandidateStatus
|
||||
ConsensusPubKey crypto.PubKey
|
||||
GovernancePubKey crypto.PubKey
|
||||
Owner crypto.Address
|
||||
GlobalStakeShares rational.Rat
|
||||
IssuedDelegatorShares rational.Rat
|
||||
RedelegatingShares rational.Rat
|
||||
VotingPower rational.Rat
|
||||
Commission rational.Rat
|
||||
CommissionMax rational.Rat
|
||||
CommissionChangeRate rational.Rat
|
||||
CommissionChangeToday rational.Rat
|
||||
ProposerRewardPool coin.Coins
|
||||
Adjustment rational.Rat
|
||||
Description Description
|
||||
### Validator
|
||||
|
||||
Validators are identified according to the `ValOwnerAddr`,
|
||||
an SDK account address for the owner of the validator.
|
||||
|
||||
Validators also have a `ValTendermintAddr`, the address
|
||||
of the public key of the validator.
|
||||
|
||||
Validators are indexed in the store using the following maps:
|
||||
|
||||
- Validators: `0x02 | ValOwnerAddr -> amino(validator)`
|
||||
- ValidatorsByPubKey: `0x03 | ValTendermintAddr -> ValOwnerAddr`
|
||||
- ValidatorsByPower: `0x05 | power | blockHeight | blockTx -> ValOwnerAddr`
|
||||
|
||||
`Validators` is the primary index - it ensures that each owner can have only one
|
||||
associated validator, where the public key of that validator can change in the
|
||||
future. Delegators can refer to the immutable owner of the validator, without
|
||||
concern for the changing public key.
|
||||
|
||||
`ValidatorsByPubKey` is a secondary index that enables lookups for slashing.
|
||||
When Tendermint reports evidence, it provides the validator address, so this
|
||||
map is needed to find the owner.
|
||||
|
||||
`ValidatorsByPower` is a secondary index that provides a sorted list of
|
||||
potential validators to quickly determine the current active set. For instance,
|
||||
the first 100 validators in this list can be returned with every EndBlock.
|
||||
|
||||
The `Validator` holds the current state and some historical actions of the
|
||||
validator.
|
||||
|
||||
```golang
|
||||
type Validator struct {
|
||||
ConsensusPubKey crypto.PubKey // Tendermint consensus pubkey of validator
|
||||
Revoked bool // has the validator been revoked?
|
||||
|
||||
PoolShares PoolShares // total shares for tokens held in the pool
|
||||
DelegatorShares sdk.Rat // total shares issued to a validator's delegators
|
||||
SlashRatio sdk.Rat // increases each time the validator is slashed
|
||||
|
||||
Description Description // description terms for the validator
|
||||
|
||||
// Needed for ordering vals in the bypower key
|
||||
BondHeight int64 // earliest height as a bonded validator
|
||||
BondIntraTxCounter int16 // block-local tx index of validator change
|
||||
|
||||
CommissionInfo CommissionInfo // info about the validator's commission
|
||||
|
||||
ProposerRewardPool sdk.Coins // reward pool collected from being the proposer
|
||||
|
||||
// TODO: maybe this belongs in distribution module ?
|
||||
PrevPoolShares PoolShares // total shares of a global hold pools
|
||||
}
|
||||
|
||||
type CommissionInfo struct {
|
||||
Rate sdk.Rat // the commission rate of fees charged to any delegators
|
||||
Max sdk.Rat // maximum commission rate which this validator can ever charge
|
||||
ChangeRate sdk.Rat // maximum daily increase of the validator commission
|
||||
ChangeToday sdk.Rat // commission rate change today, reset each day (UTC time)
|
||||
LastChange int64 // unix timestamp of last commission change
|
||||
}
|
||||
|
||||
type Description struct {
|
||||
Name string
|
||||
DateBonded string
|
||||
Identity string
|
||||
Website string
|
||||
Details string
|
||||
Moniker string // name
|
||||
Identity string // optional identity signature (ex. UPort or Keybase)
|
||||
Website string // optional website link
|
||||
Details string // optional details
|
||||
}
|
||||
```
|
||||
|
||||
Candidate parameters are described:
|
||||
* Status: it can be Bonded (active validator), Unbonding (validator candidate)
|
||||
or Revoked
|
||||
* ConsensusPubKey: candidate public key that is used strictly for participating in
|
||||
consensus
|
||||
* GovernancePubKey: public key used by the validator for governance voting
|
||||
* Owner: Address that is allowed to unbond coins.
|
||||
* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
|
||||
`Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool`
|
||||
otherwise
|
||||
* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators
|
||||
(which includes the candidate's self-bond); a delegator share represents
|
||||
their stake in the Candidate's `GlobalStakeShares`
|
||||
* RedelegatingShares: The portion of `IssuedDelegatorShares` which are
|
||||
currently re-delegating to a new validator
|
||||
* VotingPower: Proportional to the amount of bonded tokens which the validator
|
||||
has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0`
|
||||
* Commission: The commission rate of fees charged to any delegators
|
||||
* CommissionMax: The maximum commission rate this candidate can charge each
|
||||
day from the date `GlobalState.DateLastCommissionReset`
|
||||
* CommissionChangeRate: The maximum daily increase of the candidate commission
|
||||
* CommissionChangeToday: Counter for the amount of change to commission rate
|
||||
which has occurred today, reset on the first block of each day (UTC time)
|
||||
* ProposerRewardPool: reward pool for extra fees collected when this candidate
|
||||
is the proposer of a block
|
||||
* Adjustment factor used to passively calculate each validators entitled fees
|
||||
from `GlobalState.FeePool`
|
||||
* Description
|
||||
* Name: moniker
|
||||
* DateBonded: date determined which the validator was bonded
|
||||
* Identity: optional field to provide a signature which verifies the
|
||||
validators identity (ex. UPort or Keybase)
|
||||
* Website: optional website link
|
||||
* Details: optional details
|
||||
### Delegation
|
||||
|
||||
### DelegatorBond
|
||||
Delegations are identified by combining `DelegatorAddr` (the address of the delegator) with the ValOwnerAddr
|
||||
Delegators are indexed in the store as follows:
|
||||
|
||||
Atom holders may delegate coins to candidates; under this circumstance their
|
||||
funds are held in a `DelegatorBond` data structure. It is owned by one
|
||||
delegator, and is associated with the shares for one candidate. The sender of
|
||||
- Delegation: ` 0x0A | DelegatorAddr | ValOwnerAddr -> amino(delegation)`
|
||||
|
||||
Atom holders may delegate coins to validators; under this circumstance their
|
||||
funds are held in a `Delegation` data structure. It is owned by one
|
||||
delegator, and is associated with the shares for one validator. The sender of
|
||||
the transaction is the owner of the bond.
|
||||
|
||||
``` go
|
||||
type DelegatorBond struct {
|
||||
Candidate crypto.PubKey
|
||||
Shares rational.Rat
|
||||
AdjustmentFeePool coin.Coins
|
||||
AdjustmentRewardPool coin.Coins
|
||||
}
|
||||
```
|
||||
|
||||
Description:
|
||||
* Candidate: the public key of the validator candidate: bonding too
|
||||
* Shares: the number of delegator shares received from the validator candidate
|
||||
* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds
|
||||
entitled fees from `GlobalState.FeePool`
|
||||
* AdjustmentRewardPool: Adjustment factor used to passively calculate each
|
||||
bonds entitled fees from `Candidate.ProposerRewardPool`
|
||||
|
||||
|
||||
### QueueElem
|
||||
|
||||
The Unbonding and re-delegation process is implemented using the ordered queue
|
||||
data structure. All queue elements share a common structure:
|
||||
|
||||
```golang
|
||||
type QueueElem struct {
|
||||
Candidate crypto.PubKey
|
||||
InitTime int64 // when the element was added to the queue
|
||||
type Delegation struct {
|
||||
Shares sdk.Rat // delegation shares recieved
|
||||
Height int64 // last height bond updated
|
||||
}
|
||||
```
|
||||
|
||||
The queue is ordered so the next element to unbond/re-delegate is at the head.
|
||||
Every tick the head of the queue is checked and if the unbonding period has
|
||||
passed since `InitTime`, the final settlement of the unbonding is started or
|
||||
re-delegation is executed, and the element is popped from the queue. Each
|
||||
`QueueElem` is persisted in the store until it is popped from the queue.
|
||||
### UnbondingDelegation
|
||||
|
||||
### QueueElemUnbondDelegation
|
||||
Shares in a `Delegation` can be unbonded, but they must for some time exist as an `UnbondingDelegation`,
|
||||
where shares can be reduced if Byzantine behaviour is detected.
|
||||
|
||||
QueueElemUnbondDelegation structure is used in the unbonding queue.
|
||||
`UnbondingDelegation` are indexed in the store as:
|
||||
|
||||
- UnbondingDelegationByDelegator: ` 0x0B | DelegatorAddr | ValOwnerAddr ->
|
||||
amino(unbondingDelegation)`
|
||||
- UnbondingDelegationByValOwner: ` 0x0C | ValOwnerAddr | DelegatorAddr | ValOwnerAddr ->
|
||||
nil`
|
||||
|
||||
The first map here is used in queries, to lookup all unbonding delegations for
|
||||
a given delegator, while the second map is used in slashing, to lookup all
|
||||
unbonding delegations associated with a given validator that need to be
|
||||
slashed.
|
||||
|
||||
A UnbondingDelegation object is created every time an unbonding is initiated.
|
||||
The unbond must be completed with a second transaction provided by the
|
||||
delegation owner after the unbonding period has passed.
|
||||
|
||||
```golang
|
||||
type QueueElemUnbondDelegation struct {
|
||||
QueueElem
|
||||
Payout Address // account to pay out to
|
||||
Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding
|
||||
StartSlashRatio rational.Rat // candidate slash ratio
|
||||
type UnbondingDelegation struct {
|
||||
Tokens sdk.Coins // the value in Atoms of the amount of shares which are unbonding
|
||||
CompleteTime int64 // unix time to complete redelegation
|
||||
}
|
||||
```
|
||||
|
||||
### QueueElemReDelegate
|
||||
### Redelegation
|
||||
|
||||
QueueElemReDelegate structure is used in the re-delegation queue.
|
||||
Shares in a `Delegation` can be rebonded to a different validator, but they must for some time exist as a `Redelegation`,
|
||||
where shares can be reduced if Byzantine behaviour is detected. This is tracked
|
||||
as moving a delegation from a `FromValOwnerAddr` to a `ToValOwnerAddr`.
|
||||
|
||||
`Redelegation` are indexed in the store as:
|
||||
|
||||
- Redelegations: `0x0D | DelegatorAddr | FromValOwnerAddr | ToValOwnerAddr ->
|
||||
amino(redelegation)`
|
||||
- RedelegationsBySrc: `0x0E | FromValOwnerAddr | ToValOwnerAddr |
|
||||
DelegatorAddr -> nil`
|
||||
- RedelegationsByDst: `0x0F | ToValOwnerAddr | FromValOwnerAddr | DelegatorAddr
|
||||
-> nil`
|
||||
|
||||
|
||||
The first map here is used for queries, to lookup all redelegations for a given
|
||||
delegator. The second map is used for slashing based on the FromValOwnerAddr,
|
||||
while the third map is for slashing based on the ToValOwnerAddr.
|
||||
|
||||
A redelegation object is created every time a redelegation occurs. The
|
||||
redelegation must be completed with a second transaction provided by the
|
||||
delegation owner after the unbonding period has passed. The destination
|
||||
delegation of a redelegation may not itself undergo a new redelegation until
|
||||
the original redelegation has been completed.
|
||||
|
||||
```golang
|
||||
type QueueElemReDelegate struct {
|
||||
QueueElem
|
||||
Payout Address // account to pay out to
|
||||
Shares rational.Rat // amount of shares which are unbonding
|
||||
NewCandidate crypto.PubKey // validator to bond to after unbond
|
||||
type Redelegation struct {
|
||||
SourceShares sdk.Rat // amount of source shares redelegating
|
||||
DestinationShares sdk.Rat // amount of destination shares created at redelegation
|
||||
CompleteTime int64 // unix time to complete redelegation
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+251
-203
@@ -1,67 +1,61 @@
|
||||
|
||||
### Transaction Overview
|
||||
|
||||
Available Transactions:
|
||||
* TxDeclareCandidacy
|
||||
* TxEditCandidacy
|
||||
* TxDelegate
|
||||
* TxUnbond
|
||||
* TxRedelegate
|
||||
* TxProveLive
|
||||
In this section we describe the processing of the transactions and the
|
||||
corresponding updates to the state. Transactions:
|
||||
- TxCreateValidator
|
||||
- TxEditValidator
|
||||
- TxDelegation
|
||||
- TxStartUnbonding
|
||||
- TxCompleteUnbonding
|
||||
- TxRedelegate
|
||||
- TxCompleteRedelegation
|
||||
|
||||
## Transaction processing
|
||||
Other important state changes:
|
||||
- Update Validators
|
||||
|
||||
In this section we describe the processing of the transactions and the
|
||||
corresponding updates to the global state. In the following text we will use
|
||||
`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a
|
||||
reference to the queue of unbond delegations, `reDelegationQueue` is the
|
||||
reference for the queue of redelegations. We use `tx` to denote a
|
||||
reference to a transaction that is being processed, and `sender` to denote the
|
||||
address of the sender of the transaction. We use function
|
||||
`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store,
|
||||
and `saveCandidate(store, candidate)` to save it. Similarly, we use
|
||||
`loadDelegatorBond(store, sender, PubKey)` to load a delegator bond with the
|
||||
key (sender and PubKey) from the store, and
|
||||
`saveDelegatorBond(store, sender, bond)` to save it.
|
||||
`removeDelegatorBond(store, sender, bond)` is used to remove the bond from the
|
||||
store.
|
||||
Other notes:
|
||||
- `tx` denotes a reference to the transaction being processed
|
||||
- `sender` denotes the address of the sender of the transaction
|
||||
- `getXxx`, `setXxx`, and `removeXxx` functions are used to retrieve and
|
||||
modify objects from the store
|
||||
- `sdk.Rat` refers to a rational numeric type specified by the SDK.
|
||||
|
||||
### TxDeclareCandidacy
|
||||
### TxCreateValidator
|
||||
|
||||
A validator candidacy is declared using the `TxDeclareCandidacy` transaction.
|
||||
A validator is created using the `TxCreateValidator` transaction.
|
||||
|
||||
```golang
|
||||
type TxDeclareCandidacy struct {
|
||||
type TxCreateValidator struct {
|
||||
OwnerAddr sdk.Address
|
||||
ConsensusPubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission rational.Rat
|
||||
CommissionMax int64
|
||||
CommissionMaxChange int64
|
||||
SelfDelegation coin.Coin
|
||||
|
||||
Description Description
|
||||
Commission sdk.Rat
|
||||
CommissionMax sdk.Rat
|
||||
CommissionMaxChange sdk.Rat
|
||||
}
|
||||
|
||||
|
||||
declareCandidacy(tx TxDeclareCandidacy):
|
||||
candidate = loadCandidate(store, tx.PubKey)
|
||||
if candidate != nil return // candidate with that public key already exists
|
||||
createValidator(tx TxCreateValidator):
|
||||
validator = getValidator(tx.OwnerAddr)
|
||||
if validator != nil return // only one validator per address
|
||||
|
||||
candidate = NewCandidate(tx.PubKey)
|
||||
candidate.Status = Unbonded
|
||||
candidate.Owner = sender
|
||||
init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero
|
||||
init commision related fields based on the values from tx
|
||||
candidate.ProposerRewardPool = Coin(0)
|
||||
candidate.Description = tx.Description
|
||||
validator = NewValidator(OwnerAddr, ConsensusPubKey, GovernancePubKey, Description)
|
||||
init validator poolShares, delegatorShares set to 0
|
||||
init validator commision fields from tx
|
||||
validator.PoolShares = 0
|
||||
|
||||
saveCandidate(store, candidate)
|
||||
setValidator(validator)
|
||||
|
||||
txDelegate = TxDelegate(tx.PubKey, tx.Amount)
|
||||
return delegateWithCandidate(txDelegate, candidate)
|
||||
|
||||
// see delegateWithCandidate function in [TxDelegate](TxDelegate)
|
||||
txDelegate = TxDelegate(tx.OwnerAddr, tx.OwnerAddr, tx.SelfDelegation)
|
||||
delegate(txDelegate, validator) // see delegate function in [TxDelegate](TxDelegate)
|
||||
return
|
||||
```
|
||||
|
||||
### TxEditCandidacy
|
||||
### TxEditValidator
|
||||
|
||||
If either the `Description` (excluding `DateBonded` which is constant),
|
||||
`Commission`, or the `GovernancePubKey` need to be updated, the
|
||||
@@ -70,214 +64,268 @@ If either the `Description` (excluding `DateBonded` which is constant),
|
||||
```golang
|
||||
type TxEditCandidacy struct {
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission int64
|
||||
Commission sdk.Rat
|
||||
Description Description
|
||||
}
|
||||
|
||||
editCandidacy(tx TxEditCandidacy):
|
||||
candidate = loadCandidate(store, tx.PubKey)
|
||||
if candidate == nil or candidate.Status == Revoked return
|
||||
validator = getValidator(tx.ValidatorAddr)
|
||||
|
||||
if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey
|
||||
if tx.Commission >= 0 candidate.Commission = tx.Commission
|
||||
if tx.Description != nil candidate.Description = tx.Description
|
||||
if tx.Commission > CommissionMax || tx.Commission < 0 then fail
|
||||
if rateChange(tx.Commission) > CommissionMaxChange then fail
|
||||
validator.Commission = tx.Commission
|
||||
|
||||
if tx.GovernancePubKey != nil validator.GovernancePubKey = tx.GovernancePubKey
|
||||
if tx.Description != nil validator.Description = tx.Description
|
||||
|
||||
saveCandidate(store, candidate)
|
||||
setValidator(store, validator)
|
||||
return
|
||||
```
|
||||
|
||||
### TxDelegate
|
||||
### TxDelegation
|
||||
|
||||
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`.
|
||||
Within this transaction the delegator provides coins, and in return receives
|
||||
some amount of their validator's delegator-shares that are assigned to
|
||||
`Delegation.Shares`.
|
||||
|
||||
```golang
|
||||
type TxDelegate struct {
|
||||
PubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
DelegatorAddr sdk.Address
|
||||
ValidatorAddr sdk.Address
|
||||
Amount sdk.Coin
|
||||
}
|
||||
|
||||
delegate(tx TxDelegate):
|
||||
candidate = loadCandidate(store, tx.PubKey)
|
||||
if candidate == nil return
|
||||
return delegateWithCandidate(tx, candidate)
|
||||
pool = getPool()
|
||||
if validator.Status == Revoked return
|
||||
|
||||
delegateWithCandidate(tx TxDelegate, candidate Candidate):
|
||||
if candidate.Status == Revoked return
|
||||
|
||||
if candidate.Status == Bonded
|
||||
poolAccount = params.HoldBonded
|
||||
else
|
||||
poolAccount = params.HoldUnbonded
|
||||
delegation = getDelegatorBond(DelegatorAddr, ValidatorAddr)
|
||||
if delegation == nil then delegation = NewDelegation(DelegatorAddr, ValidatorAddr)
|
||||
|
||||
err = transfer(sender, poolAccount, tx.Amount)
|
||||
if err != nil return
|
||||
|
||||
bond = loadDelegatorBond(store, sender, tx.PubKey)
|
||||
if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0))
|
||||
|
||||
issuedDelegatorShares = addTokens(tx.Amount, candidate)
|
||||
bond.Shares += issuedDelegatorShares
|
||||
|
||||
saveCandidate(store, candidate)
|
||||
saveDelegatorBond(store, sender, bond)
|
||||
saveGlobalState(store, gs)
|
||||
return
|
||||
|
||||
addTokens(amount coin.Coin, candidate Candidate):
|
||||
if candidate.Status == Bonded
|
||||
gs.BondedPool += amount
|
||||
issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool)
|
||||
gs.BondedShares += issuedShares
|
||||
else
|
||||
gs.UnbondedPool += amount
|
||||
issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool)
|
||||
gs.UnbondedShares += issuedShares
|
||||
|
||||
candidate.GlobalStakeShares += issuedShares
|
||||
validator, pool, issuedDelegatorShares = validator.addTokensFromDel(tx.Amount, pool)
|
||||
delegation.Shares += issuedDelegatorShares
|
||||
|
||||
if candidate.IssuedDelegatorShares.IsZero()
|
||||
exRate = rational.One
|
||||
else
|
||||
exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares
|
||||
|
||||
issuedDelegatorShares = issuedShares / exRate
|
||||
candidate.IssuedDelegatorShares += issuedDelegatorShares
|
||||
return issuedDelegatorShares
|
||||
|
||||
exchangeRate(shares rational.Rat, tokenAmount int64):
|
||||
if shares.IsZero() then return rational.One
|
||||
return tokenAmount / shares
|
||||
|
||||
setDelegation(delegation)
|
||||
updateValidator(validator)
|
||||
setPool(pool)
|
||||
return
|
||||
```
|
||||
|
||||
### TxUnbond
|
||||
### TxStartUnbonding
|
||||
|
||||
Delegator unbonding is defined with the following transaction:
|
||||
|
||||
```golang
|
||||
type TxUnbond struct {
|
||||
PubKey crypto.PubKey
|
||||
Shares rational.Rat
|
||||
type TxStartUnbonding struct {
|
||||
DelegatorAddr sdk.Address
|
||||
ValidatorAddr sdk.Address
|
||||
Shares string
|
||||
}
|
||||
|
||||
unbond(tx TxUnbond):
|
||||
bond = loadDelegatorBond(store, sender, tx.PubKey)
|
||||
if bond == nil return
|
||||
if bond.Shares < tx.Shares return
|
||||
|
||||
bond.Shares -= tx.Shares
|
||||
|
||||
candidate = loadCandidate(store, tx.PubKey)
|
||||
|
||||
revokeCandidacy = false
|
||||
if bond.Shares.IsZero()
|
||||
if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond)
|
||||
else
|
||||
saveDelegatorBond(store, sender, bond)
|
||||
|
||||
if candidate.Status == Bonded
|
||||
poolAccount = params.HoldBonded
|
||||
else
|
||||
poolAccount = params.HoldUnbonded
|
||||
|
||||
returnedCoins = removeShares(candidate, shares)
|
||||
|
||||
unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio)
|
||||
unbondDelegationQueue.add(unbondDelegationElem)
|
||||
|
||||
transfer(poolAccount, unbondingPoolAddress, returnCoins)
|
||||
startUnbonding(tx TxStartUnbonding):
|
||||
delegation, found = getDelegatorBond(store, sender, tx.PubKey)
|
||||
if !found == nil return
|
||||
|
||||
if revokeCandidacy
|
||||
if candidate.Status == Bonded then bondedToUnbondedPool(candidate)
|
||||
candidate.Status = Revoked
|
||||
if bond.Shares < tx.Shares
|
||||
return ErrNotEnoughBondShares
|
||||
|
||||
if candidate.IssuedDelegatorShares.IsZero()
|
||||
removeCandidate(store, tx.PubKey)
|
||||
else
|
||||
saveCandidate(store, candidate)
|
||||
validator, found = GetValidator(tx.ValidatorAddr)
|
||||
if !found {
|
||||
return err
|
||||
|
||||
saveGlobalState(store, gs)
|
||||
return
|
||||
bond.Shares -= tx.Shares
|
||||
|
||||
removeShares(candidate Candidate, shares rational.Rat):
|
||||
globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares
|
||||
revokeCandidacy = false
|
||||
if bond.Shares.IsZero() {
|
||||
|
||||
if candidate.Status == Bonded
|
||||
gs.BondedShares -= globalPoolSharesToRemove
|
||||
removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove
|
||||
gs.BondedPool -= removedTokens
|
||||
else
|
||||
gs.UnbondedShares -= globalPoolSharesToRemove
|
||||
removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove
|
||||
gs.UnbondedPool -= removedTokens
|
||||
|
||||
candidate.GlobalStakeShares -= removedTokens
|
||||
candidate.IssuedDelegatorShares -= shares
|
||||
return returnedCoins
|
||||
if bond.DelegatorAddr == validator.Owner && validator.Revoked == false
|
||||
revokeCandidacy = true
|
||||
|
||||
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
|
||||
removeDelegation( bond)
|
||||
else
|
||||
bond.Height = currentBlockHeight
|
||||
setDelegation(bond)
|
||||
|
||||
return transfer(address of the bonded pool, address of the unbonded pool, removedTokens)
|
||||
pool = GetPool()
|
||||
validator, pool, returnAmount = validator.removeDelShares(pool, tx.Shares)
|
||||
setPool( pool)
|
||||
|
||||
unbondingDelegation = NewUnbondingDelegation(sender, returnAmount, currentHeight/Time, startSlashRatio)
|
||||
setUnbondingDelegation(unbondingDelegation)
|
||||
|
||||
if revokeCandidacy
|
||||
validator.Revoked = true
|
||||
|
||||
validator = updateValidator(validator)
|
||||
|
||||
if validator.DelegatorShares == 0 {
|
||||
removeValidator(validator.Owner)
|
||||
|
||||
return
|
||||
```
|
||||
|
||||
### TxRedelegate
|
||||
### TxCompleteUnbonding
|
||||
|
||||
The re-delegation command allows delegators to switch validators while still
|
||||
receiving equal reward to as if they had never unbonded.
|
||||
Complete the unbonding and transfer the coins to the delegate. Perform any
|
||||
slashing that occurred during the unbonding period.
|
||||
|
||||
```golang
|
||||
type TxRedelegate struct {
|
||||
PubKeyFrom crypto.PubKey
|
||||
PubKeyTo crypto.PubKey
|
||||
Shares rational.Rat
|
||||
type TxUnbondingComplete struct {
|
||||
DelegatorAddr sdk.Address
|
||||
ValidatorAddr sdk.Address
|
||||
}
|
||||
|
||||
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)
|
||||
redelegationComplete(tx TxRedelegate):
|
||||
unbonding = getUnbondingDelegation(tx.DelegatorAddr, tx.Validator)
|
||||
if unbonding.CompleteTime >= CurrentBlockTime && unbonding.CompleteHeight >= CurrentBlockHeight
|
||||
validator = GetValidator(tx.ValidatorAddr)
|
||||
returnTokens = ExpectedTokens * tx.startSlashRatio/validator.SlashRatio
|
||||
AddCoins(unbonding.DelegatorAddr, returnTokens)
|
||||
removeUnbondingDelegation(unbonding)
|
||||
return
|
||||
```
|
||||
|
||||
### TxProveLive
|
||||
### TxRedelegation
|
||||
|
||||
If a validator was automatically unbonded due to liveness issues and wishes to
|
||||
assert it is still online, it can send `TxProveLive`:
|
||||
The redelegation command allows delegators to instantly switch validators. Once
|
||||
the unbonding period has passed, the redelegation must be completed with
|
||||
txRedelegationComplete.
|
||||
|
||||
```golang
|
||||
type TxProveLive struct {
|
||||
PubKey crypto.PubKey
|
||||
type TxRedelegate struct {
|
||||
DelegatorAddr Address
|
||||
ValidatorFrom Validator
|
||||
ValidatorTo Validator
|
||||
Shares sdk.Rat
|
||||
CompletedTime int64
|
||||
}
|
||||
|
||||
redelegate(tx TxRedelegate):
|
||||
|
||||
pool = getPool()
|
||||
delegation = getDelegatorBond(tx.DelegatorAddr, tx.ValidatorFrom.Owner)
|
||||
if delegation == nil
|
||||
return
|
||||
|
||||
if delegation.Shares < tx.Shares
|
||||
return
|
||||
delegation.shares -= Tx.Shares
|
||||
validator, pool, createdCoins = validator.RemoveShares(pool, tx.Shares)
|
||||
setPool(pool)
|
||||
|
||||
redelegation = newRedelegation(tx.DelegatorAddr, tx.validatorFrom,
|
||||
tx.validatorTo, tx.Shares, createdCoins, tx.CompletedTime)
|
||||
setRedelegation(redelegation)
|
||||
return
|
||||
```
|
||||
|
||||
All delegators in the temporary unbonding pool which have not
|
||||
transacted to move will be bonded back to the now-live validator and begin to
|
||||
once again collect provisions and rewards.
|
||||
### TxCompleteRedelegation
|
||||
|
||||
Note that unlike TxCompleteUnbonding slashing of redelegating shares does not
|
||||
take place during completion. Slashing on redelegated shares takes place
|
||||
actively as a slashing occurs.
|
||||
|
||||
```golang
|
||||
type TxRedelegationComplete struct {
|
||||
DelegatorAddr Address
|
||||
ValidatorFrom Validator
|
||||
ValidatorTo Validator
|
||||
}
|
||||
|
||||
redelegationComplete(tx TxRedelegate):
|
||||
redelegation = getRedelegation(tx.DelegatorAddr, tx.validatorFrom, tx.validatorTo)
|
||||
if redelegation.CompleteTime >= CurrentBlockTime && redelegation.CompleteHeight >= CurrentBlockHeight
|
||||
removeRedelegation(redelegation)
|
||||
return
|
||||
```
|
||||
TODO: pseudo-code
|
||||
|
||||
### Update Validators
|
||||
|
||||
Within many transactions the validator set must be updated based on changes in
|
||||
power to a single validator. This process also updates the Tendermint-Updates
|
||||
store for use in end-block when validators are either added or kicked from the
|
||||
Tendermint.
|
||||
|
||||
```golang
|
||||
updateBondedValidators(newValidator Validator) (updatedVal Validator)
|
||||
|
||||
kickCliffValidator = false
|
||||
oldCliffValidatorAddr = getCliffValidator(ctx)
|
||||
|
||||
// add the actual validator power sorted store
|
||||
maxValidators = GetParams(ctx).MaxValidators
|
||||
iterator = ReverseSubspaceIterator(ValidatorsByPowerKey) // largest to smallest
|
||||
bondedValidatorsCount = 0
|
||||
var validator Validator
|
||||
for {
|
||||
if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) {
|
||||
|
||||
if bondedValidatorsCount == int(maxValidators) { // is cliff validator
|
||||
setCliffValidator(ctx, validator, GetPool(ctx))
|
||||
iterator.Close()
|
||||
break
|
||||
|
||||
// either retrieve the original validator from the store,
|
||||
// or under the situation that this is the "new validator" just
|
||||
// use the validator provided because it has not yet been updated
|
||||
// in the main validator store
|
||||
|
||||
ownerAddr = iterator.Value()
|
||||
if bytes.Equal(ownerAddr, newValidator.Owner) {
|
||||
validator = newValidator
|
||||
else
|
||||
validator = getValidator(ownerAddr)
|
||||
|
||||
// if not previously a validator (and unrevoked),
|
||||
// kick the cliff validator / bond this new validator
|
||||
if validator.Status() != Bonded && !validator.Revoked {
|
||||
kickCliffValidator = true
|
||||
|
||||
validator = bondValidator(ctx, store, validator)
|
||||
if bytes.Equal(ownerAddr, newValidator.Owner) {
|
||||
updatedVal = validator
|
||||
|
||||
bondedValidatorsCount++
|
||||
iterator.Next()
|
||||
|
||||
// perform the actual kicks
|
||||
if oldCliffValidatorAddr != nil && kickCliffValidator {
|
||||
validator = getValidator(store, oldCliffValidatorAddr)
|
||||
unbondValidator(ctx, store, validator)
|
||||
return
|
||||
|
||||
// perform all the store operations for when a validator status becomes unbonded
|
||||
unbondValidator(ctx Context, store KVStore, validator Validator)
|
||||
pool = GetPool(ctx)
|
||||
|
||||
// set the status
|
||||
validator, pool = validator.UpdateStatus(pool, Unbonded)
|
||||
setPool(ctx, pool)
|
||||
|
||||
// save the now unbonded validator record
|
||||
setValidator(validator)
|
||||
|
||||
// add to accumulated changes for tendermint
|
||||
setTendermintUpdates(validator.abciValidatorZero)
|
||||
|
||||
// also remove from the bonded validators index
|
||||
removeValidatorsBonded(validator)
|
||||
}
|
||||
|
||||
// perform all the store operations for when a validator status becomes bonded
|
||||
bondValidator(ctx Context, store KVStore, validator Validator) Validator
|
||||
pool = GetPool(ctx)
|
||||
|
||||
// set the status
|
||||
validator, pool = validator.UpdateStatus(pool, Bonded)
|
||||
setPool(ctx, pool)
|
||||
|
||||
// save the now bonded validator record to the three referenced stores
|
||||
setValidator(validator)
|
||||
setValidatorsBonded(validator)
|
||||
|
||||
// add to accumulated changes for tendermint
|
||||
setTendermintUpdates(validator.abciValidator)
|
||||
|
||||
return validator
|
||||
```
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
# Validator Set Changes
|
||||
|
||||
The validator set may be updated by state transitions that run at the beginning and
|
||||
end of every block. This can happen one of three ways:
|
||||
|
||||
- voting power of a validator changes due to bonding and unbonding
|
||||
- voting power of validator is "slashed" due to conflicting signed messages
|
||||
- validator is automatically unbonded due to inactivity
|
||||
|
||||
## Voting Power Changes
|
||||
|
||||
At the end of every block, we run the following:
|
||||
|
||||
(TODO remove inflation from here)
|
||||
|
||||
```golang
|
||||
tick(ctx Context):
|
||||
hrsPerYr = 8766 // as defined by a julian year of 365.25 days
|
||||
|
||||
time = ctx.Time()
|
||||
if time > gs.InflationLastTime + ProvisionTimeout
|
||||
gs.InflationLastTime = time
|
||||
gs.Inflation = nextInflation(hrsPerYr).Round(1000000000)
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
|
||||
## Slashing
|
||||
|
||||
Messges which may compromise the safety of the underlying consensus protocol ("equivocations")
|
||||
result in some amount of the offending validator's shares being removed ("slashed").
|
||||
|
||||
Currently, such messages include only the following:
|
||||
|
||||
- prevotes by the same validator for more than one BlockID at the same
|
||||
Height and Round
|
||||
- precommits by the same validator for more than one BlockID at the same
|
||||
Height and Round
|
||||
|
||||
We call any such pair of conflicting votes `Evidence`. Full nodes in the network prioritize the
|
||||
detection and gossipping of `Evidence` so that it may be rapidly included in blocks and the offending
|
||||
validators punished.
|
||||
|
||||
For some `evidence` to be valid, it must satisfy:
|
||||
|
||||
`evidence.Timestamp >= block.Timestamp - MAX_EVIDENCE_AGE`
|
||||
|
||||
where `evidence.Timestamp` is the timestamp in the block at height
|
||||
`evidence.Height` and `block.Timestamp` is the current block timestamp.
|
||||
|
||||
If valid evidence is included in a block, the offending validator loses
|
||||
a constant `SLASH_PROPORTION` of their current stake at the beginning of the block:
|
||||
|
||||
```
|
||||
oldShares = validator.shares
|
||||
validator.shares = oldShares * (1 - SLASH_PROPORTION)
|
||||
```
|
||||
|
||||
This ensures that offending validators are punished the same amount whether they
|
||||
act as a single validator with X stake or as N validators with collectively X
|
||||
stake.
|
||||
|
||||
|
||||
## Automatic Unbonding
|
||||
|
||||
Every block includes a set of precommits by the validators for the previous block,
|
||||
known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power.
|
||||
|
||||
Proposers are incentivized to include precommits from all
|
||||
validators in the LastCommit by receiving additional fees
|
||||
proportional to the difference between the voting power included in the
|
||||
LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)).
|
||||
|
||||
Validators are penalized for failing to be included in the LastCommit for some
|
||||
number of blocks by being automatically unbonded.
|
||||
|
||||
The following information is stored with each validator candidate, and is only non-zero if the candidate becomes an active validator:
|
||||
|
||||
```go
|
||||
type ValidatorSigningInfo struct {
|
||||
StartHeight int64
|
||||
SignedBlocksBitArray BitArray
|
||||
}
|
||||
```
|
||||
|
||||
Where:
|
||||
* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power).
|
||||
* `SignedBlocksBitArray` is a bit-array of size `SIGNED_BLOCKS_WINDOW` that records, for each of the last `SIGNED_BLOCKS_WINDOW` blocks,
|
||||
whether or not this validator was included in the LastCommit. It uses a `0` if the validator was included, and a `1` if it was not.
|
||||
Note it is initialized with all 0s.
|
||||
|
||||
At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded:
|
||||
|
||||
```
|
||||
h = block.Height
|
||||
index = h % SIGNED_BLOCKS_WINDOW
|
||||
|
||||
for val in block.Validators:
|
||||
signInfo = val.SignInfo
|
||||
if val in block.LastCommit:
|
||||
signInfo.SignedBlocksBitArray.Set(index, 0)
|
||||
else
|
||||
signInfo.SignedBlocksBitArray.Set(index, 1)
|
||||
|
||||
// validator must be active for at least SIGNED_BLOCKS_WINDOW
|
||||
// before they can be automatically unbonded for failing to be
|
||||
// included in 50% of the recent LastCommits
|
||||
minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW
|
||||
minSigned = SIGNED_BLOCKS_WINDOW / 2
|
||||
blocksSigned = signInfo.SignedBlocksBitArray.Sum()
|
||||
if h > minHeight AND blocksSigned < minSigned:
|
||||
unbond the validator
|
||||
```
|
||||
Reference in New Issue
Block a user