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

chore!: only distribute rewards to validators that have been validating a consumer chain for some backport #1929 #1983

Merged
merged 9 commits into from
Jun 25, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Only start distributing rewards to validators after they have been validating
for a fixed number of blocks. Introduces the `NumberOfEpochsToStartReceivingRewards` param.
([\#1929](https://github.com/cosmos/interchain-security/pull/1929))
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Only start distributing rewards to validators after they have been validating
for a fixed number of blocks. Introduces the `NumberOfEpochsToStartReceivingRewards` param.
([\#1929](https://github.com/cosmos/interchain-security/pull/1929))
11 changes: 10 additions & 1 deletion docs/docs/validators/withdraw_rewards.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@
sidebar_position: 3
---

# Withdrawing consumer chain validator rewards
# Consumer chain validator rewards

:::warning
A validator can only receive rewards from a consumer chain if the validator has been validating the consumer chain
for some time. Specifically, the validator has to be a consumer validator of the consumer chain for at least
`NumberOfEpochsToStartReceivingRewards * BlocksPerEpoch` blocks (run `interchain-security-pd query provider params` for
the actual values of the `NumberOfEpochsToStartReceivingRewards` and `BlocksPerEpoch` params).
:::


## Withdrawing rewards
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve heading formatting

Ensure that headings are surrounded by blank lines to comply with markdown best practices and enhance readability.

+ 
## Withdrawing rewards
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Withdrawing rewards
## Withdrawing rewards
Tools
Markdownlint

15-15: Expected: 1; Actual: 0; Below (MD022, blanks-around-headings)
Headings should be surrounded by blank lines

Here are example steps for withdrawing rewards from consumer chains in the provider chain

:::info
Expand Down
8 changes: 8 additions & 0 deletions proto/interchain_security/ccv/provider/v1/provider.proto
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ message Params {

// The number of blocks that comprise an epoch.
int64 blocks_per_epoch = 10;

// The number of epochs a validator has to validate a consumer chain in order to start receiving rewards from that chain.
int64 number_of_epochs_to_start_receiving_rewards = 11;
}

// SlashAcks contains cons addresses of consumer chain validators
Expand Down Expand Up @@ -368,6 +371,11 @@ message ConsumerValidator {
int64 power = 2;
// public key the validator uses on the consumer chain during this epoch
tendermint.crypto.PublicKey consumer_public_key = 3;
// height the validator had when it FIRST became a consumer validator
// If a validator becomes a consumer validator at height `H` and is continuously a consumer validator for all the upcoming
// epochs, then the height of the validator SHOULD remain `H`. This height only resets to a different height if a validator
// stops being a consumer validator during an epoch and later becomes again a consumer validator.
int64 join_height = 4;
}
// ConsumerRewardsAllocation stores the rewards allocated by a consumer chain
// to the consumer rewards pool. It is used to allocate the tokens to the consumer
Expand Down
147 changes: 137 additions & 10 deletions tests/integration/distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ func (s *CCVTestSuite) TestRewardsDistribution() {
}
consuValsRewards := consumerValsOutstandingRewardsFunc(s.providerCtx())

// increase the block height so validators are eligible for consumer rewards (see `IsEligibleForConsumerRewards`)
sainoe marked this conversation as resolved.
Show resolved Hide resolved
numberOfBlocksToStartReceivingRewards :=
providerKeeper.GetNumberOfEpochsToStartReceivingRewards(s.providerCtx()) * providerKeeper.GetBlocksPerEpoch(s.providerCtx())

for s.providerCtx().BlockHeight() <= numberOfBlocksToStartReceivingRewards {
s.providerChain.NextBlock()
}

// Transfer rewards from consumer to provider and distribute rewards to
// validators and community pool by calling BeginBlockRD
relayAllCommittedPackets(
Expand Down Expand Up @@ -711,9 +719,14 @@ func (s *CCVTestSuite) TestAllocateTokens() {

totalRewards := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(100))}

// increase the block height so validators are eligible for consumer rewards (see `IsEligibleForConsumerRewards`)
numberOfBlocksToStartReceivingRewards := providerKeeper.GetNumberOfEpochsToStartReceivingRewards(
s.providerCtx()) * providerKeeper.GetBlocksPerEpoch(s.providerCtx())
providerCtx := s.providerCtx().WithBlockHeight(numberOfBlocksToStartReceivingRewards + s.providerCtx().BlockHeight())

// fund consumer rewards pool
bankKeeper.SendCoinsFromAccountToModule(
s.providerCtx(),
providerCtx,
s.providerChain.SenderAccount.GetAddress(),
providertypes.ConsumerRewardsPool,
totalRewards,
Expand All @@ -724,7 +737,7 @@ func (s *CCVTestSuite) TestAllocateTokens() {
for chainID := range s.consumerBundles {
// update consumer allocation
providerKeeper.SetConsumerRewardsAllocation(
s.providerCtx(),
providerCtx,
chainID,
providertypes.ConsumerRewardsAllocation{
Rewards: sdk.NewDecCoinsFromCoins(rewardsPerChain...),
Expand All @@ -735,7 +748,7 @@ func (s *CCVTestSuite) TestAllocateTokens() {
// iterate over the validators and verify that no validator has outstanding rewards
totalValsRewards := sdk.DecCoins{}
for _, val := range s.providerChain.Vals.Validators {
valRewards, err := distributionKeeper.GetValidatorOutstandingRewards(s.providerCtx(), sdk.ValAddress(val.Address))
valRewards, err := distributionKeeper.GetValidatorOutstandingRewards(providerCtx, sdk.ValAddress(val.Address))
s.Require().NoError(err)
totalValsRewards = totalValsRewards.Add(valRewards.Rewards...)
}
Expand All @@ -745,17 +758,17 @@ func (s *CCVTestSuite) TestAllocateTokens() {
// At this point the distribution module account
// only holds the community pool's tokens
// since there are no validators with outstanding rewards
lastCommPool := getDistrAcctBalFn(s.providerCtx())
lastCommPool := getDistrAcctBalFn(providerCtx)

// execute BeginBlock to trigger the token allocation
providerKeeper.BeginBlockRD(s.providerCtx())
providerKeeper.BeginBlockRD(providerCtx)

valNum := len(s.providerChain.Vals.Validators)
consNum := len(s.consumerBundles)

// compute the expected validators token allocation by subtracting the community tax
rewardsPerChainDec := sdk.NewDecCoinsFromCoins(rewardsPerChain...)
communityTax, err := distributionKeeper.GetCommunityTax(s.providerCtx())
communityTax, err := distributionKeeper.GetCommunityTax(providerCtx)
s.Require().NoError(err)

rewardsPerChainTrunc, _ := rewardsPerChainDec.
Expand All @@ -767,7 +780,7 @@ func (s *CCVTestSuite) TestAllocateTokens() {
// verify the validator tokens allocation
// note that the validators have the same voting power to keep things simple
for _, val := range s.providerChain.Vals.Validators {
valRewards, err := distributionKeeper.GetValidatorOutstandingRewards(s.providerCtx(), sdk.ValAddress(val.Address))
valRewards, err := distributionKeeper.GetValidatorOutstandingRewards(providerCtx, sdk.ValAddress(val.Address))
s.Require().NoError(err)

s.Require().Equal(
Expand All @@ -779,13 +792,13 @@ func (s *CCVTestSuite) TestAllocateTokens() {
// check that the total expected rewards are transferred to the distribution module account

// store the decimal remainders in the consumer reward allocations
allocRemainderPerChain := providerKeeper.GetConsumerRewardsAllocation(s.providerCtx(), s.consumerChain.ChainID).Rewards
allocRemainderPerChain := providerKeeper.GetConsumerRewardsAllocation(providerCtx, s.consumerChain.ChainID).Rewards

// compute the total rewards distributed to the distribution module balance (validator outstanding rewards + community pool tax),
totalRewardsDistributed := sdk.NewDecCoinsFromCoins(totalRewards...).Sub(allocRemainderPerChain.MulDec(math.LegacyNewDec(int64(consNum))))

// compare the expected total rewards against the distribution module balance
s.Require().Equal(lastCommPool.Add(totalRewardsDistributed...), getDistrAcctBalFn(s.providerCtx()))
s.Require().Equal(lastCommPool.Add(totalRewardsDistributed...), getDistrAcctBalFn(providerCtx))
}

// getEscrowBalance gets the current balances in the escrow account holding the transferred tokens to the provider
Expand Down Expand Up @@ -820,7 +833,7 @@ func (s *CCVTestSuite) prepareRewardDist() {
s.coordinator.CommitNBlocks(s.consumerChain, uint64(blocksToGo))
}

func (s *CCVTestSuite) TestAllocateTokensToValidator() {
func (s *CCVTestSuite) TestAllocateTokensToConsumerValidators() {
providerKeeper := s.providerApp.GetProviderKeeper()
distributionKeeper := s.providerApp.GetTestDistributionKeeper()
bankKeeper := s.providerApp.GetTestBankKeeper()
Expand Down Expand Up @@ -866,6 +879,10 @@ func (s *CCVTestSuite) TestAllocateTokensToValidator() {
s.Run(tc.name, func() {
ctx, _ := s.providerCtx().CacheContext()

// increase the block height so validators are eligible for consumer rewards (see `IsEligibleForConsumerRewards`)
ctx = ctx.WithBlockHeight(providerKeeper.GetNumberOfEpochsToStartReceivingRewards(ctx)*providerKeeper.GetBlocksPerEpoch(ctx) +
ctx.BlockHeight())

Comment on lines +882 to +885
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimize block height adjustment logic

The logic for increasing the block height to make validators eligible for consumer rewards is repetitive across different test cases. Consider refactoring this into a utility function to avoid code duplication and improve maintainability.

- ctx = ctx.WithBlockHeight(providerKeeper.GetNumberOfEpochsToStartReceivingRewards(ctx)*providerKeeper.GetBlocksPerEpoch(ctx) + ctx.BlockHeight())
+ ctx = increaseBlockHeightForRewards(ctx, providerKeeper)

And add a new function:

func increaseBlockHeightForRewards(ctx sdk.Context, keeper ProviderKeeper) sdk.Context {
    numberOfBlocks := keeper.GetNumberOfEpochsToStartReceivingRewards(ctx) * keeper.GetBlocksPerEpoch(ctx)
    return ctx.WithBlockHeight(numberOfBlocks + ctx.BlockHeight())
}

// change the consumer valset
consuVals := providerKeeper.GetConsumerValSet(ctx, chainID)
providerKeeper.DeleteConsumerValSet(ctx, chainID)
Expand Down Expand Up @@ -948,6 +965,116 @@ func (s *CCVTestSuite) TestAllocateTokensToValidator() {
}
}

// TestAllocateTokensToConsumerValidatorsWithDifferentValidatorHeights tests `AllocateTokensToConsumerValidators` with
// consumer validators that have different heights. Specifically, test that validators that have been consumer validators
// for some time receive rewards, while validators that recently became consumer validators do not receive rewards.
func (s *CCVTestSuite) TestAllocateTokensToConsumerValidatorsWithDifferentValidatorHeights() {
// Note this test is an adaptation of a `TestAllocateTokensToConsumerValidators` testcase.
providerKeeper := s.providerApp.GetProviderKeeper()
distributionKeeper := s.providerApp.GetTestDistributionKeeper()
bankKeeper := s.providerApp.GetTestBankKeeper()

chainID := s.consumerChain.ChainID

tokens := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, math.LegacyNewDecFromIntWithPrec(math.NewInt(999), 2))}
rate := math.LegacyOneDec()
expAllocated := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, math.LegacyNewDecFromIntWithPrec(math.NewInt(999), 2))}

ctx, _ := s.providerCtx().CacheContext()
// If the provider chain has not yet reached `GetNumberOfEpochsToStartReceivingRewards * GetBlocksPerEpoch` block height,
// then all validators receive rewards (see `IsEligibleForConsumerRewards`). In this test, we want to check whether
// validators receive rewards or not based on how long they have been consumer validators. Because of this, we increase the block height.
ctx = ctx.WithBlockHeight(providerKeeper.GetNumberOfEpochsToStartReceivingRewards(ctx)*providerKeeper.GetBlocksPerEpoch(ctx) + 1)

// update the consumer validators
consuVals := providerKeeper.GetConsumerValSet(ctx, chainID)
// first 2 validators were consumer validators since block height 1 and hence get rewards
consuVals[0].JoinHeight = 1
consuVals[1].JoinHeight = 1
// last 2 validators were consumer validators since block height 2 and hence do not get rewards because they
// have not been consumer validators for `GetNumberOfEpochsToStartReceivingRewards * GetBlocksPerEpoch` blocks
consuVals[2].JoinHeight = 2
consuVals[3].JoinHeight = 2
providerKeeper.SetConsumerValSet(ctx, chainID, consuVals)

providerKeeper.DeleteConsumerValSet(ctx, chainID)
providerKeeper.SetConsumerValSet(ctx, chainID, consuVals)
consuVals = providerKeeper.GetConsumerValSet(ctx, chainID)

// set the same consumer commission rate for all consumer validators
for _, v := range consuVals {
provAddr := providertypes.NewProviderConsAddress(sdk.ConsAddress(v.ProviderConsAddr))
err := providerKeeper.SetConsumerCommissionRate(
ctx,
chainID,
provAddr,
rate,
)
s.Require().NoError(err)
}

// allocate tokens
res := providerKeeper.AllocateTokensToConsumerValidators(
ctx,
chainID,
tokens,
)

// check that the expected result is returned
s.Require().Equal(expAllocated, res)

// rewards are expected to be allocated evenly between validators 3 and 4
rewardsPerVal := expAllocated.QuoDec(math.LegacyNewDec(int64(2)))

// assert that the rewards are allocated to the first 2 validators
for _, v := range consuVals[0:2] {
valAddr := sdk.ValAddress(v.ProviderConsAddr)
rewards, err := s.providerApp.GetTestDistributionKeeper().GetValidatorOutstandingRewards(
ctx,
valAddr,
)
s.Require().NoError(err)
s.Require().Equal(rewardsPerVal, rewards.Rewards)

// send rewards to the distribution module
valRewardsTrunc, _ := rewards.Rewards.TruncateDecimal()
err = bankKeeper.SendCoinsFromAccountToModule(
ctx,
s.providerChain.SenderAccount.GetAddress(),
distrtypes.ModuleName,
valRewardsTrunc)
s.Require().NoError(err)

// check that validators can withdraw their rewards
withdrawnCoins, err := distributionKeeper.WithdrawValidatorCommission(
ctx,
valAddr,
)
s.Require().NoError(err)

// check that the withdrawn coins is equal to the entire reward amount
// times the set consumer commission rate
commission := rewards.Rewards.MulDec(rate)
c, _ := commission.TruncateDecimal()
s.Require().Equal(withdrawnCoins, c)

// check that validators get rewards in their balance
s.Require().Equal(withdrawnCoins, bankKeeper.GetAllBalances(ctx, sdk.AccAddress(valAddr)))
}

// assert that no rewards are allocated to the last 2 validators because they have not been consumer validators
// for at least `GetNumberOfEpochsToStartReceivingRewards * GetBlocksPerEpoch` blocks
for _, v := range consuVals[2:4] {
valAddr := sdk.ValAddress(v.ProviderConsAddr)
rewards, err := s.providerApp.GetTestDistributionKeeper().GetValidatorOutstandingRewards(
ctx,
valAddr,
)
s.Require().NoError(err)
s.Require().Zero(rewards.Rewards)
}
}

// TestMultiConsumerRewardsDistribution tests the rewards distribution of multiple consumers chains
func (s *CCVTestSuite) TestMultiConsumerRewardsDistribution() {
s.SetupAllCCVChannels()
Expand Down
24 changes: 24 additions & 0 deletions x/ccv/provider/keeper/distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func (k Keeper) AllocateTokens(ctx sdk.Context) {
continue
}

// if rewardsCollected.IsZero() {
// continue
// }

// temporary workaround to keep CanWithdrawInvariant happy
// general discussions here: https://github.com/cosmos/cosmos-sdk/issues/2906#issuecomment-441867634
if k.ComputeConsumerTotalVotingPower(ctx, consumerChainID) == 0 {
Expand Down Expand Up @@ -163,6 +167,15 @@ func (k Keeper) AllocateTokens(ctx sdk.Context) {
}
}

// IsEligibleForConsumerRewards returns `true` if the validator with `consumerValidatorHeight` has been a consumer
// validator for a long period of time and hence is eligible to receive rewards, and false otherwise
func (k Keeper) IsEligibleForConsumerRewards(ctx sdk.Context, consumerValidatorHeight int64) bool {
numberOfBlocksToStartReceivingRewards := k.GetNumberOfEpochsToStartReceivingRewards(ctx) * k.GetBlocksPerEpoch(ctx)

// a validator is eligible for rewards if it has been a consumer validator for `NumberOfEpochsToStartReceivingRewards` epochs
return (ctx.BlockHeight() - consumerValidatorHeight) >= numberOfBlocksToStartReceivingRewards
}

// AllocateTokensToConsumerValidators allocates tokens
// to the given consumer chain's validator set
func (k Keeper) AllocateTokensToConsumerValidators(
Expand All @@ -183,6 +196,11 @@ func (k Keeper) AllocateTokensToConsumerValidators(

// Allocate tokens by iterating over the consumer validators
for _, consumerVal := range k.GetConsumerValSet(ctx, chainID) {
// if a validator is not eligible, this means that the other eligible validators would get more rewards
if !k.IsEligibleForConsumerRewards(ctx, consumerVal.JoinHeight) {
continue
}
Comment on lines +199 to +202
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimize the reward allocation loop by checking eligibility before other operations.

In the AllocateTokensToConsumerValidators function, the eligibility check for each validator (IsEligibleForConsumerRewards) is performed within the loop after other operations have started. Consider moving this check to the beginning of the loop to avoid unnecessary computations for ineligible validators.

for _, consumerVal := range k.GetConsumerValSet(ctx, chainID) {
+   if !k.IsEligibleForConsumerRewards(ctx, consumerVal.JoinHeight) {
+       continue
+   }
    consAddr := sdk.ConsAddress(consumerVal.ProviderConsAddr)
    // other operations...
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// if a validator is not eligible, this means that the other eligible validators would get more rewards
if !k.IsEligibleForConsumerRewards(ctx, consumerVal.JoinHeight) {
continue
}
for _, consumerVal := range k.GetConsumerValSet(ctx, chainID) {
if !k.IsEligibleForConsumerRewards(ctx, consumerVal.JoinHeight) {
continue
}
consAddr := sdk.ConsAddress(consumerVal.ProviderConsAddr)
// other operations...
}


consAddr := sdk.ConsAddress(consumerVal.ProviderConsAddr)

// get the validator tokens fraction using its voting power
Expand Down Expand Up @@ -253,6 +271,12 @@ func (k Keeper) GetConsumerRewardsPool(ctx sdk.Context) sdk.Coins {
func (k Keeper) ComputeConsumerTotalVotingPower(ctx sdk.Context, chainID string) (totalPower int64) {
// sum the consumer validators set voting powers
for _, v := range k.GetConsumerValSet(ctx, chainID) {

// only consider the voting power of a validator that would receive rewards (i.e., validator has been validating for a number of blocks)
if !k.IsEligibleForConsumerRewards(ctx, v.JoinHeight) {
continue
}

Comment on lines +276 to +279
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor the voting power computation to improve efficiency.

In the ComputeConsumerTotalVotingPower function, the eligibility check for each validator is done inside the loop. This can be optimized by filtering the validators list first to include only those eligible for rewards, thereby reducing the number of iterations.

- for _, v := range k.GetConsumerValSet(ctx, chainID) {
-     if !k.IsEligibleForConsumerRewards(ctx, v.JoinHeight) {
-         continue
-     }
-     totalPower += v.Power
- }
+ eligibleValidators := filterEligibleValidators(k.GetConsumerValSet(ctx, chainID), ctx)
+ for _, v := eligibleValidators {
+     totalPower += v.Power
+ }

Committable suggestion was skipped due to low confidence.

totalPower += v.Power
}

Expand Down
26 changes: 26 additions & 0 deletions x/ccv/provider/keeper/distribution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ func TestComputeConsumerTotalVotingPower(t *testing.T) {
keeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t))
defer ctrl.Finish()

// `ComputeConsumerTotalVotingPower` used in this test retrieves the blocks per epoch, so we need to set this param
params := providertypes.DefaultParams()
params.BlocksPerEpoch = 1
keeper.SetParams(ctx, params)

// increase the block height so validators are eligible for consumer rewards (see `IsEligibleForConsumerRewards`)
ctx = ctx.WithBlockHeight(params.NumberOfEpochsToStartReceivingRewards * params.BlocksPerEpoch)

createVal := func(power int64) tmtypes.Validator {
signer := tmtypes.NewMockPV()
val := tmtypes.NewValidator(signer.PrivKey.PubKey(), power)
Expand Down Expand Up @@ -271,3 +279,21 @@ func TestGetConsumerRewardsAllocationNil(t *testing.T) {
}
require.Equal(t, expectedRewardAllocation, alloc)
}

func TestIsEligibleForConsumerRewards(t *testing.T) {
keeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t))
defer ctrl.Finish()

params := providertypes.DefaultParams()
params.NumberOfEpochsToStartReceivingRewards = 10
params.BlocksPerEpoch = 5
keeper.SetParams(ctx, params)

numberOfBlocks := params.NumberOfEpochsToStartReceivingRewards * params.BlocksPerEpoch

require.False(t, keeper.IsEligibleForConsumerRewards(ctx.WithBlockHeight(numberOfBlocks-1), 0))
require.True(t, keeper.IsEligibleForConsumerRewards(ctx.WithBlockHeight(numberOfBlocks), 0))
require.True(t, keeper.IsEligibleForConsumerRewards(ctx.WithBlockHeight(numberOfBlocks+1), 0))
require.True(t, keeper.IsEligibleForConsumerRewards(ctx.WithBlockHeight(numberOfBlocks+1), 1))
require.False(t, keeper.IsEligibleForConsumerRewards(ctx.WithBlockHeight(numberOfBlocks+1), 2))
}
7 changes: 7 additions & 0 deletions x/ccv/provider/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ func (k Keeper) GetBlocksPerEpoch(ctx sdk.Context) int64 {
return params.BlocksPerEpoch
}

// GetNumberOfEpochsToStartReceivingRewards returns the number of epochs needed by a validator to continuously validate
// to start receiving rewards
func (k Keeper) GetNumberOfEpochsToStartReceivingRewards(ctx sdk.Context) int64 {
params := k.GetParams(ctx)
return params.NumberOfEpochsToStartReceivingRewards
}

// GetParams returns the paramset for the provider module
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
store := ctx.KVStore(k.storeKey)
Expand Down
1 change: 1 addition & 0 deletions x/ccv/provider/keeper/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func TestParams(t *testing.T) {
Amount: math.NewInt(10000000),
},
600,
24,
)
providerKeeper.SetParams(ctx, newParams)
params = providerKeeper.GetParams(ctx)
Expand Down
Loading
Loading