Skip to content

Commit

Permalink
fix!: enable the distribution of ICS rewards from Stride (#2288)
Browse files Browse the repository at this point in the history
* enable Stride to distribute ICS rewards

* fix bug in migration

* assign to same consumerId

* add changelog entries
  • Loading branch information
mpoke committed Sep 20, 2024
1 parent 47b786a commit 18ba837
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 18 deletions.
2 changes: 2 additions & 0 deletions .changelog/unreleased/bug-fixes/2288-rewards-stride.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- `[x/provider]` Add patch to enable ICS rewards from Stride to be distributed.
([\#2288](https://github.com/cosmos/interchain-security/pull/2288))
2 changes: 2 additions & 0 deletions .changelog/unreleased/state-breaking/2288-rewards-stride.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- `[x/provider]` Add patch to enable ICS rewards from Stride to be distributed.
([\#2288](https://github.com/cosmos/interchain-security/pull/2288))
76 changes: 75 additions & 1 deletion x/ccv/provider/ibc_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ func (im IBCMiddleware) OnRecvPacket(
packet channeltypes.Packet,
relayer sdk.AccAddress,
) exported.Acknowledgement {
logger := im.keeper.Logger(ctx)

// executes the IBC transfer OnRecv logic
ack := im.app.OnRecvPacket(ctx, packet, relayer)

Expand All @@ -124,7 +126,29 @@ func (im IBCMiddleware) OnRecvPacket(
// execute the middleware logic only if the sender is a consumer chain
consumerId, err := im.keeper.IdentifyConsumerIdFromIBCPacket(ctx, packet)
if err != nil {
return ack
// Check if the packet is received on a canonical transfer channels
// of one of the known consumer chains.
// Note: this is a patch for the Cosmos Hub for consumers such as Stride
// TODO: remove once the known consumer chains upgrade to send ICS rewards
// with the consumer ID added to the memo field
if ctx.ChainID() == "cosmoshub-4" && // this patch is only for the Cosmos Hub
packet.DestinationChannel == "channel-391" { // canonical transfer channel Stride <> Cosmos Hub
// check source chain ID
srcChainId, err := im.keeper.GetSourceChainIdFromIBCPacket(ctx, packet)
if err != nil || srcChainId != "stride-1" {
// ignore packet if it's not from Stride
return ack
}
// accept the packet as a potential ICS reward
consumerId = "1" // consumer ID of Stride
// sanity check: make sure this is the consumer ID for Stride
chainId, err := im.keeper.GetConsumerChainId(ctx, consumerId)
if err != nil || srcChainId != chainId {
return ack
}
} else {
return ack
}
}

// extract the coin info received from the packet data
Expand All @@ -137,9 +161,41 @@ func (im IBCMiddleware) OnRecvPacket(
return ack
}

chainId, err := im.keeper.GetConsumerChainId(ctx, consumerId)
if err != nil {
logger.Error(
"cannot get consumer chain id in transfer middleware",
"consumerId", consumerId,
"packet", packet.String(),
"fungibleTokenPacketData", data.String(),
"error", err.Error(),
)
return ack
}

coinAmt, _ := math.NewIntFromString(data.Amount)
coinDenom := GetProviderDenom(data.Denom, packet)

logger.Info(
"received ICS rewards from consumer chain",
"consumerId", consumerId,
"chainId", chainId,
"denom", coinDenom,
"amount", data.Amount,
)

// initialize an empty slice to store event attributes
eventAttributes := []sdk.Attribute{}

// add event attributes
eventAttributes = append(eventAttributes, []sdk.Attribute{
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeConsumerId, consumerId),
sdk.NewAttribute(types.AttributeConsumerChainId, chainId),
sdk.NewAttribute(types.AttributeRewardDenom, coinDenom),
sdk.NewAttribute(types.AttributeRewardAmount, data.Amount),
}...)

// verify that the coin's denom is a whitelisted consumer denom,
// and if so, adds it to the consumer chain rewards allocation,
// otherwise the prohibited coin just stays in the pool forever.
Expand All @@ -151,7 +207,25 @@ func (im IBCMiddleware) OnRecvPacket(
Amount: coinAmt,
})...)
im.keeper.SetConsumerRewardsAllocation(ctx, consumerId, alloc)

logger.Info(
"scheduled ICS rewards to be distributed",
"consumerId", consumerId,
"chainId", chainId,
"denom", coinDenom,
"amount", data.Amount,
)

// add RewardDistribution event attribute
eventAttributes = append(eventAttributes, sdk.NewAttribute(types.AttributeRewardDistribution, "scheduled"))
}

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeUpdateConsumer,
eventAttributes...,
),
)
}

return ack
Expand Down
83 changes: 70 additions & 13 deletions x/ccv/provider/keeper/distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ func (k Keeper) AllocateTokens(ctx sdk.Context) {
// To avoid large iterations over all the consumer IDs, iterate only over
// chains with an IBC client created.
for _, consumerId := range k.GetAllConsumersWithIBCClients(ctx) {

// note that it's possible that no rewards are collected even though the
// reward pool isn't empty. This can happen if the reward pool holds some tokens
// of non-whitelisted denominations.
Expand All @@ -88,18 +87,35 @@ func (k Keeper) AllocateTokens(ctx sdk.Context) {
continue
}

chainId, err := k.GetConsumerChainId(ctx, consumerId)
if err != nil {
k.Logger(ctx).Error(
"cannot get consumer chain id in AllocateTokens",
"consumerId", consumerId,
"error", err.Error(),
)
continue
}

// temporary workaround to keep CanWithdrawInvariant happy
// general discussions here: https://github.com/cosmos/cosmos-sdk/issues/2906#issuecomment-441867634
if k.ComputeConsumerTotalVotingPower(ctx, consumerId) == 0 {
rewardsToSend, rewardsChange := alloc.Rewards.TruncateDecimal()
err := k.distributionKeeper.FundCommunityPool(context.Context(ctx), rewardsToSend, k.accountKeeper.GetModuleAccount(ctx, types.ConsumerRewardsPool).GetAddress())
if err != nil {
k.Logger(ctx).Error(
"fail to allocate rewards from consumer chain %s to community pool: %s",
consumerId,
err,
"fail to allocate ICS rewards to community pool",
"consumerId", consumerId,
"chainId", chainId,
"error", err.Error(),
)
}
k.Logger(ctx).Info(
"allocated ICS rewards to community pool",
"consumerId", consumerId,
"chainId", chainId,
"amount", rewardsToSend.String(),
)

// set the consumer allocation to the remaining reward decimals
alloc.Rewards = rewardsChange
Expand All @@ -114,9 +130,10 @@ func (k Keeper) AllocateTokens(ctx sdk.Context) {
communityTax, err := k.distributionKeeper.GetCommunityTax(ctx)
if err != nil {
k.Logger(ctx).Error(
"cannot get community tax while allocating rewards from consumer chain %s: %s",
consumerId,
err,
"cannot get community tax while allocating ICS rewards",
"consumerId", consumerId,
"chainId", chainId,
"error", err.Error(),
)
continue
}
Expand All @@ -134,9 +151,10 @@ func (k Keeper) AllocateTokens(ctx sdk.Context) {
err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ConsumerRewardsPool, distrtypes.ModuleName, validatorsRewardsTrunc)
if err != nil {
k.Logger(ctx).Error(
"cannot send rewards to distribution module account %s: %s",
consumerId,
err,
"cannot send ICS rewards to distribution module account",
"consumerId", consumerId,
"chainId", chainId,
"error", err.Error(),
)
continue
}
Expand All @@ -153,16 +171,38 @@ func (k Keeper) AllocateTokens(ctx sdk.Context) {
err = k.distributionKeeper.FundCommunityPool(context.Context(ctx), remainingRewards, k.accountKeeper.GetModuleAccount(ctx, types.ConsumerRewardsPool).GetAddress())
if err != nil {
k.Logger(ctx).Error(
"fail to allocate rewards from consumer chain %s to community pool: %s",
consumerId,
err,
"fail to allocate ICS rewards to community pool",
"consumerId", consumerId,
"chainId", chainId,
"error", err.Error(),
)
continue
}

// set consumer allocations to the remaining rewards decimals
alloc.Rewards = validatorsRewardsChange.Add(remainingChanges...)
k.SetConsumerRewardsAllocation(ctx, consumerId, alloc)

k.Logger(ctx).Info(
"distributed ICS rewards successfully",
"consumerId", consumerId,
"chainId", chainId,
"total-rewards", consumerRewards.String(),
"sent-to-distribution", validatorsRewardsTrunc.String(),
"sent-to-CP", remainingRewards.String(),
)

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeDistributedRewards,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeConsumerId, consumerId),
sdk.NewAttribute(types.AttributeConsumerChainId, chainId),
sdk.NewAttribute(types.AttributeRewardTotal, consumerRewards.String()),
sdk.NewAttribute(types.AttributeRewardDistributed, validatorsRewardsTrunc.String()),
sdk.NewAttribute(types.AttributeRewardCommunityPool, remainingRewards.String()),
),
)
}
}

Expand Down Expand Up @@ -341,6 +381,23 @@ func (k Keeper) IdentifyConsumerIdFromIBCPacket(ctx sdk.Context, packet channelt
return consumerId, nil
}

// GetSourceChainIdFromIBCPacket returns the chain ID of the chain that sent this packet
func (k Keeper) GetSourceChainIdFromIBCPacket(ctx sdk.Context, packet channeltypes.Packet) (string, error) {
channel, ok := k.channelKeeper.GetChannel(ctx, packet.DestinationPort, packet.DestinationChannel)
if !ok {
return "", errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "channel not found for channel ID: %s", packet.DestinationChannel)
}
if len(channel.ConnectionHops) != 1 {
return "", errorsmod.Wrap(channeltypes.ErrTooManyConnectionHops, "must have direct connection to consumer chain")
}
connectionID := channel.ConnectionHops[0]
_, tmClient, err := k.getUnderlyingClient(ctx, connectionID)
if err != nil {
return "", err
}
return tmClient.ChainId, nil
}

// HandleSetConsumerCommissionRate sets a per-consumer chain commission rate for the given provider address
// on the condition that the given consumer chain exists.
func (k Keeper) HandleSetConsumerCommissionRate(ctx sdk.Context, consumerId string, providerAddr types.ProviderConsAddress, commissionRate math.LegacyDec) error {
Expand Down
6 changes: 3 additions & 3 deletions x/ccv/provider/migrations/v8/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ func MigrateLaunchedConsumerChains(ctx sdk.Context, store storetypes.KVStore, pk

// channelId -> chainId
channelId, found := pk.GetConsumerIdToChannelId(ctx, chainId)
if !found {
return errorsmod.Wrapf(ccv.ErrInvalidConsumerState, "cannot find channel id associated with consumer id: %s", consumerId)
if found {
// if not found, then the CCV channel was not yet established
pk.SetChannelToConsumerId(ctx, channelId, consumerId)
}
pk.SetChannelToConsumerId(ctx, channelId, consumerId)

// chainId -> channelId
rekeyFromChainIdToConsumerId(store, LegacyChainToChannelKeyPrefix, chainId, consumerId)
Expand Down
8 changes: 8 additions & 0 deletions x/ccv/provider/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const (
EventTypeCreateConsumer = "create_consumer"
EventTypeUpdateConsumer = "update_consumer"
EventTypeRemoveConsumer = "remove_consumer"
EventTypeReceivedRewards = "received_ics_rewards"
EventTypeDistributedRewards = "distributed_ics_rewards"

AttributeInfractionHeight = "infraction_height"
AttributeInitialHeight = "initial_height"
Expand All @@ -31,4 +33,10 @@ const (
AttributeConsumerSpawnTime = "consumer_spawn_time"
AttributeConsumerPhase = "consumer_phase"
AttributeConsumerTopN = "consumer_topn"
AttributeRewardDenom = "reward_denom"
AttributeRewardAmount = "reward_amount"
AttributeRewardDistribution = "reward_distribution"
AttributeRewardTotal = "total_rewards"
AttributeRewardDistributed = "distributed_rewards"
AttributeRewardCommunityPool = "community_pool_rewards"
)
1 change: 0 additions & 1 deletion x/ccv/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const (
EventTypeSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour"
EventTypeSubmitConsumerDoubleVoting = "submit_consumer_double_voting"
EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash"
EventTypeFeeDistribution = "fee_distribution"
EventTypeConsumerSlashRequest = "consumer_slash_request"

AttributeKeyAckSuccess = "success"
Expand Down

0 comments on commit 18ba837

Please sign in to comment.