cosmos-sdk/docs/spec/provisioning/overview.md

230 lines
10 KiB
Markdown
Raw Normal View History

2018-05-17 10:32:13 -07:00
# 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
2018-06-05 09:11:03 -07:00
Commission on Atom Provisions and having atoms autobonded are mutually
exclusive (we cant 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
2018-05-17 10:32:13 -07:00
## 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
2018-05-17 10:32:13 -07:00
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
2018-05-17 10:32:13 -07:00
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
2018-05-17 10:32:13 -07:00
fee pool would be if there `VotingPower` was to never change and they were to
never withdraw fees.
```
validator.count = validator.VotingPower * BlockHeight
2018-05-17 10:32:13 -07:00
```
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
2018-05-17 10:32:13 -07:00
`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)
2018-05-17 10:32:13 -07:00
/ (gs.PrevPower * (height-1)) * gs.PrevFeesReceived
+ validator.Power / gs.Power * gs.RecentFee
2018-05-17 10:32:13 -07:00
AdjustmentChange = simplePool - projectedPool
validator.AdjustmentRewardPool += AdjustmentChange
2018-05-17 10:32:13 -07:00
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.
2018-05-17 10:32:13 -07:00
```
validator.AdjustmentRewardPool += withdrawn
2018-05-17 10:32:13 -07:00
gs.Adjustment += withdrawn
```
Now the entitled fee pool of each validator can be lazily accounted for at
2018-05-17 10:32:13 -07:00
any given block:
```
validator.feePool = validator.simplePool - validator.Adjustment
2018-05-17 10:32:13 -07:00
```
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
2018-05-17 10:32:13 -07:00
calculations outline the math behind withdrawing fee rewards as either a
delegator to a validator providing commission, or as the owner of a validator
2018-05-17 10:32:13 -07:00
who is receiving commission.
### Calculations For Delegators and Validators
2018-05-17 10:32:13 -07:00
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`.
2018-05-17 10:32:13 -07:00
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`
2018-05-17 10:32:13 -07:00
- global power in this context is actually shares
`validator.TotalDelegatorShares`
2018-05-17 10:32:13 -07:00
- 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`
2018-05-17 10:32:13 -07:00
- 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`
2018-05-17 10:32:13 -07:00
- global power in this context is actually shares
`validator.TotalDelegatorShares`
2018-05-17 10:32:13 -07:00
- entitled party voting power should be taken as the of commission portion
of total delegators shares,
`validator.TotalDelegatorShares * validator.Commission`
2018-05-17 10:32:13 -07:00
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.
2018-05-17 10:32:13 -07:00
- 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
```