From c01de00d29513a4be319b1561d1981af861e73bd Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Fri, 7 Jun 2024 16:13:15 +0200 Subject: [PATCH] fix!: Replace GetAllConsumerChains with lightweight version (#1946) * add GetAllConsumerChainIDs * replace GetAllConsumerChains with GetAllRegisteredConsumerChainIDs * add changelog entry * move HasToValidate to grpc_query.go as it's used only there * apply review suggestions --- .../provider/1946-get-consumer-chains.md | 3 + tests/mbt/driver/core.go | 31 +++--- tests/mbt/driver/mbt_test.go | 72 ++++++------ x/ccv/provider/keeper/distribution.go | 10 +- x/ccv/provider/keeper/genesis.go | 36 +++--- x/ccv/provider/keeper/grpc_query.go | 103 ++++++++++++++++-- x/ccv/provider/keeper/grpc_query_test.go | 97 ++++++++++++++++- x/ccv/provider/keeper/hooks.go | 6 +- x/ccv/provider/keeper/keeper.go | 92 ++-------------- x/ccv/provider/keeper/keeper_test.go | 91 ++-------------- x/ccv/provider/keeper/relay.go | 31 +++--- x/ccv/provider/migrations/v3/migrations.go | 8 +- x/ccv/provider/migrations/v5/migrations.go | 7 +- 13 files changed, 306 insertions(+), 281 deletions(-) create mode 100644 .changelog/unreleased/bug-fixes/provider/1946-get-consumer-chains.md diff --git a/.changelog/unreleased/bug-fixes/provider/1946-get-consumer-chains.md b/.changelog/unreleased/bug-fixes/provider/1946-get-consumer-chains.md new file mode 100644 index 0000000000..eae373c390 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/provider/1946-get-consumer-chains.md @@ -0,0 +1,3 @@ +- Replace `GetAllConsumerChains` with lightweight version + (`GetAllRegisteredConsumerChainIDs`) that doesn't call into the staking module + ([\#1946](https://github.com/cosmos/interchain-security/pull/1946)) \ No newline at end of file diff --git a/tests/mbt/driver/core.go b/tests/mbt/driver/core.go index 3c985cb6fa..422e282d2d 100644 --- a/tests/mbt/driver/core.go +++ b/tests/mbt/driver/core.go @@ -30,7 +30,6 @@ import ( consumerkeeper "github.com/cosmos/interchain-security/v4/x/ccv/consumer/keeper" consumertypes "github.com/cosmos/interchain-security/v4/x/ccv/consumer/types" providerkeeper "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" - providertypes "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" "github.com/cosmos/interchain-security/v4/x/ccv/types" ) @@ -219,11 +218,7 @@ func (s *Driver) getStateString() string { state.WriteString("\n") state.WriteString("Consumers Chains:\n") - consumerChains := s.providerKeeper().GetAllConsumerChains(s.providerCtx()) - chainIds := make([]string, len(consumerChains)) - for i, consumerChain := range consumerChains { - chainIds[i] = consumerChain.ChainId - } + chainIds := s.providerKeeper().GetAllRegisteredConsumerChainIDs(s.providerCtx()) state.WriteString(strings.Join(chainIds, ", ")) state.WriteString("\n\n") @@ -261,11 +256,11 @@ func (s *Driver) getChainStateString(chain ChainId) string { if !s.isProviderChain(chain) { // Check whether the chain is in the consumer chains on the provider - consumerChains := s.providerKeeper().GetAllConsumerChains(s.providerCtx()) + consumerChainIDs := s.providerKeeper().GetAllRegisteredConsumerChainIDs(s.providerCtx()) found := false - for _, consumerChain := range consumerChains { - if consumerChain.ChainId == string(chain) { + for _, consumerChainID := range consumerChainIDs { + if consumerChainID == string(chain) { found = true } } @@ -369,16 +364,16 @@ func (s *Driver) endAndBeginBlock(chain ChainId, timeAdvancement time.Duration) return header } -func (s *Driver) runningConsumers() []providertypes.Chain { - consumersOnProvider := s.providerKeeper().GetAllConsumerChains(s.providerCtx()) +func (s *Driver) runningConsumerChainIDs() []ChainId { + consumerIDsOnProvider := s.providerKeeper().GetAllRegisteredConsumerChainIDs(s.providerCtx()) - consumersWithIntactChannel := make([]providertypes.Chain, 0) - for _, consumer := range consumersOnProvider { - if s.path(ChainId(consumer.ChainId)).Path.EndpointA.GetChannel().State == channeltypes.CLOSED || - s.path(ChainId(consumer.ChainId)).Path.EndpointB.GetChannel().State == channeltypes.CLOSED { + consumersWithIntactChannel := make([]ChainId, 0) + for _, consumerChainID := range consumerIDsOnProvider { + if s.path(ChainId(consumerChainID)).Path.EndpointA.GetChannel().State == channeltypes.CLOSED || + s.path(ChainId(consumerChainID)).Path.EndpointB.GetChannel().State == channeltypes.CLOSED { continue } - consumersWithIntactChannel = append(consumersWithIntactChannel, consumer) + consumersWithIntactChannel = append(consumersWithIntactChannel, ChainId(consumerChainID)) } return consumersWithIntactChannel } @@ -447,8 +442,8 @@ func (s *Driver) RequestSlash( // DeliverAcks delivers, for each path, // all possible acks (up to math.MaxInt many per path). func (s *Driver) DeliverAcks() { - for _, chain := range s.runningConsumers() { - path := s.path(ChainId(chain.ChainId)) + for _, chainID := range s.runningConsumerChainIDs() { + path := s.path(chainID) path.DeliverAcks(path.Path.EndpointA.Chain.ChainID, math.MaxInt) path.DeliverAcks(path.Path.EndpointB.Chain.ChainID, math.MaxInt) } diff --git a/tests/mbt/driver/mbt_test.go b/tests/mbt/driver/mbt_test.go index 9a82bd7b9b..2acbd4a8f4 100644 --- a/tests/mbt/driver/mbt_test.go +++ b/tests/mbt/driver/mbt_test.go @@ -304,21 +304,21 @@ func RunItfTrace(t *testing.T, path string) { // needs a header of height H+1 to accept the packet // so, we do two blocks, one with a very small increment, // and then another to increment the rest of the time - runningConsumersBefore := driver.runningConsumers() + runningConsumerChainIDsBefore := driver.runningConsumerChainIDs() driver.endAndBeginBlock("provider", 1*time.Nanosecond) - for _, consumer := range driver.runningConsumers() { - UpdateProviderClientOnConsumer(t, driver, consumer.ChainId) + for _, consumerChainID := range driver.runningConsumerChainIDs() { + UpdateProviderClientOnConsumer(t, driver, string(consumerChainID)) } driver.endAndBeginBlock("provider", time.Duration(timeAdvancement)*time.Second-1*time.Nanosecond) - runningConsumersAfter := driver.runningConsumers() + runningConsumerChainIDsAfter := driver.runningConsumerChainIDs() // the consumers that were running before but not after must have timed out - for _, consumer := range runningConsumersBefore { + for _, consumerChainID := range runningConsumerChainIDsBefore { found := false - for _, consumerAfter := range runningConsumersAfter { - if consumerAfter.ChainId == consumer.ChainId { + for _, consumerChainIDAfter := range runningConsumerChainIDsAfter { + if consumerChainIDAfter == consumerChainID { found = true break } @@ -332,8 +332,8 @@ func RunItfTrace(t *testing.T, path string) { // because setting up chains will modify timestamps // when the coordinator is starting chains lastTimestamps := make(map[ChainId]time.Time, len(consumers)) - for _, consumer := range driver.runningConsumers() { - lastTimestamps[ChainId(consumer.ChainId)] = driver.runningTime(ChainId(consumer.ChainId)) + for _, consumerChainID := range driver.runningConsumerChainIDs() { + lastTimestamps[consumerChainID] = driver.runningTime(consumerChainID) } driver.coordinator.CurrentTime = driver.runningTime("provider") @@ -364,12 +364,12 @@ func RunItfTrace(t *testing.T, path string) { // for all connected consumers, update the clients... // unless it was the last consumer to be started, in which case it already has the header // as we called driver.setupConsumer - for _, consumer := range driver.runningConsumers() { - if len(consumersToStart) > 0 && consumer.ChainId == consumersToStart[len(consumersToStart)-1].Value.(string) { + for _, consumerChainID := range driver.runningConsumerChainIDs() { + if len(consumersToStart) > 0 && string(consumerChainID) == consumersToStart[len(consumersToStart)-1].Value.(string) { continue } - UpdateProviderClientOnConsumer(t, driver, consumer.ChainId) + UpdateProviderClientOnConsumer(t, driver, string(consumerChainID)) } case "EndAndBeginBlockForConsumer": @@ -490,33 +490,33 @@ func RunItfTrace(t *testing.T, path string) { t.Logf("Comparing model state to actual state...") // compare the running consumers - modelRunningConsumers := RunningConsumers(currentModelState) + modelRunningConsumerChainIDs := RunningConsumers(currentModelState) - systemRunningConsumers := driver.runningConsumers() - actualRunningConsumers := make([]string, len(systemRunningConsumers)) - for i, chain := range systemRunningConsumers { - actualRunningConsumers[i] = chain.ChainId + systemRunningConsumerChainIDs := driver.runningConsumerChainIDs() + actualRunningConsumerChainIDs := make([]string, len(systemRunningConsumerChainIDs)) + for i, chainID := range systemRunningConsumerChainIDs { + actualRunningConsumerChainIDs[i] = string(chainID) } // sort the slices so that we can compare them - sort.Slice(modelRunningConsumers, func(i, j int) bool { - return modelRunningConsumers[i] < modelRunningConsumers[j] + sort.Slice(modelRunningConsumerChainIDs, func(i, j int) bool { + return modelRunningConsumerChainIDs[i] < modelRunningConsumerChainIDs[j] }) - sort.Slice(actualRunningConsumers, func(i, j int) bool { - return actualRunningConsumers[i] < actualRunningConsumers[j] + sort.Slice(actualRunningConsumerChainIDs, func(i, j int) bool { + return actualRunningConsumerChainIDs[i] < actualRunningConsumerChainIDs[j] }) - require.Equal(t, modelRunningConsumers, actualRunningConsumers, "Running consumers do not match") + require.Equal(t, modelRunningConsumerChainIDs, actualRunningConsumerChainIDs, "Running consumers do not match") // check validator sets - provider current validator set should be the one from the staking keeper - CompareValidatorSets(t, driver, currentModelState, actualRunningConsumers, realAddrsToModelConsAddrs) + CompareValidatorSets(t, driver, currentModelState, actualRunningConsumerChainIDs, realAddrsToModelConsAddrs) // check times - sanity check that the block times match the ones from the model - CompareTimes(driver, actualRunningConsumers, currentModelState, timeOffset) + CompareTimes(driver, actualRunningConsumerChainIDs, currentModelState, timeOffset) // check sent packets: we check that the package queues in the model and the system have the same length. - for _, consumer := range actualRunningConsumers { - ComparePacketQueues(t, driver, currentModelState, consumer, timeOffset) + for _, consumerChainID := range actualRunningConsumerChainIDs { + ComparePacketQueues(t, driver, currentModelState, consumerChainID, timeOffset) } // compare that the sent packets on the proider match the model CompareSentPacketsOnProvider(driver, currentModelState, timeOffset) @@ -526,8 +526,8 @@ func RunItfTrace(t *testing.T, path string) { CompareJailedValidators(driver, currentModelState, timeOffset, addressMap) // for all newly sent vsc packets, figure out which vsc id in the model they correspond to - for _, consumer := range actualRunningConsumers { - actualPackets := driver.packetQueue(PROVIDER, ChainId(consumer)) + for _, consumerChainID := range actualRunningConsumerChainIDs { + actualPackets := driver.packetQueue(PROVIDER, ChainId(consumerChainID)) actualNewPackets := make([]types.ValidatorSetChangePacketData, 0) for _, packet := range actualPackets { @@ -543,7 +543,7 @@ func RunItfTrace(t *testing.T, path string) { actualNewPackets = append(actualNewPackets, packetData) } - modelPackets := PacketQueue(currentModelState, PROVIDER, consumer) + modelPackets := PacketQueue(currentModelState, PROVIDER, consumerChainID) newModelVscIds := make([]uint64, 0) for _, packet := range modelPackets { modelVscId := uint64(packet.Value.(itf.MapExprType)["value"].Value.(itf.MapExprType)["id"].Value.(int64)) @@ -781,15 +781,15 @@ func CompareValSet(modelValSet map[string]itf.Expr, systemValSet map[string]int6 } func CompareSentPacketsOnProvider(driver *Driver, currentModelState map[string]itf.Expr, timeOffset time.Time) { - for _, consumer := range driver.runningConsumers() { - vscSendTimestamps := driver.providerKeeper().GetAllVscSendTimestamps(driver.providerCtx(), consumer.ChainId) + for _, consumerChainID := range driver.runningConsumerChainIDs() { + vscSendTimestamps := driver.providerKeeper().GetAllVscSendTimestamps(driver.providerCtx(), string(consumerChainID)) actualVscSendTimestamps := make([]time.Time, 0) for _, vscSendTimestamp := range vscSendTimestamps { actualVscSendTimestamps = append(actualVscSendTimestamps, vscSendTimestamp.Timestamp) } - modelVscSendTimestamps := VscSendTimestamps(currentModelState, consumer.ChainId) + modelVscSendTimestamps := VscSendTimestamps(currentModelState, string(consumerChainID)) for i, modelVscSendTimestamp := range modelVscSendTimestamps { actualTimeWithOffset := actualVscSendTimestamps[i].Unix() - timeOffset.Unix() @@ -798,7 +798,7 @@ func CompareSentPacketsOnProvider(driver *Driver, currentModelState map[string]i modelVscSendTimestamp, actualTimeWithOffset, "Vsc send timestamps do not match for consumer %v", - consumer.ChainId, + consumerChainID, ) } } @@ -852,9 +852,9 @@ func (s *Stats) EnterStats(driver *Driver) { // max number of in-flight packets inFlightPackets := 0 - for _, consumer := range driver.runningConsumers() { - inFlightPackets += len(driver.packetQueue(PROVIDER, ChainId(consumer.ChainId))) - inFlightPackets += len(driver.packetQueue(ChainId(consumer.ChainId), PROVIDER)) + for _, consumerChainID := range driver.runningConsumerChainIDs() { + inFlightPackets += len(driver.packetQueue(PROVIDER, consumerChainID)) + inFlightPackets += len(driver.packetQueue(consumerChainID, PROVIDER)) } if inFlightPackets > s.maxNumInFlightPackets { s.maxNumInFlightPackets = inFlightPackets diff --git a/x/ccv/provider/keeper/distribution.go b/x/ccv/provider/keeper/distribution.go index 06ee0c0464..9d1b6eb759 100644 --- a/x/ccv/provider/keeper/distribution.go +++ b/x/ccv/provider/keeper/distribution.go @@ -76,14 +76,14 @@ func (k Keeper) AllocateTokens(ctx sdk.Context) { } // Iterate over all registered consumer chains - for _, consumer := range k.GetAllConsumerChains(ctx) { + for _, consumerChainID := range k.GetAllRegisteredConsumerChainIDs(ctx) { // transfer the consumer rewards to the distribution module account // note that the rewards transferred are only consumer whitelisted denoms - rewardsCollected, err := k.TransferConsumerRewardsToDistributionModule(ctx, consumer.ChainId) + rewardsCollected, err := k.TransferConsumerRewardsToDistributionModule(ctx, consumerChainID) if err != nil { k.Logger(ctx).Error( "fail to transfer rewards to distribution module for chain %s: %s", - consumer.ChainId, + consumerChainID, err, ) continue @@ -101,7 +101,7 @@ func (k Keeper) AllocateTokens(ctx sdk.Context) { // temporary workaround to keep CanWithdrawInvariant happy // general discussions here: https://github.com/cosmos/cosmos-sdk/issues/2906#issuecomment-441867634 feePool := k.distributionKeeper.GetFeePool(ctx) - if k.ComputeConsumerTotalVotingPower(ctx, consumer.ChainId) == 0 { + if k.ComputeConsumerTotalVotingPower(ctx, consumerChainID) == 0 { feePool.CommunityPool = feePool.CommunityPool.Add(rewardsCollectedDec...) k.distributionKeeper.SetFeePool(ctx, feePool) return @@ -116,7 +116,7 @@ func (k Keeper) AllocateTokens(ctx sdk.Context) { // allocate tokens to consumer validators feeAllocated := k.AllocateTokensToConsumerValidators( ctx, - consumer.ChainId, + consumerChainID, feeMultiplier, ) remaining = remaining.Sub(feeAllocated) diff --git a/x/ccv/provider/keeper/genesis.go b/x/ccv/provider/keeper/genesis.go index 2075ff48ae..cc8bdc41cd 100644 --- a/x/ccv/provider/keeper/genesis.go +++ b/x/ccv/provider/keeper/genesis.go @@ -108,47 +108,51 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) { // ExportGenesis returns the CCV provider module's exported genesis func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { // get a list of all registered consumer chains - registeredChains := k.GetAllConsumerChains(ctx) + registeredChainIDs := k.GetAllRegisteredConsumerChainIDs(ctx) var exportedVscSendTimestamps []types.ExportedVscSendTimestamp // export states for each consumer chains var consumerStates []types.ConsumerState - for _, chain := range registeredChains { - gen, found := k.GetConsumerGenesis(ctx, chain.ChainId) + for _, chainID := range registeredChainIDs { + // no need for the second return value of GetConsumerClientId + // as GetAllRegisteredConsumerChainIDs already iterated through + // the entire prefix range + clientID, _ := k.GetConsumerClientId(ctx, chainID) + gen, found := k.GetConsumerGenesis(ctx, chainID) if !found { - panic(fmt.Errorf("cannot find genesis for consumer chain %s with client %s", chain.ChainId, chain.ClientId)) + panic(fmt.Errorf("cannot find genesis for consumer chain %s with client %s", chainID, clientID)) } // initial consumer chain states cs := types.ConsumerState{ - ChainId: chain.ChainId, - ClientId: chain.ClientId, + ChainId: chainID, + ClientId: clientID, ConsumerGenesis: gen, - UnbondingOpsIndex: k.GetAllUnbondingOpIndexes(ctx, chain.ChainId), + UnbondingOpsIndex: k.GetAllUnbondingOpIndexes(ctx, chainID), } // try to find channel id for the current consumer chain - channelId, found := k.GetChainToChannel(ctx, chain.ChainId) + channelId, found := k.GetChainToChannel(ctx, chainID) if found { cs.ChannelId = channelId - cs.InitialHeight, found = k.GetInitChainHeight(ctx, chain.ChainId) + cs.InitialHeight, found = k.GetInitChainHeight(ctx, chainID) if !found { - panic(fmt.Errorf("cannot find init height for consumer chain %s", chain.ChainId)) + panic(fmt.Errorf("cannot find init height for consumer chain %s", chainID)) } - cs.SlashDowntimeAck = k.GetSlashAcks(ctx, chain.ChainId) + cs.SlashDowntimeAck = k.GetSlashAcks(ctx, chainID) } - cs.PendingValsetChanges = k.GetPendingVSCPackets(ctx, chain.ChainId) + cs.PendingValsetChanges = k.GetPendingVSCPackets(ctx, chainID) consumerStates = append(consumerStates, cs) - vscSendTimestamps := k.GetAllVscSendTimestamps(ctx, chain.ChainId) - exportedVscSendTimestamps = append(exportedVscSendTimestamps, types.ExportedVscSendTimestamp{ChainId: chain.ChainId, VscSendTimestamps: vscSendTimestamps}) + vscSendTimestamps := k.GetAllVscSendTimestamps(ctx, chainID) + exportedVscSendTimestamps = append(exportedVscSendTimestamps, types.ExportedVscSendTimestamp{ChainId: chainID, VscSendTimestamps: vscSendTimestamps}) } // ConsumerAddrsToPrune are added only for registered consumer chains consumerAddrsToPrune := []types.ConsumerAddrsToPrune{} - for _, chain := range registeredChains { - consumerAddrsToPrune = append(consumerAddrsToPrune, k.GetAllConsumerAddrsToPrune(ctx, chain.ChainId)...) + for _, chainID := range registeredChainIDs { + consumerAddrsToPrune = append(consumerAddrsToPrune, k.GetAllConsumerAddrsToPrune(ctx, chainID)...) } params := k.GetParams(ctx) diff --git a/x/ccv/provider/keeper/grpc_query.go b/x/ccv/provider/keeper/grpc_query.go index 019c1dc957..ee00556f06 100644 --- a/x/ccv/provider/keeper/grpc_query.go +++ b/x/ccv/provider/keeper/grpc_query.go @@ -47,15 +47,66 @@ func (k Keeper) QueryConsumerChains(goCtx context.Context, req *types.QueryConsu ctx := sdk.UnwrapSDKContext(goCtx) chains := []*types.Chain{} - for _, chain := range k.GetAllConsumerChains(ctx) { - // prevent implicit memory aliasing - c := chain + for _, chainID := range k.GetAllRegisteredConsumerChainIDs(ctx) { + c, err := k.GetConsumerChain(ctx, chainID) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } chains = append(chains, &c) } return &types.QueryConsumerChainsResponse{Chains: chains}, nil } +// GetConsumerChain returns a Chain data structure with all the necessary fields +func (k Keeper) GetConsumerChain(ctx sdk.Context, chainID string) (types.Chain, error) { + clientID, found := k.GetConsumerClientId(ctx, chainID) + if !found { + return types.Chain{}, fmt.Errorf("cannot find clientID for consumer (%s)", chainID) + } + + topN, found := k.GetTopN(ctx, chainID) + + // Get MinPowerInTop_N + var minPowerInTopN int64 + if found && topN > 0 { + res, err := k.ComputeMinPowerToOptIn(ctx, k.stakingKeeper.GetLastValidators(ctx), topN) + if err != nil { + return types.Chain{}, fmt.Errorf("failed to compute min power to opt in for chain (%s): %w", chainID, err) + } + minPowerInTopN = res + } else { + minPowerInTopN = -1 + } + + validatorSetCap, _ := k.GetValidatorSetCap(ctx, chainID) + + validatorsPowerCap, _ := k.GetValidatorsPowerCap(ctx, chainID) + + allowlist := k.GetAllowList(ctx, chainID) + strAllowlist := make([]string, len(allowlist)) + for i, addr := range allowlist { + strAllowlist[i] = addr.String() + } + + denylist := k.GetDenyList(ctx, chainID) + strDenylist := make([]string, len(denylist)) + for i, addr := range denylist { + strDenylist[i] = addr.String() + } + + return types.Chain{ + ChainId: chainID, + ClientId: clientID, + Top_N: topN, + MinPowerInTop_N: minPowerInTopN, + ValidatorSetCap: validatorSetCap, + ValidatorsPowerCap: validatorsPowerCap, + Allowlist: strAllowlist, + Denylist: strDenylist, + }, nil +} + func (k Keeper) QueryConsumerChainStarts(goCtx context.Context, req *types.QueryConsumerChainStartProposalsRequest) (*types.QueryConsumerChainStartProposalsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") @@ -307,11 +358,9 @@ func (k Keeper) QueryConsumerChainsValidatorHasToValidate(goCtx context.Context, // get all the consumer chains for which the validator is either already // opted-in, currently a consumer validator or if its voting power is within the TopN validators consumersToValidate := []string{} - for _, consumer := range k.GetAllConsumerChains(ctx) { - chainID := consumer.ChainId - - if hasToValidate, err := k.HasToValidate(ctx, provAddr, chainID); err == nil && hasToValidate { - consumersToValidate = append(consumersToValidate, chainID) + for _, consumerChainID := range k.GetAllRegisteredConsumerChainIDs(ctx) { + if hasToValidate, err := k.hasToValidate(ctx, provAddr, consumerChainID); err == nil && hasToValidate { + consumersToValidate = append(consumersToValidate, consumerChainID) } } @@ -320,6 +369,44 @@ func (k Keeper) QueryConsumerChainsValidatorHasToValidate(goCtx context.Context, }, nil } +// hasToValidate checks if a validator needs to validate on a consumer chain +func (k Keeper) hasToValidate( + ctx sdk.Context, + provAddr types.ProviderConsAddress, + chainID string, +) (bool, error) { + // if the validator was sent as part of the packet in the last epoch, it has to validate + if k.IsConsumerValidator(ctx, chainID, provAddr) { + return true, nil + } + + // if the validator was not part of the last epoch, check if the validator is going to be part of te next epoch + bondedValidators := k.stakingKeeper.GetLastValidators(ctx) + if topN, found := k.GetTopN(ctx, chainID); found && topN > 0 { + // in a Top-N chain, we automatically opt in all validators that belong to the top N + minPower, err := k.ComputeMinPowerToOptIn(ctx, bondedValidators, topN) + if err == nil { + k.OptInTopNValidators(ctx, chainID, bondedValidators, minPower) + } else { + k.Logger(ctx).Error("failed to compute min power to opt in for chain", "chain", chainID, "error", err) + } + } + + // if the validator is opted in and belongs to the validators of the next epoch, then if nothing changes + // the validator would have to validate in the next epoch + if k.IsOptedIn(ctx, chainID, provAddr) { + nextValidators := k.ComputeNextValidators(ctx, chainID, k.stakingKeeper.GetLastValidators(ctx)) + for _, v := range nextValidators { + consAddr := sdk.ConsAddress(v.ProviderConsAddr) + if provAddr.ToSdkConsAddr().Equals(consAddr) { + return true, nil + } + } + } + + return false, nil +} + // QueryValidatorConsumerCommissionRate returns the commission rate a given // validator charges on a given consumer chain func (k Keeper) QueryValidatorConsumerCommissionRate(goCtx context.Context, req *types.QueryValidatorConsumerCommissionRateRequest) (*types.QueryValidatorConsumerCommissionRateResponse, error) { diff --git a/x/ccv/provider/keeper/grpc_query_test.go b/x/ccv/provider/keeper/grpc_query_test.go index 71a9b82b7e..4f3825238f 100644 --- a/x/ccv/provider/keeper/grpc_query_test.go +++ b/x/ccv/provider/keeper/grpc_query_test.go @@ -1,11 +1,14 @@ package keeper_test import ( - "github.com/cometbft/cometbft/proto/tendermint/crypto" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "fmt" "testing" "time" + "github.com/cometbft/cometbft/proto/tendermint/crypto" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" sdktypes "github.com/cosmos/cosmos-sdk/types" @@ -249,3 +252,93 @@ func TestQueryValidatorConsumerCommissionRate(t *testing.T) { res, _ = pk.QueryValidatorConsumerCommissionRate(ctx, &req) require.Equal(t, expectedCommissionRate, res.Rate) } + +// TestGetConsumerChain tests GetConsumerChain behaviour correctness +func TestGetConsumerChain(t *testing.T) { + pk, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + chainIDs := []string{"chain-1", "chain-2", "chain-3", "chain-4"} + + // mock the validator set + vals := []stakingtypes.Validator{ + {OperatorAddress: "cosmosvaloper1c4k24jzduc365kywrsvf5ujz4ya6mwympnc4en"}, // 50 power + {OperatorAddress: "cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcvrj90c"}, // 150 power + {OperatorAddress: "cosmosvaloper1clpqr4nrk4khgkxj78fcwwh6dl3uw4epsluffn"}, // 300 power + {OperatorAddress: "cosmosvaloper1tflk30mq5vgqjdly92kkhhq3raev2hnz6eete3"}, // 500 power + } + powers := []int64{50, 150, 300, 500} // sum = 1000 + mocks.MockStakingKeeper.EXPECT().GetLastValidators(gomock.Any()).Return(vals).AnyTimes() + + for i, val := range vals { + mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), val.GetOperator()).Return(powers[i]).AnyTimes() + } + + // set Top N parameters, client ids and expected result + topNs := []uint32{0, 70, 90, 100} + expectedMinPowerInTopNs := []int64{ + -1, // Top N is 0, so not a Top N chain + 300, // 500 and 300 are in Top 70% + 150, // 150 is also in the top 90%, + 50, // everyone is in the top 100% + } + + validatorSetCaps := []uint32{0, 5, 10, 20} + validatorPowerCaps := []uint32{0, 5, 10, 33} + allowlists := [][]types.ProviderConsAddress{ + {}, + {types.NewProviderConsAddress([]byte("providerAddr1")), types.NewProviderConsAddress([]byte("providerAddr2"))}, + {types.NewProviderConsAddress([]byte("providerAddr3"))}, + {}, + } + + denylists := [][]types.ProviderConsAddress{ + {types.NewProviderConsAddress([]byte("providerAddr4")), types.NewProviderConsAddress([]byte("providerAddr5"))}, + {}, + {types.NewProviderConsAddress([]byte("providerAddr6"))}, + {}, + } + + expectedGetAllOrder := []types.Chain{} + for i, chainID := range chainIDs { + clientID := fmt.Sprintf("client-%d", len(chainIDs)-i) + topN := topNs[i] + pk.SetConsumerClientId(ctx, chainID, clientID) + pk.SetTopN(ctx, chainID, topN) + pk.SetValidatorSetCap(ctx, chainID, validatorSetCaps[i]) + pk.SetValidatorsPowerCap(ctx, chainID, validatorPowerCaps[i]) + for _, addr := range allowlists[i] { + pk.SetAllowlist(ctx, chainID, addr) + } + for _, addr := range denylists[i] { + pk.SetDenylist(ctx, chainID, addr) + } + strAllowlist := make([]string, len(allowlists[i])) + for j, addr := range allowlists[i] { + strAllowlist[j] = addr.String() + } + + strDenylist := make([]string, len(denylists[i])) + for j, addr := range denylists[i] { + strDenylist[j] = addr.String() + } + + expectedGetAllOrder = append(expectedGetAllOrder, + types.Chain{ + ChainId: chainID, + ClientId: clientID, + Top_N: topN, + MinPowerInTop_N: expectedMinPowerInTopNs[i], + ValidatorSetCap: validatorSetCaps[i], + ValidatorsPowerCap: validatorPowerCaps[i], + Allowlist: strAllowlist, + Denylist: strDenylist, + }) + } + + for i, chainID := range pk.GetAllRegisteredAndProposedChainIDs(ctx) { + c, err := pk.GetConsumerChain(ctx, chainID) + require.NoError(t, err) + require.Equal(t, expectedGetAllOrder[i], c) + } +} diff --git a/x/ccv/provider/keeper/hooks.go b/x/ccv/provider/keeper/hooks.go index ed926f16c7..5aef4a16ef 100644 --- a/x/ccv/provider/keeper/hooks.go +++ b/x/ccv/provider/keeper/hooks.go @@ -90,9 +90,9 @@ func (h Hooks) AfterUnbondingInitiated(ctx sdk.Context, id uint64) error { } // get all consumers where the validator is in the validator set - for _, chain := range h.k.GetAllConsumerChains(ctx) { - if h.k.IsConsumerValidator(ctx, chain.ChainId, types.NewProviderConsAddress(consAddr)) { - consumerChainIDS = append(consumerChainIDS, chain.ChainId) + for _, chainID := range h.k.GetAllRegisteredConsumerChainIDs(ctx) { + if h.k.IsConsumerValidator(ctx, chainID, types.NewProviderConsAddress(consAddr)) { + consumerChainIDS = append(consumerChainIDS, chainID) } } diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 438cb5aa67..9b95036dda 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -237,13 +237,15 @@ func (k Keeper) GetAllPendingConsumerChainIDs(ctx sdk.Context) []string { return chainIDs } -// GetAllConsumerChains gets all of the consumer chains, for which the provider module +// GetAllRegisteredConsumerChainIDs gets all of the consumer chain IDs, for which the provider module // created IBC clients. Consumer chains with created clients are also referred to as registered. // // Note that the registered consumer chains are stored under keys with the following format: // ChainToClientBytePrefix | chainID // Thus, the returned array is in ascending order of chainIDs. -func (k Keeper) GetAllConsumerChains(ctx sdk.Context) (chains []types.Chain) { +func (k Keeper) GetAllRegisteredConsumerChainIDs(ctx sdk.Context) []string { + chainIDs := []string{} + store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, []byte{types.ChainToClientBytePrefix}) defer iterator.Close() @@ -251,50 +253,10 @@ func (k Keeper) GetAllConsumerChains(ctx sdk.Context) (chains []types.Chain) { for ; iterator.Valid(); iterator.Next() { // remove 1 byte prefix from key to retrieve chainID chainID := string(iterator.Key()[1:]) - clientID := string(iterator.Value()) - - topN, found := k.GetTopN(ctx, chainID) - - var minPowerInTopN int64 - if found && topN > 0 { - res, err := k.ComputeMinPowerToOptIn(ctx, k.stakingKeeper.GetLastValidators(ctx), topN) - if err != nil { - k.Logger(ctx).Error("failed to compute min power to opt in for chain", "chain", chainID, "error", err) - minPowerInTopN = -1 - } else { - minPowerInTopN = res - } - } else { - minPowerInTopN = -1 - } - - validatorSetCap, _ := k.GetValidatorSetCap(ctx, chainID) - validatorsPowerCap, _ := k.GetValidatorsPowerCap(ctx, chainID) - allowlist := k.GetAllowList(ctx, chainID) - strAllowlist := make([]string, len(allowlist)) - for i, addr := range allowlist { - strAllowlist[i] = addr.String() - } - - denylist := k.GetDenyList(ctx, chainID) - strDenylist := make([]string, len(denylist)) - for i, addr := range denylist { - strDenylist[i] = addr.String() - } - - chains = append(chains, types.Chain{ - ChainId: chainID, - ClientId: clientID, - Top_N: topN, - MinPowerInTop_N: minPowerInTopN, - ValidatorSetCap: validatorSetCap, - ValidatorsPowerCap: validatorsPowerCap, - Allowlist: strAllowlist, - Denylist: strDenylist, - }) + chainIDs = append(chainIDs, chainID) } - return chains + return chainIDs } // SetChannelToChain sets the mapping from the CCV channel ID to the consumer chainID. @@ -1159,10 +1121,7 @@ func (k Keeper) BondDenom(ctx sdk.Context) string { func (k Keeper) GetAllRegisteredAndProposedChainIDs(ctx sdk.Context) []string { allConsumerChains := []string{} - consumerChains := k.GetAllConsumerChains(ctx) - for _, consumerChain := range consumerChains { - allConsumerChains = append(allConsumerChains, consumerChain.ChainId) - } + allConsumerChains = append(allConsumerChains, k.GetAllRegisteredConsumerChainIDs(ctx)...) proposedChains := k.GetAllProposedConsumerChainIDs(ctx) for _, proposedChain := range proposedChains { allConsumerChains = append(allConsumerChains, proposedChain.ChainID) @@ -1284,43 +1243,6 @@ func (k Keeper) DeleteAllOptedIn( } } -func (k Keeper) HasToValidate( - ctx sdk.Context, - provAddr types.ProviderConsAddress, - chainID string, -) (bool, error) { - // if the validator was sent as part of the packet in the last epoch, it has to validate - if k.IsConsumerValidator(ctx, chainID, provAddr) { - return true, nil - } - - // if the validator was not part of the last epoch, check if the validator is going to be part of te next epoch - bondedValidators := k.stakingKeeper.GetLastValidators(ctx) - if topN, found := k.GetTopN(ctx, chainID); found && topN > 0 { - // in a Top-N chain, we automatically opt in all validators that belong to the top N - minPower, err := k.ComputeMinPowerToOptIn(ctx, bondedValidators, topN) - if err == nil { - k.OptInTopNValidators(ctx, chainID, bondedValidators, minPower) - } else { - k.Logger(ctx).Error("failed to compute min power to opt in for chain", "chain", chainID, "error", err) - } - } - - // if the validator is opted in and belongs to the validators of the next epoch, then if nothing changes - // the validator would have to validate in the next epoch - if k.IsOptedIn(ctx, chainID, provAddr) { - nextValidators := k.ComputeNextValidators(ctx, chainID, k.stakingKeeper.GetLastValidators(ctx)) - for _, v := range nextValidators { - consAddr := sdk.ConsAddress(v.ProviderConsAddr) - if provAddr.ToSdkConsAddr().Equals(consAddr) { - return true, nil - } - } - } - - return false, nil -} - // SetConsumerCommissionRate sets a per-consumer chain commission rate // for the given validator address func (k Keeper) SetConsumerCommissionRate( diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index 894011cfab..f68afc20c2 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -8,7 +8,6 @@ import ( "time" ibctesting "github.com/cosmos/ibc-go/v7/testing" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" @@ -21,8 +20,6 @@ import ( testkeeper "github.com/cosmos/interchain-security/v4/testutil/keeper" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ccv "github.com/cosmos/interchain-security/v4/x/ccv/types" - - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) const consumer = "consumer" @@ -396,96 +393,22 @@ func TestVscSendTimestamp(t *testing.T) { require.Empty(t, providerKeeper.GetAllVscSendTimestamps(ctx, chainID)) } -// TestGetAllConsumerChains tests GetAllConsumerChains behaviour correctness -func TestGetAllConsumerChains(t *testing.T) { - pk, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) +func TestGetAllRegisteredConsumerChainIDs(t *testing.T) { + pk, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() chainIDs := []string{"chain-2", "chain-1", "chain-4", "chain-3"} + // GetAllRegisteredConsumerChainIDs iterates over chainID in lexicographical order + expectedChainIDs := []string{"chain-1", "chain-2", "chain-3", "chain-4"} - // mock the validator set - vals := []stakingtypes.Validator{ - {OperatorAddress: "cosmosvaloper1c4k24jzduc365kywrsvf5ujz4ya6mwympnc4en"}, // 50 power - {OperatorAddress: "cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcvrj90c"}, // 150 power - {OperatorAddress: "cosmosvaloper1clpqr4nrk4khgkxj78fcwwh6dl3uw4epsluffn"}, // 300 power - {OperatorAddress: "cosmosvaloper1tflk30mq5vgqjdly92kkhhq3raev2hnz6eete3"}, // 500 power - } - powers := []int64{50, 150, 300, 500} // sum = 1000 - mocks.MockStakingKeeper.EXPECT().GetLastValidators(gomock.Any()).Return(vals).AnyTimes() - - for i, val := range vals { - mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), val.GetOperator()).Return(powers[i]).AnyTimes() - } - - // set Top N parameters, client ids and expected result - topNs := []uint32{0, 70, 90, 100} - expectedMinPowerInTopNs := []int64{ - -1, // Top N is 0, so not a Top N chain - 300, // 500 and 300 are in Top 70% - 150, // 150 is also in the top 90%, - 50, // everyone is in the top 100% - } - - validatorSetCaps := []uint32{0, 5, 10, 20} - validatorPowerCaps := []uint32{0, 5, 10, 33} - allowlists := [][]types.ProviderConsAddress{ - {}, - {types.NewProviderConsAddress([]byte("providerAddr1")), types.NewProviderConsAddress([]byte("providerAddr2"))}, - {types.NewProviderConsAddress([]byte("providerAddr3"))}, - {}, - } - - denylists := [][]types.ProviderConsAddress{ - {types.NewProviderConsAddress([]byte("providerAddr4")), types.NewProviderConsAddress([]byte("providerAddr5"))}, - {}, - {types.NewProviderConsAddress([]byte("providerAddr6"))}, - {}, - } - - expectedGetAllOrder := []types.Chain{} for i, chainID := range chainIDs { clientID := fmt.Sprintf("client-%d", len(chainIDs)-i) - topN := topNs[i] pk.SetConsumerClientId(ctx, chainID, clientID) - pk.SetTopN(ctx, chainID, topN) - pk.SetValidatorSetCap(ctx, chainID, validatorSetCaps[i]) - pk.SetValidatorsPowerCap(ctx, chainID, validatorPowerCaps[i]) - for _, addr := range allowlists[i] { - pk.SetAllowlist(ctx, chainID, addr) - } - for _, addr := range denylists[i] { - pk.SetDenylist(ctx, chainID, addr) - } - strAllowlist := make([]string, len(allowlists[i])) - for j, addr := range allowlists[i] { - strAllowlist[j] = addr.String() - } - - strDenylist := make([]string, len(denylists[i])) - for j, addr := range denylists[i] { - strDenylist[j] = addr.String() - } - - expectedGetAllOrder = append(expectedGetAllOrder, - types.Chain{ - ChainId: chainID, - ClientId: clientID, - Top_N: topN, - MinPowerInTop_N: expectedMinPowerInTopNs[i], - ValidatorSetCap: validatorSetCaps[i], - ValidatorsPowerCap: validatorPowerCaps[i], - Allowlist: strAllowlist, - Denylist: strDenylist, - }) - } - // sorting by chainID - sort.Slice(expectedGetAllOrder, func(i, j int) bool { - return expectedGetAllOrder[i].ChainId < expectedGetAllOrder[j].ChainId - }) + } - result := pk.GetAllConsumerChains(ctx) + result := pk.GetAllRegisteredConsumerChainIDs(ctx) require.Len(t, result, len(chainIDs)) - require.Equal(t, expectedGetAllOrder, result) + require.Equal(t, expectedChainIDs, result) } // TestGetAllChannelToChains tests GetAllChannelToChains behaviour correctness diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 96cca06bfc..852b70928b 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -166,10 +166,10 @@ func (k Keeper) EndBlockVSU(ctx sdk.Context) { // If the CCV channel is not established for a consumer chain, // the updates will remain queued until the channel is established func (k Keeper) SendVSCPackets(ctx sdk.Context) { - for _, chain := range k.GetAllConsumerChains(ctx) { + for _, chainID := range k.GetAllRegisteredConsumerChainIDs(ctx) { // check if CCV channel is established and send - if channelID, found := k.GetChainToChannel(ctx, chain.ChainId); found { - k.SendVSCPacketsToChain(ctx, chain.ChainId, channelID) + if channelID, found := k.GetChainToChannel(ctx, chainID); found { + k.SendVSCPacketsToChain(ctx, chainID, channelID) } } } @@ -220,35 +220,36 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { // get the bonded validators from the staking module bondedValidators := k.stakingKeeper.GetLastValidators(ctx) - for _, chain := range k.GetAllConsumerChains(ctx) { - currentValidators := k.GetConsumerValSet(ctx, chain.ChainId) + for _, chainID := range k.GetAllRegisteredConsumerChainIDs(ctx) { + currentValidators := k.GetConsumerValSet(ctx, chainID) + topN, _ := k.GetTopN(ctx, chainID) - if chain.Top_N > 0 { + if topN > 0 { // in a Top-N chain, we automatically opt in all validators that belong to the top N - minPower, err := k.ComputeMinPowerToOptIn(ctx, bondedValidators, chain.Top_N) + minPower, err := k.ComputeMinPowerToOptIn(ctx, bondedValidators, topN) if err == nil { - k.OptInTopNValidators(ctx, chain.ChainId, bondedValidators, minPower) + k.OptInTopNValidators(ctx, chainID, bondedValidators, minPower) } else { // we just log here and do not panic because panic-ing would halt the provider chain - k.Logger(ctx).Error("failed to compute min power to opt in for chain", "chain", chain.ChainId, "error", err) + k.Logger(ctx).Error("failed to compute min power to opt in for chain", "chain", chainID, "error", err) } } - nextValidators := k.ComputeNextValidators(ctx, chain.ChainId, bondedValidators) + nextValidators := k.ComputeNextValidators(ctx, chainID, bondedValidators) valUpdates := DiffValidators(currentValidators, nextValidators) - k.SetConsumerValSet(ctx, chain.ChainId, nextValidators) + k.SetConsumerValSet(ctx, chainID, nextValidators) // check whether there are changes in the validator set; // note that this also entails unbonding operations // w/o changes in the voting power of the validators in the validator set - unbondingOps := k.GetUnbondingOpsFromIndex(ctx, chain.ChainId, valUpdateID) + unbondingOps := k.GetUnbondingOpsFromIndex(ctx, chainID, valUpdateID) if len(valUpdates) != 0 || len(unbondingOps) != 0 { // construct validator set change packet data - packet := ccv.NewValidatorSetChangePacketData(valUpdates, valUpdateID, k.ConsumeSlashAcks(ctx, chain.ChainId)) - k.AppendPendingVSCPackets(ctx, chain.ChainId, packet) + packet := ccv.NewValidatorSetChangePacketData(valUpdates, valUpdateID, k.ConsumeSlashAcks(ctx, chainID)) + k.AppendPendingVSCPackets(ctx, chainID, packet) k.Logger(ctx).Info("VSCPacket enqueued:", - "chainID", chain.ChainId, + "chainID", chainID, "vscID", valUpdateID, "len updates", len(valUpdates), "len unbonding ops", len(unbondingOps), diff --git a/x/ccv/provider/migrations/v3/migrations.go b/x/ccv/provider/migrations/v3/migrations.go index d308316761..0a3ae68f12 100644 --- a/x/ccv/provider/migrations/v3/migrations.go +++ b/x/ccv/provider/migrations/v3/migrations.go @@ -11,15 +11,15 @@ import ( // MigrateQueuedPackets processes all queued packet data for all consumer chains that were stored // on the provider in the v2 consensus version (jail throttling v1). func MigrateQueuedPackets(ctx sdk.Context, k providerkeeper.Keeper) error { - for _, consumer := range k.GetAllConsumerChains(ctx) { - slashData, vscmData := k.LegacyGetAllThrottledPacketData(ctx, consumer.ChainId) + for _, consumerChainID := range k.GetAllRegisteredConsumerChainIDs(ctx) { + slashData, vscmData := k.LegacyGetAllThrottledPacketData(ctx, consumerChainID) if len(slashData) > 0 { k.Logger(ctx).Error(fmt.Sprintf("slash data being dropped: %v", slashData)) } for _, data := range vscmData { - k.HandleVSCMaturedPacket(ctx, consumer.ChainId, data) + k.HandleVSCMaturedPacket(ctx, consumerChainID, data) } - k.LegacyDeleteThrottledPacketDataForConsumer(ctx, consumer.ChainId) + k.LegacyDeleteThrottledPacketDataForConsumer(ctx, consumerChainID) } return nil } diff --git a/x/ccv/provider/migrations/v5/migrations.go b/x/ccv/provider/migrations/v5/migrations.go index 748b35dd0e..7ed8bd8efc 100644 --- a/x/ccv/provider/migrations/v5/migrations.go +++ b/x/ccv/provider/migrations/v5/migrations.go @@ -10,12 +10,9 @@ import ( // If a chain is in voting while the upgrade happens, this is not sufficient, // and a migration to rewrite the proposal is needed. func MigrateTopNForRegisteredChains(ctx sdk.Context, providerKeeper providerkeeper.Keeper) { - // get all consumer chains - registeredConsumerChains := providerKeeper.GetAllConsumerChains(ctx) - // Set the topN of each chain to 95 - for _, chain := range registeredConsumerChains { - providerKeeper.SetTopN(ctx, chain.ChainId, 95) + for _, chainID := range providerKeeper.GetAllRegisteredConsumerChainIDs(ctx) { + providerKeeper.SetTopN(ctx, chainID, 95) } }