对 Atom 增发收益收取佣金,同时又允许 Atom 增发自动绑定(直接分配到验证人的已绑定质押),这在 BPoS 中会带来问题。从根本上说,这两种机制是互斥的。如果同时将佣金机制和自动绑定机制应用到质押代币上,那么任意验证人与其委托人之间的质押代币分配都会随着每个区块而变化。这样就需要对每条委托记录在每个区块都进行一次计算,而这被认为计算成本过高。总之,我们只能在“对 Atom 收取佣金且增发的 atom 不绑定”和“增发的 atom 直接绑定但不收取 Atom 佣金”之间二选一,而我们选择实现前者。希望将其增发收益重新绑定的利益相关者,可以选择编写脚本,定期提取并重新绑定奖励。
在权益证明(PoS)区块链中,由交易手续费产生的奖励会支付给验证人。手续费分配模块会将这些奖励公平地分配给验证人的各个委托人。奖励按 period 计算。每当验证人的委托发生变化时,例如验证人收到新的委托,period 就会更新。随后,单个验证人的奖励可以通过“委托开始前所在 period 的总奖励减去当前总奖励”来计算。更多信息参见 F1 手续费分配论文。支付给验证人的佣金会在验证人被移除或验证人请求提取时发放。佣金会在每次 BeginBlock 操作时计算并累加,以更新累计的手续费金额。分配给委托人的奖励会在委托发生变更、被移除或请求提取时发放。在分配奖励之前,会先应用该委托期间验证人发生的所有罚没。
在 F1 手续费分配中,委托人获得的奖励会在其委托被提取时计算。该计算需要读取求和中的各项,这些项来自委托时结束的那个 period,以及为此次提取创建的最终 period,并基于各 period 中“奖励除以代币份额”的结果进行计算。此外,由于罚没会改变一笔委托所拥有的代币数量(但我们对此采用惰性计算,只在委托人解除委托时计算),因此对于委托发生与奖励提取之间出现的任何罚没,我们必须分别计算罚没前后各 period 的奖励。因此,罚没和委托一样,都会引用由该罚没事件结束的 period。任何不再被任何委托或任何罚没引用的历史奖励记录都可以安全删除,因为它们永远不会再被读取(未来的委托和未来的罚没总会引用未来的 periods)。这一点通过为每条历史奖励存储项跟踪一个 ReferenceCount 来实现。每当创建一个可能需要引用历史记录的新对象(委托或罚没)时,引用计数就会递增。每当删除一个此前需要引用历史记录的对象时,引用计数就会递减。如果引用计数降为零,对应的历史记录就会被删除。
// ExternalCommunityPoolKeeper is the interface that an external community pool module keeper must fulfill// for x/distribution to properly accept it as a community pool fund destination.type ExternalCommunityPoolKeeper interface { // GetCommunityPoolModule gets the module name that funds should be sent to for the community pool. // This is the address that x/distribution will send funds to for external management. GetCommunityPoolModule()string // FundCommunityPool allows an account to directly fund the community fund pool. FundCommunityPool(ctx sdk.Context, amount sdk.Coins, senderAddr sdk.AccAddress)error // DistributeFromCommunityPool distributes funds from the community pool module account to // a receiver address. DistributeFromCommunityPool(ctx sdk.Context, amount sdk.Coins, receiveAddr sdk.AccAddress)error}
默认情况下,distribution 模块会使用内部实现的 community pool。也可以为该模块提供一个外部 community pool,使资金改为流向它而不是内部实现。Cosmos SDK 维护的参考外部 community pool 实现是 x/protocolpool。
// ExternalCommunityPoolKeeper is the interface that an external community pool module keeper must fulfill// for x/distribution to properly accept it as a community pool fund destination.type ExternalCommunityPoolKeeper interface { // GetCommunityPoolModule gets the module name that funds should be sent to for the community pool. // This is the address that x/distribution will send funds to for external management. GetCommunityPoolModule()string // FundCommunityPool allows an account to directly fund the community fund pool. FundCommunityPool(ctx sdk.Context, amount sdk.Coins, senderAddr sdk.AccAddress)error // DistributeFromCommunityPool distributes funds from the community pool module account to // a receiver address. DistributeFromCommunityPool(ctx sdk.Context, amount sdk.Coins, receiveAddr sdk.AccAddress)error}
在这个分配示例中,底层共识引擎会按照区块提议者权重相对于全部已绑定权重的比例来选择区块提议者。所有验证者在将预提交包含进其提议区块方面的表现都相同。于是令 (pre_commits included) / (total bonded validator power) 保持不变,这样验证者的摊销区块奖励就是总奖励中的 ( validator power / total bonded power) * (1 - community tax rate)。因此,单个委托者获得的奖励为:
(delegator proportion of the validator power / validator power) * (validator power / total bonded power) * (1 - community tax rate) * (1 - validator commission rate)= (delegator proportion of the validator power / total bonded power) * (1 -community tax rate) * (1 - validator commission rate)
func (k Keeper)SetWithdrawAddr(ctx context.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress)error if k.blockedAddrs[withdrawAddr.String()] { fail with "`{ withdrawAddr}` is not allowed to receive external funds"} if !k.GetWithdrawAddrEnabled(ctx) { fail with `ErrSetWithdrawAddrDisabled`}k.SetDelegatorWithdrawAddr(ctx, delegatorAddr, withdrawAddr)
// initialize starting info for a new delegationfunc (k Keeper)initializeDelegation(ctx context.Context, val sdk.ValAddress, del sdk.AccAddress) { // period has already been incremented - we want to store the period ended by this delegation action previousPeriod := k.GetValidatorCurrentRewards(ctx, val).Period - 1 // increment reference count for the period we're going to track k.incrementReferenceCount(ctx, val, previousPeriod) validator := k.stakingKeeper.Validator(ctx, val) delegation := k.stakingKeeper.Delegation(ctx, del, val) // calculate delegation stake in tokens // we don't store directly, so multiply delegation shares * (tokens per share) // note: necessary to truncate so we don't allow withdrawing more rewards than owed stake := validator.TokensFromSharesTruncated(delegation.GetShares())k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight())))}
This simple distribution mechanism describes a functional way to passively
distribute rewards between validators and delegators. Note that this mechanism does
not distribute funds in as precisely as active reward distribution mechanisms and
will therefore be upgraded in the future.The mechanism operates as follows. Collected rewards are pooled globally and
divided out passively to validators and delegators. Each validator has the
opportunity to charge commission to the delegators on the rewards collected on
behalf of the delegators. Fees are collected directly into a global reward pool
and validator proposer-reward pool. Due to the nature of passive accounting,
whenever changes to parameters which affect the rate of reward distribution
occurs, withdrawal of rewards must also occur.
Whenever withdrawing, one must withdraw the maximum amount they are entitled
to, leaving nothing in the pool.
Whenever bonding, unbonding, or re-delegating tokens to an existing account, a
full withdrawal of the rewards must occur (as the rules for lazy accounting
change).
Whenever a validator chooses to change the commission on rewards, all accumulated
commission rewards must be simultaneously withdrawn.
The above scenarios are covered in hooks.md.The distribution mechanism outlined herein is used to lazily distribute the
following rewards between validators and associated delegators:
multi-token fees to be socially distributed
inflated staked asset provisions
validator commission on all rewards earned by their delegators stake
Fees are pooled within a global pool. The mechanisms used allow for validators
and delegators to independently and lazily withdraw their rewards.
As a part of the lazy computations, each delegator holds an accumulation term
specific to each validator which is used to estimate what their approximate
fair portion of tokens held in the global fee pool is owed to them.
Under the circumstance that there was constant and equal flow of incoming
reward tokens every block, this distribution mechanism would be equal to the
active distribution (distribute individually to all delegators each block).
However, this is unrealistic so deviations from the active distribution will
occur based on fluctuations of incoming reward tokens as well as timing of
reward withdrawal by other delegators.If you happen to know that incoming rewards are about to significantly increase,
you are incentivized to not withdraw until after this event, increasing the
worth of your existing accum. See #2764
for further details.
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 BPoS. Fundamentally, these two mechanisms are mutually
exclusive. If both commission and auto-bonding mechanisms are simultaneously
applied to the staking-token then the distribution of staking-tokens between
any validator and its delegators will change with each block. This then
necessitates a calculation for each delegation records for each block -
which is considered computationally expensive.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 rewards.
In Proof of Stake (PoS) blockchains, rewards gained from transaction fees are paid to validators. The fee distribution module fairly distributes the rewards to the validators’ constituent delegators.Rewards are calculated per period. The period is updated each time a validator’s delegation changes, for example, when the validator receives a new delegation.
The rewards for a single validator can then be calculated by taking the total rewards for the period before the delegation started, minus the current total rewards.
To learn more, see the F1 Fee Distribution paper.The commission to the validator is paid when the validator is removed or when the validator requests a withdrawal.
The commission is calculated and incremented at every BeginBlock operation to update accumulated fee amounts.The rewards to a delegator are distributed when the delegation is changed or removed, or a withdrawal is requested.
Before rewards are distributed, all slashes to the validator that occurred during the current delegation are applied.
In F1 fee distribution, the rewards a delegator receives are calculated when their delegation is withdrawn. This calculation must read the terms of the summation of rewards divided by the share of tokens from the period which they ended when they delegated, and the final period that was created for the withdrawal.Additionally, as slashes change the amount of tokens a delegation will have (but we calculate this lazily,
only when a delegator un-delegates), we must calculate rewards in separate periods before / after any slashes
which occurred in between when a delegator delegated and when they withdrew their rewards. Thus slashes, like
delegations, reference the period which was ended by the slash event.All stored historical rewards records for periods which are no longer referenced by any delegations
or any slashes can thus be safely removed, as they will never be read (future delegations and future
slashes will always reference future periods). This is implemented by tracking a ReferenceCount
along with each historical reward storage entry. Each time a new object (delegation or slash)
is created which might need to reference the historical record, the reference count is incremented.
Each time one object which previously needed to reference the historical record is deleted, the reference
count is decremented. If the reference count hits zero, the historical record is deleted.
// ExternalCommunityPoolKeeper is the interface that an external community pool module keeper must fulfill// for x/distribution to properly accept it as a community pool fund destination.type ExternalCommunityPoolKeeper interface { // GetCommunityPoolModule gets the module name that funds should be sent to for the community pool. // This is the address that x/distribution will send funds to for external management. GetCommunityPoolModule()string // FundCommunityPool allows an account to directly fund the community fund pool. FundCommunityPool(ctx sdk.Context, amount sdk.Coins, senderAddr sdk.AccAddress)error // DistributeFromCommunityPool distributes funds from the community pool module account to // a receiver address. DistributeFromCommunityPool(ctx sdk.Context, amount sdk.Coins, receiveAddr sdk.AccAddress)error}
By default, the distribution module will use a community pool implementation that is internal. An external community pool
can be provided to the module which will have funds be diverted to it instead of the internal implementation. The reference
external community pool maintained by the Cosmos SDK is x/protocolpool.
All globally tracked parameters for distribution are stored within
FeePool. 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.
Each delegation distribution only needs to record the height at which it last
withdrew fees. Because a delegation must withdraw fees each time it’s
properties change (aka bonded tokens etc.) its properties will remain constant
and the delegator’s accumulation factor can be calculated passively knowing
only the height of the last withdrawal and its current properties.
At each BeginBlock, all fees received in the previous block are transferred to
the distribution ModuleAccount account. When a delegator or validator
withdraws their rewards, they are taken out of the ModuleAccount. During begin
block, the different claims on the fees collected are updated as follows:
The reserve community tax is charged.
The remainder is distributed proportionally by voting power to all bonded validators
See params for description of parameters.Let fees be the total fees collected in the previous block, including
inflationary rewards to the stake. All fees are collected in a specific module
account during the block. During BeginBlock, they are sent to the
"distribution"ModuleAccount. No other sending of tokens occurs. Instead, the
rewards each account is entitled to are stored, and withdrawals can be triggered
through the messages FundCommunityPool, WithdrawValidatorCommission and
WithdrawDelegatorReward.
The community pool gets community_tax * fees, plus any remaining dust after
validators get their rewards that are always rounded down to the nearest
integer value.
Starting with Cosmos SDK v0.53.0, an external community pool, such as x/protocolpool, can be used in place of the x/distribution managed community pool.Please view the warning in the next section before deciding to use an external community pool.
// ExternalCommunityPoolKeeper is the interface that an external community pool module keeper must fulfill// for x/distribution to properly accept it as a community pool fund destination.type ExternalCommunityPoolKeeper interface { // GetCommunityPoolModule gets the module name that funds should be sent to for the community pool. // This is the address that x/distribution will send funds to for external management. GetCommunityPoolModule()string // FundCommunityPool allows an account to directly fund the community fund pool. FundCommunityPool(ctx sdk.Context, amount sdk.Coins, senderAddr sdk.AccAddress)error // DistributeFromCommunityPool distributes funds from the community pool module account to // a receiver address. DistributeFromCommunityPool(ctx sdk.Context, amount sdk.Coins, receiveAddr sdk.AccAddress)error}
The proposer receives no extra rewards. All fees are distributed among all the
bonded validators, including the proposer, in proportion to their consensus power.
powFrac = validator power / total bonded validator powervoteMul = 1 - community_tax
Each validator’s rewards are distributed to its delegators. The validator also
has a self-delegation that is treated like a regular delegation in
distribution calculations.The validator sets a commission rate. The commission rate is flexible, but each
validator sets a maximum rate and a maximum daily increase. These maximums cannot be exceeded and protect delegators from sudden increases of validator commission rates to prevent validators from taking all of the rewards.The outstanding rewards that the operator is entitled to are stored in
ValidatorAccumulatedCommission, while the rewards the delegators are entitled
to are stored in ValidatorCurrentRewards. The F1 fee distribution scheme is used to calculate the rewards per delegator as they
withdraw or update their delegation, and is thus not handled in BeginBlock.
For this example distribution, the underlying consensus engine selects block proposers in
proportion to their power relative to the entire bonded power.All validators are equally performant at including pre-commits in their proposed
blocks. Then hold (pre_commits included) / (total bonded validator power)
constant so that the amortized block reward for the validator is ( validator power / total bonded power) * (1 - community tax rate) of
the total rewards. Consequently, the reward for a single delegator is:
(delegator proportion of the validator power / validator power) * (validator power / total bonded power) * (1 - community tax rate) * (1 - validator commission rate)= (delegator proportion of the validator power / total bonded power) * (1 -community tax rate) * (1 - validator commission rate)
By default, the withdraw address is the delegator address. To change its withdraw address, a delegator must send a MsgSetWithdrawAddress message.
Changing the withdraw address is possible only if the parameter WithdrawAddrEnabled is set to true.The withdraw address cannot be any of the module accounts. These accounts are blocked from being withdraw addresses by being added to the distribution keeper’s blockedAddrs array at initialization.Response:
func (k Keeper)SetWithdrawAddr(ctx context.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress)error if k.blockedAddrs[withdrawAddr.String()] { fail with "`{ withdrawAddr}` is not allowed to receive external funds"} if !k.GetWithdrawAddrEnabled(ctx) { fail with `ErrSetWithdrawAddrDisabled`}k.SetDelegatorWithdrawAddr(ctx, delegatorAddr, withdrawAddr)
A delegator can withdraw its rewards.
Internally in the distribution module, this transaction simultaneously removes the previous delegation with associated rewards, the same as if the delegator simply started a new delegation of the same value.
The rewards are sent immediately from the distribution ModuleAccount to the withdraw address.
Any remainder (truncated decimals) are sent to the community pool.
The starting height of the delegation is set to the current validator period, and the reference count for the previous period is decremented.
The amount withdrawn is deducted from the ValidatorOutstandingRewards variable for the validator.In the F1 distribution, the total rewards are calculated per validator period, and a delegator receives a piece of those rewards in proportion to their stake in the validator.
In basic F1, the total rewards that all the delegators are entitled to between to periods is calculated the following way.
Let R(X) be the total accumulated rewards up to period X divided by the tokens staked at that time. The delegator allocation is R(X) * delegator_stake.
Then the rewards for all the delegators for staking between periods A and B are (R(B) - R(A)) * total stake.
However, these calculated rewards don’t account for slashing.Taking the slashes into account requires iteration.
Let F(X) be the fraction a validator is to be slashed for a slashing event that happened at period X.
If the validator was slashed at periods P1, ..., PN, where A < P1, PN < B, the distribution module calculates the individual delegator’s rewards, T(A, B), as follows:
stake := initial stake rewards := 0 previous := A for P in P1, ..., PN`: rewards = (R(P) - previous) * stake stake = stake * F(P)previous = Prewards = rewards + (R(B) - R(PN)) * stake
The historical rewards are calculated retroactively by playing back all the slashes and then attenuating the delegator’s stake at each step.
The final calculated stake is equivalent to the actual staked coins in the delegation with a margin of error due to rounding errors.Response:
The validator can send the WithdrawValidatorCommission message to withdraw their accumulated commission.
The commission is calculated in every block during BeginBlock, so no iteration is required to withdraw.
The amount withdrawn is deducted from the ValidatorOutstandingRewards variable for the validator.
Only integer amounts can be sent. If the accumulated awards have decimals, the amount is truncated before the withdrawal is sent, and the remainder is left to be withdrawn later.
This handler will return an error if an ExternalCommunityPool is used.
This message sends coins directly from the sender to the community pool.The transaction fails if the amount cannot be transferred from the sender to the distribution module account.
Each time a delegation is changed, the rewards are withdrawn and the delegation is reinitialized.
Initializing a delegation increments the validator period and keeps track of the starting period of the delegation.
// initialize starting info for a new delegationfunc (k Keeper)initializeDelegation(ctx context.Context, val sdk.ValAddress, del sdk.AccAddress) { // period has already been incremented - we want to store the period ended by this delegation action previousPeriod := k.GetValidatorCurrentRewards(ctx, val).Period - 1 // increment reference count for the period we're going to track k.incrementReferenceCount(ctx, val, previousPeriod) validator := k.stakingKeeper.Validator(ctx, val) delegation := k.stakingKeeper.Delegation(ctx, del, val) // calculate delegation stake in tokens // we don't store directly, so multiply delegation shares * (tokens per share) // note: necessary to truncate so we don't allow withdrawing more rewards than owed stake := validator.TokensFromSharesTruncated(delegation.GetShares())k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight())))}
Distribution module params can be updated through MsgUpdateParams, which can be done using governance proposal and the signer will always be gov module account address.
The starting height of the delegation is set to the previous period.
Because of the Before-hook, this period is the last period for which the delegator was rewarded.
Outstanding commission is sent to the validator’s self-delegation withdrawal address.
Remaining delegator rewards get sent to the community fee pool.Note: The validator gets removed only when it has no remaining delegations.
At that time, all outstanding delegator rewards will have been withdrawn.
Any remaining rewards are dust amounts.
The distribution module contains the following parameters:
Key
Type
Example
communitytax
string (dec)
“0.020000000000000000” [0]
withdrawaddrenabled
bool
true
[0] communitytax must be positive and cannot exceed 1.00.
baseproposerreward and bonusproposerreward were parameters that are deprecated in v0.47 and are not used.
The reserve pool is the pool of collected funds for use by governance taken via the CommunityTax.
Currently with the Cosmos SDK, tokens collected by the CommunityTax are accounted for but unspendable.
The rewards command allows users to query delegator rewards. Users can optionally include the validator address to query rewards earned from a specific validator.
simd query distribution rewards [delegator-addr] [validator-addr] [flags]
The tx commands allow users to interact with the distribution module.
simd tx distribution --help
fund-community-pool
The fund-community-pool command allows users to send funds to the community pool.
simd tx distribution fund-community-pool [amount] [flags]
Example:
simd tx distribution fund-community-pool 100stake --from cosmos1...
set-withdraw-addr
The set-withdraw-addr command allows users to set the withdraw address for rewards associated with a delegator address.
simd tx distribution set-withdraw-addr [withdraw-addr] [flags]
Example:
simd tx distribution set-withdraw-addr cosmos1... --from cosmos1...
withdraw-all-rewards
The withdraw-all-rewards command allows users to withdraw all rewards for a delegator.
simd tx distribution withdraw-all-rewards [flags]
Example:
simd tx distribution withdraw-all-rewards --from cosmos1...
withdraw-rewards
The withdraw-rewards command allows users to withdraw all rewards from a given delegation address,
and optionally withdraw validator commission if the delegation address given is a validator operator and the user proves the --commission flag.
simd tx distribution withdraw-rewards [validator-addr] [flags]
Example:
simd tx distribution withdraw-rewards cosmosvaloper1... --from cosmos1... --commission
The ValidatorHistoricalRewards endpoint allows users to query historical rewards for a validator at a specific period. This is useful for debugging reward calculations by inspecting internal distribution state.Example:
The DelegatorStartingInfo endpoint allows users to query the starting info for a delegator on a given validator. Combined with ValidatorHistoricalRewards, this enables verification of reward calculations by retrieving the previous period and stake, then looking up cumulative reward ratios for that period.Example: