cosmos-sdk/docs/_attic/WIP-lamborghini-distribution/transactions.md

15 KiB

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.

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.

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.

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 operator 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)