Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: IPRPC pool rewards distribution #1678

Merged
merged 11 commits into from
Oct 9, 2024
22 changes: 14 additions & 8 deletions x/dualstaking/keeper/delegator_reward.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper

import (
"fmt"
"strconv"

"cosmossdk.io/math"
Expand Down Expand Up @@ -158,39 +159,46 @@ func (k Keeper) ClaimRewards(ctx sdk.Context, delegator string, provider string)

// RewardProvidersAndDelegators is the main function handling provider rewards with delegations
// it returns the provider reward amount and updates the delegatorReward map with the reward portion for each delegator
// it also returns the "claimable reward amount" which is the leftover rewards due to int division or errors
func (k Keeper) RewardProvidersAndDelegators(ctx sdk.Context, provider string, chainID string, totalReward sdk.Coins, senderModule string, calcOnlyProvider bool, calcOnlyDelegators bool, calcOnlyContributor bool) (providerReward sdk.Coins, claimableRewards sdk.Coins, err error) {
block := uint64(ctx.BlockHeight())
zeroCoins := sdk.NewCoins()
claimableRewards = totalReward
epoch, _, err := k.epochstorageKeeper.GetEpochStartForBlock(ctx, block)
if err != nil {
return zeroCoins, zeroCoins, utils.LavaFormatError(types.ErrCalculatingProviderReward.Error(), err,
return zeroCoins, claimableRewards, utils.LavaFormatError(types.ErrCalculatingProviderReward.Error(), err,
utils.Attribute{Key: "block", Value: block},
)
}
stakeEntry, found := k.epochstorageKeeper.GetStakeEntry(ctx, epoch, chainID, provider)
if !found {
return zeroCoins, zeroCoins, err
return zeroCoins, claimableRewards, utils.LavaFormatWarning("RewardProvidersAndDelegators: cannot send rewards to provider and delegators", fmt.Errorf("provider stake entry not found, probably unstaked"),
utils.LogAttr("epoch", epoch),
utils.LogAttr("provider", provider),
utils.LogAttr("chain_id", chainID),
utils.LogAttr("sender_module", senderModule),
utils.LogAttr("reward", totalReward.String()),
)
}

delegations, err := k.GetProviderDelegators(ctx, provider, epoch)
if err != nil {
return zeroCoins, zeroCoins, utils.LavaFormatError("cannot get provider's delegators", err)
return zeroCoins, claimableRewards, utils.LavaFormatError("cannot get provider's delegators", err)
}
claimableRewards = totalReward
// make sure this is post boost when rewards pool is introduced
contributorAddresses, contributorPart := k.specKeeper.GetContributorReward(ctx, chainID)
contributorsNum := sdk.NewInt(int64(len(contributorAddresses)))
if !contributorsNum.IsZero() && contributorPart.GT(math.LegacyZeroDec()) {
contributorReward := totalReward.MulInt(contributorPart.MulInt64(spectypes.ContributorPrecision).RoundInt()).QuoInt(sdk.NewInt(spectypes.ContributorPrecision))
// make sure to round it down for the integers division
contributorReward = contributorReward.QuoInt(contributorsNum).MulInt(contributorsNum)
claimableRewards = totalReward.Sub(contributorReward...)
if !calcOnlyContributor {
err = k.PayContributors(ctx, senderModule, contributorAddresses, contributorReward, chainID)
if err != nil {
return zeroCoins, zeroCoins, err
return zeroCoins, claimableRewards, err
}
}
claimableRewards = claimableRewards.Sub(contributorReward...)
omerlavanet marked this conversation as resolved.
Show resolved Hide resolved
}

relevantDelegations := lavaslices.Filter(delegations,
Expand All @@ -217,11 +225,9 @@ func (k Keeper) updateDelegatorsReward(ctx sdk.Context, totalDelegations math.In

for _, delegation := range delegations {
delegatorReward := k.CalcDelegatorReward(ctx, delegatorsReward, totalDelegations, delegation)

if !calcOnly {
k.rewardDelegator(ctx, delegation, delegatorReward, senderModule)
}

usedDelegatorRewards = usedDelegatorRewards.Add(delegatorReward...)
}

Expand Down
25 changes: 25 additions & 0 deletions x/epochstorage/keeper/stake_entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,31 @@ func (k Keeper) GetStakeEntry(ctx sdk.Context, epoch uint64, chainID string, pro
return entry, true
}

// HasStakeEntry checks if a specific stake entry exists in the stake entries KV store
func (k Keeper) HasStakeEntry(ctx sdk.Context, epoch uint64, chainID string, provider string) bool {
pk, err := k.stakeEntries.Indexes.Index.MatchExact(ctx, collections.Join3(epoch, chainID, provider))
if err != nil {
utils.LavaFormatWarning("HasStakeEntry: MatchExact with ref key failed", err,
utils.LogAttr("epoch", epoch),
utils.LogAttr("chain_id", chainID),
utils.LogAttr("provider", provider),
)
return false
}

found, err := k.stakeEntries.Has(ctx, pk)
if err != nil {
utils.LavaFormatError("HasStakeEntry: Has with primary key failed", err,
utils.LogAttr("epoch", epoch),
utils.LogAttr("chain_id", chainID),
utils.LogAttr("provider", provider),
)
return false
}

return found
}

// Set stake entry
func (k Keeper) SetStakeEntry(ctx sdk.Context, epoch uint64, stakeEntry types.StakeEntry) {
stake := uint64(0)
Expand Down
27 changes: 24 additions & 3 deletions x/rewards/keeper/iprpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ func (k Keeper) distributeIprpcRewards(ctx sdk.Context, iprpcReward types.IprpcR
k.handleNoIprpcRewardToProviders(ctx, iprpcReward.SpecFunds)
return
}

leftovers := sdk.NewCoins()
for _, specFund := range iprpcReward.SpecFunds {
details := map[string]string{}
Expand All @@ -148,6 +147,26 @@ func (k Keeper) distributeIprpcRewards(ctx sdk.Context, iprpcReward types.IprpcR
continue
}

// remove providers that are registered in the spec fund but unstaked (can't reward unstaked providers)
epoch, _, err := k.epochstorage.GetEpochStartForBlock(ctx, uint64(ctx.BlockHeight()))
Yaroms marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
// should never happen, print an error and return
utils.LavaFormatError("cannot distribute iprpc rewards", err,
utils.LogAttr("block", ctx.BlockHeight()),
)
}
stakedProvidersCu := []types.ProviderCuType{}
for _, providerCu := range specCu.ProvidersCu {
if k.epochstorage.HasStakeEntry(ctx, epoch, specFund.Spec, providerCu.Provider) {
// provider is found, add to the stakedProvidersCu list
stakedProvidersCu = append(stakedProvidersCu, providerCu)
} else {
// provider is not found, don't add to the stakedProvidersCu list and subtract its CU from the spec's
// total CU
specCu.TotalCu -= providerCu.CU
}
}

// tax the rewards to the community and validators
fundAfterTax := sdk.NewCoins()
for _, coin := range specFund.Fund {
Expand All @@ -162,7 +181,7 @@ func (k Keeper) distributeIprpcRewards(ctx sdk.Context, iprpcReward types.IprpcR

UsedReward := sdk.NewCoins()
// distribute IPRPC reward for spec
for _, providerCU := range specCu.ProvidersCu {
for _, providerCU := range stakedProvidersCu {
if specCu.TotalCu == 0 {
// spec was not serviced by any provider, continue
continue
Expand All @@ -178,9 +197,11 @@ func (k Keeper) distributeIprpcRewards(ctx sdk.Context, iprpcReward types.IprpcR
UsedReward = UsedRewardTemp

// reward the provider
_, _, err := k.dualstakingKeeper.RewardProvidersAndDelegators(ctx, providerCU.Provider, specFund.Spec, providerIprpcReward, string(types.IprpcPoolName), false, false, false)
_, claimableRewards, err := k.dualstakingKeeper.RewardProvidersAndDelegators(ctx, providerCU.Provider, specFund.Spec, providerIprpcReward, string(types.IprpcPoolName), false, false, false)
if err != nil {
// failed sending the rewards, add the claimable rewards to the leftovers that will be transferred to the community pool
utils.LavaFormatError("failed to send iprpc rewards to provider", err, utils.LogAttr("provider", providerCU))
leftovers = leftovers.Add(claimableRewards...)
omerlavanet marked this conversation as resolved.
Show resolved Hide resolved
}
details[providerCU.Provider] = fmt.Sprintf("cu: %d reward: %s", providerCU.CU, providerIprpcReward.String())
}
Expand Down
2 changes: 2 additions & 0 deletions x/rewards/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type TimerStoreKeeper interface {
type EpochstorageKeeper interface {
GetStakeEntryCurrent(ctx sdk.Context, chainID string, address string) (epochstoragetypes.StakeEntry, bool)
GetAllStakeEntriesCurrentForChainId(ctx sdk.Context, chainID string) []epochstoragetypes.StakeEntry
GetEpochStartForBlock(ctx sdk.Context, block uint64) (epochStart, blockInEpoch uint64, err error)
HasStakeEntry(ctx sdk.Context, epoch uint64, chainID string, provider string) bool
}

type DowntimeKeeper interface {
Expand Down
Loading