From 7fb626f548b41dfb5be2cd5314760e3d00da51d8 Mon Sep 17 00:00:00 2001 From: Rigel Date: Wed, 8 Aug 2018 06:10:21 -0400 Subject: [PATCH] Merge PR #1702: lamborghini distribution & inflation spec upgrade --- Gopkg.lock | 1 + PENDING.md | 1 + docs/spec/README.md | 3 +- docs/spec/distribution/end_block.md | 36 ++ .../example_sheet/distribution.xlsx} | Bin docs/spec/distribution/future_improvements.md | 16 + docs/spec/distribution/overview.md | 54 +++ docs/spec/distribution/state.md | 100 +++++ docs/spec/distribution/transactions.md | 399 ++++++++++++++++++ docs/spec/distribution/triggers.md | 31 ++ docs/spec/inflation/end_block.md | 52 +++ docs/spec/inflation/state.md | 21 + docs/spec/provisioning/overview.md | 229 ---------- docs/spec/provisioning/state.md | 13 - docs/spec/staking/README.md | 35 +- docs/spec/staking/end_block.md | 44 +- docs/spec/staking/state.md | 18 +- docs/spec/staking/transactions.md | 9 +- .../keeper.go | 0 .../keeper_test.go | 0 .../movement.go | 0 x/{fee_distribution => distribution}/types.go | 0 22 files changed, 742 insertions(+), 320 deletions(-) create mode 100644 docs/spec/distribution/end_block.md rename docs/spec/{provisioning/fee_distribution_model.xlsx => distribution/example_sheet/distribution.xlsx} (100%) create mode 100644 docs/spec/distribution/future_improvements.md create mode 100644 docs/spec/distribution/overview.md create mode 100644 docs/spec/distribution/state.md create mode 100644 docs/spec/distribution/transactions.md create mode 100644 docs/spec/distribution/triggers.md create mode 100644 docs/spec/inflation/end_block.md create mode 100644 docs/spec/inflation/state.md delete mode 100644 docs/spec/provisioning/overview.md delete mode 100644 docs/spec/provisioning/state.md rename x/{fee_distribution => distribution}/keeper.go (100%) rename x/{fee_distribution => distribution}/keeper_test.go (100%) rename x/{fee_distribution => distribution}/movement.go (100%) rename x/{fee_distribution => distribution}/types.go (100%) diff --git a/Gopkg.lock b/Gopkg.lock index ec32cccba0..018836215d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -617,6 +617,7 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 + inputs-digest = "71e86b1f1e9ec71901c20d8532dc8477df66eff37a407322379f6a8b03e5d91b" input-imports = [ "github.com/bartekn/go-bip39", "github.com/bgentry/speakeasy", diff --git a/PENDING.md b/PENDING.md index 9d405a0f66..d942ecb176 100644 --- a/PENDING.md +++ b/PENDING.md @@ -52,6 +52,7 @@ IMPROVEMENTS * [tools] Remove `rm -rf vendor/` from `make get_vendor_deps` * [x/auth] Recover ErrorOutOfGas panic in order to set sdk.Result attributes correctly * [x/stake] Add revoked to human-readable validator +* [spec] \#967 Inflation and distribution specs drastically improved * [tests] Add tests to example apps in docs * [x/gov] Votes on a proposal can now be queried * [x/bank] Unit tests are now table-driven diff --git a/docs/spec/README.md b/docs/spec/README.md index 0b708aba26..49ea67b7a2 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -17,7 +17,8 @@ said, they provide a detailed resource for understanding the Cosmos-SDK. - [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 +- [Distribution](distribution) - Fee distribution, and atom provision distribution +- [Inflation](inflation) - Atom provision creation - [IBC](ibc) - Inter-Blockchain Communication (IBC) protocol. - [Other](other) - Other components of the Cosmos Hub, including the reserve pool, All in Bits vesting, etc. diff --git a/docs/spec/distribution/end_block.md b/docs/spec/distribution/end_block.md new file mode 100644 index 0000000000..bc6847ef4b --- /dev/null +++ b/docs/spec/distribution/end_block.md @@ -0,0 +1,36 @@ +# End Block + +At each endblock, the fees received are sorted to the proposer, community fund, +and global pool. 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 proportionally +by voting power to all bonded validators independent of whether they voted +(social distribution). Note the social distribution is applied to proposer +validator in addition to the proposer reward. + +The amount of proposer reward is calculated from pre-commits Tendermint +messages in order to incentivize validators to wait and include additional +pre-commits in the block. All provision rewards are added to a provision reward +pool which validator holds individually +(`ValidatorDistribution.ProvisionsRewardPool`). + +``` +func SortFees(feesCollected sdk.Coins, global Global, proposer ValidatorDistribution, + sumPowerPrecommitValidators, totalBondedTokens, communityTax sdk.Dec) + + feesCollectedDec = MakeDecCoins(feesCollected) + proposerReward = feesCollectedDec * (0.01 + 0.04 + * sumPowerPrecommitValidators / totalBondedTokens) + proposer.ProposerPool += proposerReward + + communityFunding = feesCollectedDec * communityTax + global.CommunityFund += communityFunding + + poolReceived = feesCollectedDec - proposerReward - communityFunding + global.Pool += poolReceived + global.EverReceivedPool += poolReceived + global.LastReceivedPool = poolReceived + + SetValidatorDistribution(proposer) + SetGlobal(global) +``` diff --git a/docs/spec/provisioning/fee_distribution_model.xlsx b/docs/spec/distribution/example_sheet/distribution.xlsx similarity index 100% rename from docs/spec/provisioning/fee_distribution_model.xlsx rename to docs/spec/distribution/example_sheet/distribution.xlsx diff --git a/docs/spec/distribution/future_improvements.md b/docs/spec/distribution/future_improvements.md new file mode 100644 index 0000000000..954fb4d623 --- /dev/null +++ b/docs/spec/distribution/future_improvements.md @@ -0,0 +1,16 @@ +## Future Improvements + +### Power Change + +Within the current implementation all power changes ever made are indefinitely stored +within the current state. In the future this state should be trimmed on an epoch basis. Delegators +which will have not withdrawn their fees will be penalized in some way, depending on what is +computationally feasible this may include: + - burning non-withdrawn fees + - requiring more expensive withdrawal costs which include proofs from archive nodes of historical state + +In addition or as an alternative it may make sense to implement a "rolling" epoch which cycles through +all the delegators in small groups (for example 5 delegators per block) and just runs the withdrawal transaction +at standard rates and takes transaction fees from the withdrawal amount. + + diff --git a/docs/spec/distribution/overview.md b/docs/spec/distribution/overview.md new file mode 100644 index 0000000000..5e28e8b3b0 --- /dev/null +++ b/docs/spec/distribution/overview.md @@ -0,0 +1,54 @@ +# Distribution + +## Overview + +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, and validator proposer-reward +pool. Due to the nature 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, + - 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), + - a validator chooses to change the commission on fees, all accumulated + commission fees must be simultaneously withdrawn. + +The above scenarios are covered in `triggers.md`. + +The distribution mechanism outlines herein is used to lazily distribute the +following between validators and associated delegators: + - multi-token fees to be socially distributed, + - proposer reward pool, + - inflated atom provisions, and + - validator commission on all rewards earned by their delegators stake + +Fees are pooled within a global pool, as well as validator specific +proposer-reward pools. The mechanisms used allow for validators and delegators +to independently and lazily withdrawn their rewards. As a part of the lazy +computations adjustment factors must be maintained for each validator and +delegator to determine the true proportion of fees in each pool which 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 change their portion of bonded +Atoms. + +## Affect on Staking + + +Charging commission on Atom provisions while also allowing for Atom-provisions +to be auto-bonded (distributed directly to the validators bonded stake) is +problematic within DPoS. Fundamentally these two mechnisms are mutually +exclusive. If there are atoms commissions and auto-bonding Atoms, 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. In conclusion we can only have atom commission and unbonded atoms +provisions, or bonded atom provisions with no Atom commission, and we elect to +implement the former. Stakeholders wishing to rebond their provisions may elect +to set up a script to periodically withdraw and rebond fees. + diff --git a/docs/spec/distribution/state.md b/docs/spec/distribution/state.md new file mode 100644 index 0000000000..7865c085ec --- /dev/null +++ b/docs/spec/distribution/state.md @@ -0,0 +1,100 @@ +## State + +### Global + +All globally tracked parameters for distribution are stored within +`Global`. Rewards are collected and added to the reward pool and +distributed to validators/delegators from here. + +Note that the reward pool holds decimal coins (`DecCoins`) to allow +for fractions of coins to be received from operations like inflation. +When coins are distributed from the pool they are truncated back to +`sdk.Coins` which are non-decimal. + + - Global: `0x00 -> amino(global)` + +```golang +// coins with decimal +type DecCoins []DecCoin + +type DecCoin struct { + Amount sdk.Dec + Denom string +} + +type Global struct { + PrevBondedTokens sdk.Dec // bonded token amount for the global pool on the previous block + Adjustment sdk.Dec // global adjustment factor for lazy calculations + Pool DecCoins // funds for all validators which have yet to be withdrawn + PrevReceivedPool DecCoins // funds added to the pool on the previous block + EverReceivedPool DecCoins // total funds ever added to the pool + CommunityFund DecCoins // pool for community funds yet to be spent +} +``` + +### Validator Distribution + +Validator distribution information for the relevant validator is updated each time: + 1. delegation amount to a validator are updated, + 2. a validator successfully proposes a block and receives a reward, + 3. any delegator withdraws from a validator, or + 4. the validator withdraws it's commission. + + - ValidatorDistribution: `0x02 | ValOwnerAddr -> amino(validatorDistribution)` + +```golang +type ValidatorDistribution struct { + CommissionWithdrawalHeight int64 // last time this validator withdrew commission + Adjustment sdk.Dec // global pool adjustment factor + ProposerAdjustment DecCoins // proposer pool adjustment factor + ProposerPool DecCoins // reward pool collected from being the proposer + EverReceivedProposerReward DecCoins // all rewards ever collected from being the proposer + PrevReceivedProposerReward DecCoins // previous rewards collected from being the proposer + PrevBondedTokens sdk.Dec // bonded token amount on the previous block + PrevDelegatorShares sdk.Dec // amount of delegator shares for the validator on the previous block +} +``` + +### Delegation Distribution + +Each delegation holds multiple adjustment factors to specify its entitlement to +the rewards from a validator. `AdjustmentPool` is used to passively calculate +each bonds entitled fees from the `RewardPool`. `AdjustmentPool` is used to +passively calculate each bonds entitled fees from +`ValidatorDistribution.ProposerRewardPool` + + - DelegatorDistribution: ` 0x02 | DelegatorAddr | ValOwnerAddr -> amino(delegatorDist)` + +```golang +type DelegatorDist struct { + WithdrawalHeight int64 // last time this delegation withdrew rewards + Adjustment sdk.Dec // fee provisioning adjustment factor + AdjustmentProposer DecCoins // proposers pool adjustment factor + PrevTokens sdk.Dec // bonded tokens held by the delegation on the previous block + PrevShares sdk.Dec // delegator shares held by the delegation on the previous block +} +``` + +### Power Change + +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. Each power change is indexed by its block +height. + + - PowerChange: `0x03 | amino(Height) -> amino(validatorDist)` + +```golang +type PowerChange struct { + Height int64 // block height at change + ValidatorBondedTokens sdk.Dec // following used to create distribution scenarios + ValidatorDelegatorShares sdk.Dec + ValidatorDelegatorShareExRate sdk.Dec + ValidatorCommission sdk.Dec + PoolBondedTokens sdk.Dec + Global Global + ValDistr ValidatorDistribution + DelegationShares sdk.Dec + DelDistr DelegatorDistribution +} +``` diff --git a/docs/spec/distribution/transactions.md b/docs/spec/distribution/transactions.md new file mode 100644 index 0000000000..1401c3b851 --- /dev/null +++ b/docs/spec/distribution/transactions.md @@ -0,0 +1,399 @@ +# Transactions + +## TxWithdrawDelegation + +When a delegator wishes to withdraw their transaction fees it must send +`TxWithdrawDelegation`. Note that parts of this transaction logic are also +triggered each with any change in individual delegations, such as an unbond, +redelegation, or delegation of additional tokens to a specific validator. + +Each time a withdrawal is made by a recipient the adjustment term must be +modified for each block with a change in distributors shares since the time of +last withdrawal. This is accomplished by iterating over all relevant +`PowerChange`'s stored in distribution state. + + +```golang +type TxWithdrawDelegation struct { + delegatorAddr sdk.AccAddress + withdrawAddr sdk.AccAddress // address to make the withdrawal to +} + +func WithdrawDelegator(delegatorAddr, withdrawAddr sdk.AccAddress) + entitlement = GetDelegatorEntitlement(delegatorAddr) + AddCoins(withdrawAddr, totalEntitlment.TruncateDecimal()) + +func GetDelegatorEntitlement(delegatorAddr sdk.AccAddress) DecCoins + + // compile all the distribution scenarios + delegations = GetDelegations(delegatorAddr) + DelDistr = GetDelegationDistribution(delegation.DelegatorAddr, + delegation.ValidatorAddr) + pcs = GetPowerChanges(DelDistr.WithdrawalHeight) + + // update all adjustment factors for each delegation since last withdrawal + for pc = range pcs + for delegation = range delegations + DelDistr = GetDelegationDistribution(delegation.DelegatorAddr, + delegation.ValidatorAddr) + pc.ProcessPowerChangeDelegation(delegation, DelDistr) + + // collect all entitled fees + entitlement = 0 + for delegation = range delegations + global = GetGlobal() + pool = GetPool() + DelDistr = GetDelegationDistribution(delegation.DelegatorAddr, + delegation.ValidatorAddr) + ValDistr = GetValidatorDistribution(delegation.ValidatorAddr) + validator = GetValidator(delegation.ValidatorAddr) + + scenerio1 = NewDelegationFromGlobalPool(delegation, validator, + pool, global, ValDistr, DelDistr) + scenerio2 = NewDelegationFromProvisionPool(delegation, validator, + ValDistr, DelDistr) + entitlement += scenerio1.WithdrawalEntitlement() + entitlement += scenerio2.WithdrawalEntitlement() + + return entitlement + +func (pc PowerChange) ProcessPowerChangeDelegation(delegation sdk.Delegation, + DelDistr DelegationDistribution) + + // get the historical scenarios + scenario1 = pc.DelegationFromGlobalPool(delegation, DelDistr) + scenario2 = pc.DelegationFromProvisionPool(delegation, DelDistr) + + // process the adjustment factors + scenario1.UpdateAdjustmentForPowerChange(pc.Height) + scenario2.UpdateAdjustmentForPowerChange(pc.Height) +``` + +## TxWithdrawValidator + +When a validator wishes to withdraw their transaction fees it must send +`TxWithdrawDelegation`. Note that parts of this transaction logic is also +triggered each with any change in individual delegations, such as an unbond, +redelegation, or delegation of additional tokens to a specific validator. This +transaction withdraws the validators commission rewards, as well as any rewards +earning on their self-delegation. + +```golang +type TxWithdrawValidator struct { + ownerAddr sdk.AccAddress // validator address to withdraw from + withdrawAddr sdk.AccAddress // address to make the withdrawal to +} + +func WithdrawalValidator(ownerAddr, withdrawAddr sdk.AccAddress) + + // update the delegator adjustment factors and also withdrawal delegation fees + entitlement = GetDelegatorEntitlement(ownerAddr) + + // update the validator adjustment factors for commission + ValDistr = GetValidatorDistribution(ownerAddr.ValidatorAddr) + pcs = GetPowerChanges(ValDistr.CommissionWithdrawalHeight) + for pc = range pcs + pc.ProcessPowerChangeCommission() + + // withdrawal validator commission rewards + global = GetGlobal() + pool = GetPool() + ValDistr = GetValidatorDistribution(delegation.ValidatorAddr) + validator = GetValidator(delegation.ValidatorAddr) + + scenerio1 = NewCommissionFromGlobalPool(validator, + pool, global, ValDistr) + scenerio2 = CommissionFromProposerPool(validator, ValDistr) + entitlement += scenerio1.WithdrawalEntitlement() + entitlement += scenerio2.WithdrawalEntitlement() + + AddCoins(withdrawAddr, totalEntitlment.TruncateDecimal()) + +func (pc PowerChange) ProcessPowerChangeCommission() + + // get the historical scenarios + scenario1 = pc.CommissionFromGlobalPool() + scenario2 = pc.CommissionFromProposerPool() + + // process the adjustment factors + scenario1.UpdateAdjustmentForPowerChange(pc.Height) + scenario2.UpdateAdjustmentForPowerChange(pc.Height) +``` + +## Common Calculations + +### Distribution scenario + +A common form of abstracted calculations exists between validators and +delegations attempting to withdrawal their rewards, either from `Global.Pool` +or from `ValidatorDistribution.ProposerPool`. With the following interface +fulfilled the entitled fees for the various scenarios can be calculated. + +```golang +type DistributionScenario interface { + DistributorTokens() DecCoins // current tokens from distributor + DistributorCumulativeTokens() DecCoins // total tokens ever received + DistributorPrevReceivedTokens() DecCoins // last value of tokens received + DistributorShares() sdk.Dec // current shares + DistributorPrevShares() sdk.Dec // shares last block + + RecipientAdjustment() sdk.Dec + RecipientShares() sdk.Dec // current shares + RecipientPrevShares() sdk.Dec // shares last block + + ModifyAdjustments(withdrawal sdk.Dec) // proceedure to modify adjustment factors +} +``` + +#### Entitled reward from distribution scenario + +The entitlement to the distributor's tokens held can be accounted for lazily. +To begin this calculation we must determine the recipient's _simple pool_ and +_projected pool_. The simple pool represents a lazy accounting of what a +recipient's entitlement to the distributor's tokens would be if all recipients +for that distributor had static shares (equal to the current shares), and no +recipients had ever withdrawn their entitled rewards. The projected pool +represents the anticipated recipient's entitlement to the distributors tokens +based on the current blocks token input (for example fees reward received) to +the distributor, and the distributor's tokens and shares of the previous block +assuming that neither had changed in the current block. Using the simple and +projected pools we can determine all cumulative changes which have taken place +outside of the recipient and adjust the recipient's _adjustment factor_ to +account for these changes and ultimately keep track of the correct entitlement +to the distributors tokens. + +``` +func (d DistributionScenario) RecipientCount(height int64) sdk.Dec + return v.RecipientShares() * height + +func (d DistributionScenario) GlobalCount(height int64) sdk.Dec + return d.DistributorShares() * height + +func (d DistributionScenario) SimplePool() DecCoins + return d.RecipientCount() / d.GlobalCount() * d.DistributorCumulativeTokens + +func (d DistributionScenario) ProjectedPool(height int64) DecCoins + return d.RecipientPrevShares() * (height-1) + / (d.DistributorPrevShares() * (height-1)) + * d.DistributorCumulativeTokens + + d.RecipientShares() / d.DistributorShares() + * d.DistributorPrevReceivedTokens() +``` + +The `DistributionScenario` _adjustment_ terms account for changes in +recipient/distributor shares and recipient withdrawals. The adjustment factor +must be modified whenever the recipient withdraws from the distributor or the +distributor's/recipient's shares are changed. + - When the shares of the recipient is changed the adjustment factor is + increased/decreased by the difference between the _simple_ and _projected_ + pools. In other words, the cumulative difference in the shares if the shares + has been the new shares as opposed to the old shares for the entire duration of + the blockchain up the previous block. + - When a recipient makes a withdrawal the adjustment factor is increased by the + withdrawal amount. + +``` +func (d DistributionScenario) UpdateAdjustmentForPowerChange(height int64) + simplePool = d.SimplePool() + projectedPool = d.ProjectedPool(height) + AdjustmentChange = simplePool - projectedPool + if AdjustmentChange > 0 + d.ModifyAdjustments(AdjustmentChange) + +func (d DistributionScenario) WithdrawalEntitlement() DecCoins + entitlement = d.SimplePool() - d.RecipientAdjustment() + d.ModifyAdjustments(entitlement) + return entitlement +``` + +### Distribution scenarios + +Note that the distribution scenario structures are found in `state.md`. + +#### Delegation's entitlement to Global.Pool + +For delegations (including validator's self-delegation) all fees from fee pool +are subject to commission rate from the owner of the validator. The global +shares should be taken as true number of global bonded shares. The recipients +shares should be taken as the bonded tokens less the validator's commission. + +``` +type DelegationFromGlobalPool struct { + DelegationShares sdk.Dec + ValidatorCommission sdk.Dec + ValidatorBondedTokens sdk.Dec + ValidatorDelegatorShareExRate sdk.Dec + PoolBondedTokens sdk.Dec + Global Global + ValDistr ValidatorDistribution + DelDistr DelegatorDistribution +} + +func (d DelegationFromGlobalPool) DistributorTokens() DecCoins + return d.Global.Pool + +func (d DelegationFromGlobalPool) DistributorCumulativeTokens() DecCoins + return d.Global.EverReceivedPool + +func (d DelegationFromGlobalPool) DistributorPrevReceivedTokens() DecCoins + return d.Global.PrevReceivedPool + +func (d DelegationFromGlobalPool) DistributorShares() sdk.Dec + return d.PoolBondedTokens + +func (d DelegationFromGlobalPool) DistributorPrevShares() sdk.Dec + return d.Global.PrevBondedTokens + +func (d DelegationFromGlobalPool) RecipientShares() sdk.Dec + return d.DelegationShares * d.ValidatorDelegatorShareExRate() * + d.ValidatorBondedTokens() * (1 - d.ValidatorCommission) + +func (d DelegationFromGlobalPool) RecipientPrevShares() sdk.Dec + return d.DelDistr.PrevTokens + +func (d DelegationFromGlobalPool) RecipientAdjustment() sdk.Dec + return d.DelDistr.Adjustment + +func (d DelegationFromGlobalPool) ModifyAdjustments(withdrawal sdk.Dec) + d.ValDistr.Adjustment += withdrawal + d.DelDistr.Adjustment += withdrawal + d.global.Adjustment += withdrawal + SetValidatorDistribution(d.ValDistr) + SetDelegatorDistribution(d.DelDistr) + SetGlobal(d.Global) +``` + +#### Delegation's entitlement to ValidatorDistribution.ProposerPool + +Delegations (including validator's self-delegation) are still subject +commission on the rewards gained from the proposer pool. Global shares in this +context is actually the validators total delegations shares. The recipient's +shares is taken as the effective delegation shares less the validator's +commission. + +``` +type DelegationFromProposerPool struct { + DelegationShares sdk.Dec + ValidatorCommission sdk.Dec + ValidatorDelegatorShares sdk.Dec + ValDistr ValidatorDistribution + DelDistr DelegatorDistribution +} + +func (d DelegationFromProposerPool) DistributorTokens() DecCoins + return d.ValDistr.ProposerPool + +func (d DelegationFromProposerPool) DistributorCumulativeTokens() DecCoins + return d.ValDistr.EverReceivedProposerReward + +func (d DelegationFromProposerPool) DistributorPrevReceivedTokens() DecCoins + return d.ValDistr.PrevReceivedProposerReward + +func (d DelegationFromProposerPool) DistributorShares() sdk.Dec + return d.ValidatorDelegatorShares + +func (d DelegationFromProposerPool) DistributorPrevShares() sdk.Dec + return d.ValDistr.PrevDelegatorShares + +func (d DelegationFromProposerPool) RecipientShares() sdk.Dec + return d.DelegationShares * (1 - d.ValidatorCommission) + +func (d DelegationFromProposerPool) RecipientPrevShares() sdk.Dec + return d.DelDistr.PrevShares + +func (d DelegationFromProposerPool) RecipientAdjustment() sdk.Dec + return d.DelDistr.AdjustmentProposer + +func (d DelegationFromProposerPool) ModifyAdjustments(withdrawal sdk.Dec) + d.ValDistr.AdjustmentProposer += withdrawal + d.DelDistr.AdjustmentProposer += withdrawal + SetValidatorDistribution(d.ValDistr) + SetDelegatorDistribution(d.DelDistr) +``` + +#### Validators's commission entitlement to Global.Pool + +Similar to a delegator's entitlement, but with recipient shares based on the +commission portion of bonded tokens. + +``` +type CommissionFromGlobalPool struct { + ValidatorBondedTokens sdk.Dec + ValidatorCommission sdk.Dec + PoolBondedTokens sdk.Dec + Global Global + ValDistr ValidatorDistribution +} + +func (c CommissionFromGlobalPool) DistributorTokens() DecCoins + return c.Global.Pool + +func (c CommissionFromGlobalPool) DistributorCumulativeTokens() DecCoins + return c.Global.EverReceivedPool + +func (c CommissionFromGlobalPool) DistributorPrevReceivedTokens() DecCoins + return c.Global.PrevReceivedPool + +func (c CommissionFromGlobalPool) DistributorShares() sdk.Dec + return c.PoolBondedTokens + +func (c CommissionFromGlobalPool) DistributorPrevShares() sdk.Dec + return c.Global.PrevBondedTokens + +func (c CommissionFromGlobalPool) RecipientShares() sdk.Dec + return c.ValidatorBondedTokens() * c.ValidatorCommission + +func (c CommissionFromGlobalPool) RecipientPrevShares() sdk.Dec + return c.ValDistr.PrevBondedTokens * c.ValidatorCommission + +func (c CommissionFromGlobalPool) RecipientAdjustment() sdk.Dec + return c.ValDistr.Adjustment + +func (c CommissionFromGlobalPool) ModifyAdjustments(withdrawal sdk.Dec) + c.ValDistr.Adjustment += withdrawal + c.Global.Adjustment += withdrawal + SetValidatorDistribution(c.ValDistr) + SetGlobal(c.Global) +``` + +#### Validators's commission entitlement to ValidatorDistribution.ProposerPool + +Similar to a delegators entitlement to the proposer pool, but with recipient +shares based on the commission portion of the total delegator shares. + +``` +type CommissionFromProposerPool struct { + ValidatorDelegatorShares sdk.Dec + ValidatorCommission sdk.Dec + ValDistr ValidatorDistribution +} + +func (c CommissionFromProposerPool) DistributorTokens() DecCoins + return c.ValDistr.ProposerPool + +func (c CommissionFromProposerPool) DistributorCumulativeTokens() DecCoins + return c.ValDistr.EverReceivedProposerReward + +func (c CommissionFromProposerPool) DistributorPrevReceivedTokens() DecCoins + return c.ValDistr.PrevReceivedProposerReward + +func (c CommissionFromProposerPool) DistributorShares() sdk.Dec + return c.ValidatorDelegatorShares + +func (c CommissionFromProposerPool) DistributorPrevShares() sdk.Dec + return c.ValDistr.PrevDelegatorShares + +func (c CommissionFromProposerPool) RecipientShares() sdk.Dec + return c.ValidatorDelegatorShares * (c.ValidatorCommission) + +func (c CommissionFromProposerPool) RecipientPrevShares() sdk.Dec + return c.ValDistr.PrevDelegatorShares * (c.ValidatorCommission) + +func (c CommissionFromProposerPool) RecipientAdjustment() sdk.Dec + return c.ValDistr.AdjustmentProposer + +func (c CommissionFromProposerPool) ModifyAdjustments(withdrawal sdk.Dec) + c.ValDistr.AdjustmentProposer += withdrawal + SetValidatorDistribution(c.ValDistr) +``` + diff --git a/docs/spec/distribution/triggers.md b/docs/spec/distribution/triggers.md new file mode 100644 index 0000000000..8800609a44 --- /dev/null +++ b/docs/spec/distribution/triggers.md @@ -0,0 +1,31 @@ +# Triggers + +## Create validator distribution + + - triggered-by: validator entering bonded validator group (`stake.bondValidator()`) + +Whenever a new validator is added to the Tendermint validator set they are +entitled to begin earning rewards of atom provisions and fees. At this point +`ValidatorDistribution.Pool()` must be zero (as the validator has not yet +earned any rewards) meaning that the initial value for `validator.Adjustment` +must be set to the value of `validator.SimplePool()` for the height which the +validator is added on the validator set. + +## Create or modify delegation distribution + + - triggered-by: `stake.TxDelegate`, `stake.TxBeginRedelegate`, `stake.TxBeginUnbonding` + +The pool of a new delegator bond will be 0 for the height at which the bond was +added. This is achieved by setting `DelegationDistribution.WithdrawalHeight` to +the height which the bond was added. Additionally the `AdjustmentPool` and +`AdjustmentProposerPool` must be set to the equivalent values of +`DelegationDistribution.SimplePool()` and +`DelegationDistribution.SimpleProposerPool()` for the height of delegation. + +## Commission rate change + + - triggered-by: `stake.TxEditValidator` + +If a validator changes its commission rate, all commission on fees must be +simultaneously withdrawn using the transaction `TxWithdrawValidator` + diff --git a/docs/spec/inflation/end_block.md b/docs/spec/inflation/end_block.md new file mode 100644 index 0000000000..49408a3f06 --- /dev/null +++ b/docs/spec/inflation/end_block.md @@ -0,0 +1,52 @@ +# End Block + +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 target 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%. + +Within the inflation module the tokens are created, and fed to the distribution +module to be further processed and distributed similarly to fee distribution (with +the exception that there are no special rewards for the block proposer) + +Note that params are global params (TODO: link to the global params spec) + +``` +EndBlock(): + + //process provisions + hrsPerYr = 8766 // as defined by a julian year of 365.25 days + precision = 10000 + + time = BFTTime() // time is in seconds + if time > GetInflationLastTime() + 3600 + SetInflationLastTime(InflationLastTime + 3600) + inflation = nextInflation(hrsPerYr).Round(precision) + SetInflation(inflation) + + provisions = inflation * (pool.TotalSupply() / hrsPerYr) + pool.LooseTokens += provisions + + distribution.AddInflation(provisions) + +nextInflation(hrsPerYr rational.Rat): + + bondedRatio = pool.BondedPool / pool.TotalSupply() + + inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange + inflationRateChange = inflationRateChangePerYear / hrsPerYr + + inflation = GetInflation() + inflationRateChange + switch inflation + case > params.InflationMax + return params.InflationMax + case < params.InflationMin + return params.InflationMin + default + return inflation +``` diff --git a/docs/spec/inflation/state.md b/docs/spec/inflation/state.md new file mode 100644 index 0000000000..dea10c0462 --- /dev/null +++ b/docs/spec/inflation/state.md @@ -0,0 +1,21 @@ +## State + +### Inflation + - key: `0x00` + - value: `amino(Inflation)` + +The current annual inflation rate. + +```golang +type Inflation sdk.Rat +``` + +### InflationLastTime + - key: `0x01` + - value: `amino(InflationLastTime)` + +The last unix time which the inflation was processed for. + +```golang +type InflationLastTime int64 +``` diff --git a/docs/spec/provisioning/overview.md b/docs/spec/provisioning/overview.md deleted file mode 100644 index 046223a4b0..0000000000 --- a/docs/spec/provisioning/overview.md +++ /dev/null @@ -1,229 +0,0 @@ -# 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 -``` diff --git a/docs/spec/provisioning/state.md b/docs/spec/provisioning/state.md deleted file mode 100644 index 0711b01aac..0000000000 --- a/docs/spec/provisioning/state.md +++ /dev/null @@ -1,13 +0,0 @@ - - -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` diff --git a/docs/spec/staking/README.md b/docs/spec/staking/README.md index 30dbf1dd31..16ce96a05d 100644 --- a/docs/spec/staking/README.md +++ b/docs/spec/staking/README.md @@ -20,22 +20,19 @@ 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. Params - 1. Pool - 2. Validators - 3. Delegations - 2. **[Transactions](transactions.md)** - 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 - 3. Automatic Unbonding -3. **[Future improvements](future_improvements.md)** +1. **[State](state.md)** + 1. Params + 1. Pool + 2. Validators + 3. Delegations +2. **[Transactions](transactions.md)** + 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 + 3. Automatic Unbonding diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md index 61643f5266..b40b06b92c 100644 --- a/docs/spec/staking/end_block.md +++ b/docs/spec/staking/end_block.md @@ -1,10 +1,6 @@ # 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 +## 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 @@ -21,41 +17,3 @@ EndBlock() ValidatorSetChanges 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 -``` - diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index 76101e6097..376a38ced4 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -2,13 +2,12 @@ ### Pool - - key: `01` - - value: `amino(pool)` - 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, moving Atom inflation information, etc. + - Pool: `0x01 -> amino(pool)` + ```golang type Pool struct { LooseTokens int64 // tokens not associated with any bonded validator @@ -21,12 +20,12 @@ type Pool struct { ``` ### Params - - key: `00` - - value: `amino(params)` Params is global data structure that stores system parameters and defines overall functioning of the stake module. + - Params: `0x00 -> amino(params)` + ```golang type Params struct { InflationRateChange sdk.Rat // maximum annual change in inflation rate @@ -81,16 +80,11 @@ type Validator struct { Description Description // description terms for the validator - // Needed for ordering vals in the bypower key + // Needed for ordering vals in the by-power 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 ? - LastBondedTokens sdk.Rat // last bonded token amount + CommissionInfo CommissionInfo // info about the validator's commission } type CommissionInfo struct { diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index 55b1a8ed12..4f2567958d 100644 --- a/docs/spec/staking/transactions.md +++ b/docs/spec/staking/transactions.md @@ -1,5 +1,4 @@ - -### Transaction Overview +## Transaction Overview In this section we describe the processing of the transactions and the corresponding updates to the state. Transactions: @@ -23,6 +22,8 @@ Other notes: ### TxCreateValidator + - triggers: `distribution.CreateValidatorDistribution` + A validator is created using the `TxCreateValidator` transaction. ```golang @@ -82,7 +83,9 @@ editCandidacy(tx TxEditCandidacy): return ``` -### TxDelegation +### TxDelegate + + - triggers: `distribution.CreateOrModDelegationDistribution` Within this transaction the delegator provides coins, and in return receives some amount of their validator's delegator-shares that are assigned to diff --git a/x/fee_distribution/keeper.go b/x/distribution/keeper.go similarity index 100% rename from x/fee_distribution/keeper.go rename to x/distribution/keeper.go diff --git a/x/fee_distribution/keeper_test.go b/x/distribution/keeper_test.go similarity index 100% rename from x/fee_distribution/keeper_test.go rename to x/distribution/keeper_test.go diff --git a/x/fee_distribution/movement.go b/x/distribution/movement.go similarity index 100% rename from x/fee_distribution/movement.go rename to x/distribution/movement.go diff --git a/x/fee_distribution/types.go b/x/distribution/types.go similarity index 100% rename from x/fee_distribution/types.go rename to x/distribution/types.go