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.
The distribution module stores it’s params in state with the prefix of 0x09,
it can be updated with governance or the address with authority.
Params: 0x09 | ProtocolBuffer(Params)
Copy
Ask AI
// Params defines the set of params for the distribution module.message Params { option (amino.name) = "cosmos-sdk/x/distribution/Params"; option (gogoproto.goproto_stringer) = false; string community_tax = 1 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; // Deprecated: The base_proposer_reward field is deprecated and is no longer used // in the x/distribution module's reward mechanism. string base_proposer_reward = 2 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false, deprecated = true ]; // Deprecated: The bonus_proposer_reward field is deprecated and is no longer used // in the x/distribution module's reward mechanism. string bonus_proposer_reward = 3 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false, deprecated = true ]; bool withdraw_addr_enabled = 4;}
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.
Copy
Ask AI
/ 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.
Copy
Ask AI
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:
Copy
Ask AI
(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:
Copy
Ask AI
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:
Copy
Ask AI
// MsgWithdrawDelegatorReward represents delegation withdrawal to a delegator// from a single validator.message MsgWithdrawDelegatorReward { option (cosmos.msg.v1.signer) = "delegator_address"; option (amino.name) = "cosmos-sdk/MsgWithdrawDelegationReward"; option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; string delegator_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; string validator_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];}
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.
Copy
Ask AI
/ 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.
Copy
Ask AI
// MsgUpdateParams is the Msg/UpdateParams request type.//// Since: cosmos-sdk 0.47message MsgUpdateParams { option (cosmos.msg.v1.signer) = "authority"; option (amino.name) = "cosmos-sdk/distribution/MsgUpdateParams"; // authority is the address that controls the module (defaults to x/gov unless overwritten). string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // params defines the x/distribution parameters to update. // // NOTE: All parameters must be supplied. Params params = 2 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];}
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.
Copy
Ask AI
simd query distribution rewards [delegator-addr] [validator-addr] [flags]