diff --git a/tests/integration/setup.go b/tests/integration/setup.go index 5b33a8b191..1a61cbd706 100644 --- a/tests/integration/setup.go +++ b/tests/integration/setup.go @@ -160,8 +160,9 @@ func (suite *CCVTestSuite) SetupTest() { suite.registerPacketSniffer(bundle.Chain) // check that TopN is correctly set for the consumer - topN := providerKeeper.GetTopN(suite.providerCtx(), bundle.ConsumerId) - suite.Require().Equal(bundle.TopN, topN) + powerShapingParameters, err := providerKeeper.GetConsumerPowerShapingParameters(suite.providerCtx(), bundle.ConsumerId) + suite.Require().NoError(err) + suite.Require().Equal(bundle.TopN, powerShapingParameters.Top_N) } // initialize each consumer chain with it's corresponding genesis state @@ -283,7 +284,7 @@ func initConsumerChain( err = bundle.Path.EndpointA.UpdateClient() s.Require().NoError(err) - if consumerId == "0" { + if consumerId == icstestingutils.FirstConsumerID { // Support tests that were written before multiple consumers were supported. firstBundle := s.getFirstBundle() s.consumerApp = firstBundle.App diff --git a/testutil/ibc_testing/generic_setup.go b/testutil/ibc_testing/generic_setup.go index 74bf620080..ca46aff6b4 100644 --- a/testutil/ibc_testing/generic_setup.go +++ b/testutil/ibc_testing/generic_setup.go @@ -164,7 +164,7 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp]( providerKeeper.SetConsumerInitializationParameters(providerChain.GetContext(), consumerId, initializationParameters) providerKeeper.SetConsumerPowerShapingParameters(providerChain.GetContext(), consumerId, powerShapingParameters) providerKeeper.SetConsumerPhase(providerChain.GetContext(), consumerId, providertypes.ConsumerPhase_CONSUMER_PHASE_INITIALIZED) - providerKeeper.AppendConsumerToBeLaunchedOnSpawnTime(providerChain.GetContext(), consumerId, coordinator.CurrentTime) + providerKeeper.AppendConsumerToBeLaunched(providerChain.GetContext(), consumerId, coordinator.CurrentTime) // opt-in all validators lastVals, err := providerApp.GetProviderKeeper().GetLastBondedValidators(providerChain.GetContext()) diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index e4b52dbb01..95c5de2380 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -262,8 +262,8 @@ func TestProviderStateIsCleanedAfterConsumerChainIsStopped(t *testing.T, ctx sdk require.Empty(t, acks) // in case the chain was successfully stopped, it should not contain a Top N associated to it - topN := providerKeeper.GetTopN(ctx, consumerId) - require.Zero(t, topN) + _, err := providerKeeper.GetConsumerPowerShapingParameters(ctx, consumerId) + require.Error(t, err) // test key assignment state is cleaned require.Empty(t, providerKeeper.GetAllValidatorConsumerPubKeys(ctx, &consumerId)) diff --git a/x/ccv/provider/keeper/distribution.go b/x/ccv/provider/keeper/distribution.go index 9291e57a76..9d67181158 100644 --- a/x/ccv/provider/keeper/distribution.go +++ b/x/ccv/provider/keeper/distribution.go @@ -335,10 +335,10 @@ func (k Keeper) IdentifyConsumerIdFromIBCPacket(ctx sdk.Context, packet channelt // 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 { - // check that the consumer chain exists - if !k.IsConsumerProposedOrRegistered(ctx, consumerId) { + // check that the consumer chain is active -- registered, initialized, or launched + if !k.IsConsumerActive(ctx, consumerId) { return errorsmod.Wrapf( - types.ErrUnknownConsumerId, + types.ErrInvalidPhase, "unknown consumer chain, with id: %s", consumerId) } diff --git a/x/ccv/provider/keeper/grpc_query.go b/x/ccv/provider/keeper/grpc_query.go index d47fd8fec9..14410324fa 100644 --- a/x/ccv/provider/keeper/grpc_query.go +++ b/x/ccv/provider/keeper/grpc_query.go @@ -110,7 +110,10 @@ func (k Keeper) GetConsumerChain(ctx sdk.Context, consumerId string) (types.Chai } clientID, _ := k.GetConsumerClientId(ctx, consumerId) - topN := k.GetTopN(ctx, consumerId) + powerShapingParameters, err := k.GetConsumerPowerShapingParameters(ctx, consumerId) + if err != nil { + return types.Chain{}, fmt.Errorf("cannot find power shaping parameters for consumer (%s): %s", consumerId, err.Error()) + } // Get the minimal power in the top N for the consumer chain minPowerInTopN, found := k.GetMinimumPowerInTopN(ctx, consumerId) @@ -141,22 +144,19 @@ func (k Keeper) GetConsumerChain(ctx sdk.Context, consumerId string) (types.Chai return types.Chain{}, fmt.Errorf("cannot get metadata for consumer (%s): %w", consumerId, err) } - allowInactiveVals := k.AllowsInactiveValidators(ctx, consumerId) - minStake := k.GetMinStake(ctx, consumerId) - return types.Chain{ ChainId: chainID, ClientId: clientID, - Top_N: topN, + Top_N: powerShapingParameters.Top_N, MinPowerInTop_N: minPowerInTopN, - ValidatorSetCap: k.GetValidatorSetCap(ctx, consumerId), - ValidatorsPowerCap: k.GetValidatorsPowerCap(ctx, consumerId), + ValidatorSetCap: powerShapingParameters.ValidatorSetCap, + ValidatorsPowerCap: powerShapingParameters.ValidatorsPowerCap, Allowlist: strAllowlist, Denylist: strDenylist, Phase: phase, Metadata: metadata, - AllowInactiveVals: allowInactiveVals, - MinStake: minStake, + AllowInactiveVals: powerShapingParameters.AllowInactiveVals, + MinStake: powerShapingParameters.MinStake, }, nil } @@ -314,7 +314,7 @@ func (k Keeper) QueryConsumerChainOptedInValidators(goCtx context.Context, req * optedInVals := []string{} ctx := sdk.UnwrapSDKContext(goCtx) - if !k.IsConsumerProposedOrRegistered(ctx, consumerId) { + if !k.IsConsumerActive(ctx, consumerId) { return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("unknown consumer chain: %s", consumerId)) } @@ -366,19 +366,23 @@ func (k Keeper) QueryConsumerValidators(goCtx context.Context, req *types.QueryC } minPower := int64(0) // for TopN chains, compute the minPower that will be automatically opted in - if topN := k.GetTopN(ctx, consumerId); topN > 0 { + powerShapingParameters, err := k.GetConsumerPowerShapingParameters(ctx, consumerId) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to get power shaping params: %s", err)) + } + if powerShapingParameters.Top_N > 0 { activeValidators, err := k.GetLastProviderConsensusActiveValidators(ctx) if err != nil { return nil, status.Error(codes.Internal, fmt.Sprintf("failed to get active validators: %s", err)) } - minPower, err = k.ComputeMinPowerInTopN(ctx, activeValidators, topN) + minPower, err = k.ComputeMinPowerInTopN(ctx, activeValidators, powerShapingParameters.Top_N) if err != nil { return nil, status.Error(codes.Internal, fmt.Sprintf("failed to compute min power to opt in for chain %s: %s", consumerId, err)) } } - consumerValSet = k.ComputeNextValidators(ctx, consumerId, bondedValidators, minPower) + consumerValSet = k.ComputeNextValidators(ctx, consumerId, bondedValidators, powerShapingParameters, minPower) // sort the address of the validators by ascending lexical order as they were persisted to the store sort.Slice(consumerValSet, func(i, j int) bool { @@ -485,10 +489,14 @@ func (k Keeper) hasToValidate( minPowerToOptIn := int64(0) // If the consumer is TopN compute the minimum power - if topN := k.GetTopN(ctx, consumerId); topN > 0 { + powerShapingParameters, err := k.GetConsumerPowerShapingParameters(ctx, consumerId) + if err != nil { + return false, err + } + if powerShapingParameters.Top_N > 0 { // compute the minimum power to opt-in since the one in the state is stale // Note that the effective min power will be computed at the end of the epoch - minPowerToOptIn, err = k.ComputeMinPowerInTopN(ctx, activeValidators, topN) + minPowerToOptIn, err = k.ComputeMinPowerInTopN(ctx, activeValidators, powerShapingParameters.Top_N) if err != nil { return false, err } @@ -500,7 +508,7 @@ func (k Keeper) hasToValidate( if err != nil { return false, err } - nextValidators := k.ComputeNextValidators(ctx, consumerId, lastVals, minPowerToOptIn) + nextValidators := k.ComputeNextValidators(ctx, consumerId, lastVals, powerShapingParameters, minPowerToOptIn) for _, v := range nextValidators { consAddr := sdk.ConsAddress(v.ProviderConsAddr) if provAddr.ToSdkConsAddr().Equals(consAddr) { @@ -532,7 +540,7 @@ func (k Keeper) QueryValidatorConsumerCommissionRate(goCtx context.Context, req ctx := sdk.UnwrapSDKContext(goCtx) - if !k.IsConsumerProposedOrRegistered(ctx, consumerId) { + if !k.IsConsumerActive(ctx, consumerId) { return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("unknown consumer chain: %s", consumerId)) } diff --git a/x/ccv/provider/keeper/grpc_query_test.go b/x/ccv/provider/keeper/grpc_query_test.go index a618a66d84..9063e1cdd0 100644 --- a/x/ccv/provider/keeper/grpc_query_test.go +++ b/x/ccv/provider/keeper/grpc_query_test.go @@ -55,11 +55,11 @@ func TestQueryAllPairsValConAddrByConsumerChainID(t *testing.T) { require.Error(t, err) // Request with invalid consumer id - response, err := pk.QueryAllPairsValConAddrByConsumerChainID(ctx, &types.QueryAllPairsValConAddrByConsumerChainIDRequest{ConsumerId: "invalidConsumerId"}) + _, err = pk.QueryAllPairsValConAddrByConsumerChainID(ctx, &types.QueryAllPairsValConAddrByConsumerChainIDRequest{ConsumerId: "invalidConsumerId"}) require.Error(t, err) // Request is valid - response, err = pk.QueryAllPairsValConAddrByConsumerChainID(ctx, &types.QueryAllPairsValConAddrByConsumerChainIDRequest{ConsumerId: consumerId}) + response, err := pk.QueryAllPairsValConAddrByConsumerChainID(ctx, &types.QueryAllPairsValConAddrByConsumerChainIDRequest{ConsumerId: consumerId}) require.NoError(t, err) expectedResult := types.PairValConAddrProviderAndConsumer{ @@ -117,6 +117,10 @@ func TestQueryConsumerValidators(t *testing.T) { // set the consumer to the "registered" phase pk.SetConsumerPhase(ctx, consumerId, types.ConsumerPhase_CONSUMER_PHASE_REGISTERED) + // set power shaping params + err = pk.SetConsumerPowerShapingParameters(ctx, consumerId, types.PowerShapingParameters{}) + require.NoError(t, err) + // expect empty valset testkeeper.SetupMocksForLastBondedValidatorsExpectation(mocks.MockStakingKeeper, 0, []stakingtypes.Validator{}, 1) // -1 to allow the calls "AnyTimes" res, err := pk.QueryConsumerValidators(ctx, &req) @@ -124,7 +128,7 @@ func TestQueryConsumerValidators(t *testing.T) { require.Len(t, res.Validators, 0) // create bonded validators - val1 := createStakingValidator(ctx, mocks, 1, 1, 1) + val1 := createStakingValidator(ctx, mocks, 1, 1) pk1, _ := val1.CmtConsPublicKey() valConsAddr1, _ := val1.GetConsAddr() providerAddr1 := types.NewProviderConsAddress(valConsAddr1) @@ -133,7 +137,7 @@ func TestQueryConsumerValidators(t *testing.T) { val1.Description = stakingtypes.Description{Moniker: "ConsumerValidator1"} val1.Commission.Rate = math.LegacyMustNewDecFromStr("0.123") - val2 := createStakingValidator(ctx, mocks, 1, 2, 2) + val2 := createStakingValidator(ctx, mocks, 2, 2) pk2, _ := val2.CmtConsPublicKey() valConsAddr2, _ := val2.GetConsAddr() providerAddr2 := types.NewProviderConsAddress(valConsAddr2) @@ -142,7 +146,7 @@ func TestQueryConsumerValidators(t *testing.T) { val2.Description = stakingtypes.Description{Moniker: "ConsumerValidator2"} val2.Commission.Rate = math.LegacyMustNewDecFromStr("0.456") - val3 := createStakingValidator(ctx, mocks, 1, 3, 3) + val3 := createStakingValidator(ctx, mocks, 3, 3) pk3, _ := val3.CmtConsPublicKey() valConsAddr3, _ := val3.GetConsAddr() providerAddr3 := types.NewProviderConsAddress(valConsAddr3) @@ -289,7 +293,7 @@ func TestQueryConsumerChainsValidatorHasToValidate(t *testing.T) { pk, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - val := createStakingValidator(ctx, mocks, 1, 1, 1) + val := createStakingValidator(ctx, mocks, 1, 1) valConsAddr, _ := val.GetConsAddr() providerAddr := types.NewProviderConsAddress(valConsAddr) mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valConsAddr).Return(val, nil).AnyTimes() @@ -300,13 +304,15 @@ func TestQueryConsumerChainsValidatorHasToValidate(t *testing.T) { } // set up some consumer chains - consumerChains := []string{"chain1", "chain2", "chain3", "chain4"} - for _, cc := range consumerChains { - pk.SetConsumerClientId(ctx, cc, "clientID") + consumerIDs := []string{"1", "23", "456", "6789"} + for _, cID := range consumerIDs { + pk.SetConsumerClientId(ctx, cID, "clientID") + err := pk.SetConsumerPowerShapingParameters(ctx, cID, types.PowerShapingParameters{}) + require.NoError(t, err) } - // set `providerAddr` as a consumer validator on "chain1" - pk.SetConsumerValidator(ctx, "chain1", types.ConsensusValidator{ + // set `providerAddr` as a consumer validator on first consumer chain + pk.SetConsumerValidator(ctx, consumerIDs[0], types.ConsensusValidator{ ProviderConsAddr: providerAddr.ToSdkConsAddr(), Power: 1, PublicKey: &crypto.PublicKey{ @@ -316,17 +322,18 @@ func TestQueryConsumerChainsValidatorHasToValidate(t *testing.T) { }, }) - // set `providerAddr` as an opted-in validator on "chain3" - pk.SetOptedIn(ctx, "chain3", providerAddr) + // set `providerAddr` as an opted-in validator on third consumer chain + pk.SetOptedIn(ctx, consumerIDs[2], providerAddr) // set max provider consensus vals to include all validators params := pk.GetParams(ctx) params.MaxProviderConsensusValidators = 3 pk.SetParams(ctx, params) - // `providerAddr` has to validate "chain1" because it is a consumer validator in this chain, as well as "chain3" - // because it opted in, in "chain3" and `providerAddr` belongs to the bonded validators - expectedChains := []string{"chain1", "chain3"} + // `providerAddr` has to validate + // - first consumer because it is a consumer validator in this chain, + // - third consumer because it opted in + expectedChains := []string{consumerIDs[0], consumerIDs[2]} res, err := pk.QueryConsumerChainsValidatorHasToValidate(ctx, &req) require.NoError(t, err) @@ -606,7 +613,10 @@ func TestQueryConsumerChains(t *testing.T) { Metadata: types.ConsumerMetadata{Name: chainID}, } pk.SetConsumerPhase(ctx, cID, c.Phase) - pk.SetConsumerMetadata(ctx, cID, c.Metadata) + err := pk.SetConsumerMetadata(ctx, cID, c.Metadata) + require.NoError(t, err) + err = pk.SetConsumerPowerShapingParameters(ctx, cID, types.PowerShapingParameters{}) + require.NoError(t, err) pk.SetConsumerChainId(ctx, cID, chainID) consumerIds[i] = cID @@ -644,7 +654,7 @@ func TestQueryConsumerChains(t *testing.T) { name: "expect initialized consumers when phase is set to Initialized", setup: func(ctx sdk.Context, pk keeper.Keeper) { consumers[1].Phase = types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED - err := pk.AppendConsumerToBeLaunchedOnSpawnTime(ctx, consumerIds[1], time.Now()) + err := pk.AppendConsumerToBeLaunched(ctx, consumerIds[1], time.Now()) require.NoError(t, err) pk.SetConsumerPhase(ctx, consumerIds[1], types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED) }, diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 93469af316..4598ff30c1 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -694,10 +694,7 @@ func (k Keeper) GetAllActiveConsumerIds(ctx sdk.Context) []string { consumerIds := []string{} for i := uint64(0); i < latestConsumerId; i++ { consumerId := fmt.Sprintf("%d", i) - phase := k.GetConsumerPhase(ctx, consumerId) - if phase != types.ConsumerPhase_CONSUMER_PHASE_REGISTERED && - phase != types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED && - phase != types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED { + if !k.IsConsumerActive(ctx, consumerId) { continue } consumerIds = append(consumerIds, consumerId) @@ -706,29 +703,6 @@ func (k Keeper) GetAllActiveConsumerIds(ctx sdk.Context) []string { return consumerIds } -// GetTopN returns N if chain `consumerId` has a top N associated, and 0 otherwise. -func (k Keeper) GetTopN( - ctx sdk.Context, - consumerId string, -) uint32 { - powerShapingParameters, err := k.GetConsumerPowerShapingParameters(ctx, consumerId) - if err != nil { - k.Logger(ctx).Error("could not retrieve power shaping parameters", "error", err) - } - - return powerShapingParameters.Top_N -} - -// IsTopN returns true if chain with `consumerId` is a Top-N chain (i.e., enforces at least one validator to validate chain `consumerId`) -func (k Keeper) IsTopN(ctx sdk.Context, consumerId string) bool { - return k.GetTopN(ctx, consumerId) > 0 -} - -// IsOptIn returns true if chain with `consumerId` is an Opt-In chain (i.e., no validator is forced to validate chain `consumerId`) -func (k Keeper) IsOptIn(ctx sdk.Context, consumerId string) bool { - return k.GetTopN(ctx, consumerId) == 0 -} - func (k Keeper) SetOptedIn( ctx sdk.Context, consumerId string, @@ -865,30 +839,6 @@ func (k Keeper) DeleteConsumerCommissionRate( store.Delete(types.ConsumerCommissionRateKey(consumerId, providerAddr)) } -// GetValidatorsPowerCap returns the associated power cap of chain with `consumerId` and 0 if no power cap association is found -func (k Keeper) GetValidatorsPowerCap( - ctx sdk.Context, - consumerId string, -) uint32 { - powerShapingParameters, err := k.GetConsumerPowerShapingParameters(ctx, consumerId) - if err != nil { - k.Logger(ctx).Error("could not retrieve power shaping parameters", "error", err) - } - return powerShapingParameters.ValidatorsPowerCap -} - -// GetValidatorSetCap returns the associated validator set cap of chain with `consumerId` and 0 if no set cap association is found -func (k Keeper) GetValidatorSetCap( - ctx sdk.Context, - consumerId string, -) uint32 { - powerShapingParameters, err := k.GetConsumerPowerShapingParameters(ctx, consumerId) - if err != nil { - k.Logger(ctx).Error("could not retrieve power shaping parameters", "error", err) - } - return powerShapingParameters.ValidatorSetCap -} - // SetAllowlist allowlists validator with `providerAddr` address on chain `consumerId` func (k Keeper) SetAllowlist( ctx sdk.Context, @@ -1054,32 +1004,6 @@ func (k Keeper) DeleteMinimumPowerInTopN( store.Delete(types.MinimumPowerInTopNKey(consumerId)) } -// GetMinStake returns the minimum stake required for a validator to validate -// a given consumer chain. Returns 0 if min stake is not found for this consumer id. -func (k Keeper) GetMinStake( - ctx sdk.Context, - consumerId string, -) uint64 { - powerShapingParameters, err := k.GetConsumerPowerShapingParameters(ctx, consumerId) - if err != nil { - k.Logger(ctx).Error("could not retrieve power shaping parameters", "error", err) - } - return powerShapingParameters.MinStake -} - -// AllowsInactiveValidators returns whether inactive validators are allowed to validate -// a given consumer chain. Returns false if flag on inactive validators is not found for this consumer id. -func (k Keeper) AllowsInactiveValidators( - ctx sdk.Context, - consumerId string, -) bool { - powerShapingParameters, err := k.GetConsumerPowerShapingParameters(ctx, consumerId) - if err != nil { - k.Logger(ctx).Error("could not retrieve power shaping parameters", "error", err) - } - return powerShapingParameters.AllowInactiveVals -} - func (k Keeper) UnbondingCanComplete(ctx sdk.Context, id uint64) error { return k.stakingKeeper.UnbondingCanComplete(ctx, id) } diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index b9b8294955..1794c2fba5 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -284,38 +284,6 @@ func TestSetSlashLog(t *testing.T) { require.False(t, providerKeeper.GetSlashLog(ctx, addrWithoutDoubleSigns)) } -// TestTopN tests the `SetTopN`, `GetTopN`, `IsTopN`, and `IsOptIn` methods -func TestTopN(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - tests := []struct { - consumerId string - N uint32 - isOptIn bool - }{ - {consumerId: "TopNChain1", N: 50, isOptIn: false}, - {consumerId: "TopNChain2", N: 100, isOptIn: false}, - {consumerId: "OptInChain", N: 0, isOptIn: true}, - } - - for _, test := range tests { - providerKeeper.SetConsumerPowerShapingParameters(ctx, test.consumerId, types.PowerShapingParameters{ - Top_N: test.N, - }) - topN := providerKeeper.GetTopN(ctx, test.consumerId) - require.Equal(t, test.N, topN) - - if test.isOptIn { - require.True(t, providerKeeper.IsOptIn(ctx, test.consumerId)) - require.False(t, providerKeeper.IsTopN(ctx, test.consumerId)) - } else { - require.False(t, providerKeeper.IsOptIn(ctx, test.consumerId)) - require.True(t, providerKeeper.IsTopN(ctx, test.consumerId)) - } - } -} - func TestGetAllOptedIn(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() @@ -458,37 +426,9 @@ func TestDenylist(t *testing.T) { require.True(t, providerKeeper.IsDenylistEmpty(ctx, chainID)) } -// TestAllowInactiveValidators tests the `SetAllowInactiveValidators` and `AllowsInactiveValidators` methods -func TestAllowInactiveValidators(t *testing.T) { - k, ctx, _, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - - consumerId := "consumerId" - - // check that by default, AllowsInactiveValidators returns false - require.False(t, k.AllowsInactiveValidators(ctx, consumerId)) - - // set the chain to allow inactive validators - k.SetConsumerPowerShapingParameters(ctx, consumerId, types.PowerShapingParameters{ - AllowInactiveVals: true, - }) - // check that AllowsInactiveValidators returns true - require.True(t, k.AllowsInactiveValidators(ctx, consumerId)) - - // set the chain to not allow inactive validators - k.SetConsumerPowerShapingParameters(ctx, consumerId, types.PowerShapingParameters{ - AllowInactiveVals: false, - }) - - // check that AllowsInactiveValidators returns false - require.False(t, k.AllowsInactiveValidators(ctx, consumerId)) -} - // Tests setting, getting and deleting parameters that are stored per-consumer chain. // The tests cover the following parameters: // - MinimumPowerInTopN -// - MinStake -// - ValidatorSetCap -// - ValidatorPowersCap func TestKeeperConsumerParams(t *testing.T) { k, ctx, _, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) @@ -511,52 +451,6 @@ func TestKeeperConsumerParams(t *testing.T) { initialValue: 1000, updatedValue: 2000, }, - { - name: "Minimum Stake", - settingFunc: func(ctx sdk.Context, id string, val int64) { - k.SetConsumerPowerShapingParameters(ctx, id, - types.PowerShapingParameters{ - MinStake: uint64(val), - }) - }, - getFunc: func(ctx sdk.Context, id string) int64 { - return int64(k.GetMinStake(ctx, id)) - }, - deleteFunc: func(ctx sdk.Context, id string) { k.DeleteConsumerPowerShapingParameters(ctx, id) }, - initialValue: 1000, - updatedValue: 2000, - }, - { - name: "Validator Set Cap", - settingFunc: func(ctx sdk.Context, id string, val int64) { - k.SetConsumerPowerShapingParameters(ctx, id, - types.PowerShapingParameters{ - ValidatorSetCap: uint32(val), - }) - }, - getFunc: func(ctx sdk.Context, id string) int64 { - return int64(k.GetValidatorSetCap(ctx, id)) - }, - deleteFunc: func(ctx sdk.Context, id string) { k.DeleteConsumerPowerShapingParameters(ctx, id) }, - initialValue: 10, - updatedValue: 200, - }, - { - name: "Validator Powers Cap", - settingFunc: func(ctx sdk.Context, id string, val int64) { - k.SetConsumerPowerShapingParameters(ctx, id, - types.PowerShapingParameters{ - ValidatorsPowerCap: uint32(val), - }) - }, - getFunc: func(ctx sdk.Context, id string) int64 { - val := k.GetValidatorsPowerCap(ctx, id) - return int64(val) - }, - deleteFunc: func(ctx sdk.Context, id string) { k.DeleteConsumerPowerShapingParameters(ctx, id) }, - initialValue: 10, - updatedValue: 11, - }, } for _, tt := range tests { diff --git a/x/ccv/provider/keeper/key_assignment.go b/x/ccv/provider/keeper/key_assignment.go index d037fcd648..47a890098a 100644 --- a/x/ccv/provider/keeper/key_assignment.go +++ b/x/ccv/provider/keeper/key_assignment.go @@ -403,10 +403,7 @@ func (k Keeper) AssignConsumerKey( validator stakingtypes.Validator, consumerKey tmprotocrypto.PublicKey, ) error { - phase := k.GetConsumerPhase(ctx, consumerId) - if phase != types.ConsumerPhase_CONSUMER_PHASE_REGISTERED && - phase != types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED && - phase != types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED { + if !k.IsConsumerActive(ctx, consumerId) { // check that the consumer chain is either registered, initialized, or launched return errorsmod.Wrapf( types.ErrInvalidPhase, @@ -462,6 +459,7 @@ func (k Keeper) AssignConsumerKey( oldConsumerAddr := types.NewConsumerConsAddress(oldConsumerAddrTmp) // check whether the consumer chain has already launched (i.e., a client to the consumer was already created) + phase := k.GetConsumerPhase(ctx, consumerId) if phase == types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED { // mark the old consumer address as prunable once UnbondingPeriod elapses; // note: this state is removed on EndBlock @@ -547,19 +545,6 @@ func (k Keeper) DeleteKeyAssignments(ctx sdk.Context, consumerId string) { } } -// IsConsumerProposedOrRegistered checks if a consumer chain is either registered, meaning either already running -// or will run soon, or proposed its ConsumerAdditionProposal was submitted but the chain was not yet added to ICS yet. -func (k Keeper) IsConsumerProposedOrRegistered(ctx sdk.Context, consumerId string) bool { - allConsumerChains := k.GetAllActiveConsumerIds(ctx) - for _, c := range allConsumerChains { - if c == consumerId { - return true - } - } - - return false -} - // ValidatorConsensusKeyInUse checks if the given consensus key is already // used by validator in a consumer chain. // Note that this method is called when a new validator is created in the x/staking module of cosmos-sdk. diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index e78e8f8c9b..b10cb883c6 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -91,14 +91,16 @@ func (k msgServer) AssignConsumerKey(goCtx context.Context, msg *types.MsgAssign func (k msgServer) RemoveConsumer(goCtx context.Context, msg *types.MsgRemoveConsumer) (*types.MsgRemoveConsumerResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + resp := types.MsgRemoveConsumerResponse{} + consumerId := msg.ConsumerId ownerAddress, err := k.Keeper.GetConsumerOwnerAddress(ctx, consumerId) if err != nil { - return &types.MsgRemoveConsumerResponse{}, errorsmod.Wrapf(types.ErrNoOwnerAddress, "cannot retrieve owner address %s", ownerAddress) + return &resp, errorsmod.Wrapf(types.ErrNoOwnerAddress, "cannot retrieve owner address %s", ownerAddress) } if msg.Signer != ownerAddress { - return &types.MsgRemoveConsumerResponse{}, errorsmod.Wrapf(types.ErrUnauthorized, "expected owner address %s, got %s", ownerAddress, msg.Signer) + return &resp, errorsmod.Wrapf(types.ErrUnauthorized, "expected owner address %s, got %s", ownerAddress, msg.Signer) } phase := k.Keeper.GetConsumerPhase(ctx, consumerId) @@ -109,13 +111,19 @@ func (k msgServer) RemoveConsumer(goCtx context.Context, msg *types.MsgRemoveCon previousStopTime, err := k.Keeper.GetConsumerStopTime(ctx, consumerId) if err == nil { - k.Keeper.RemoveConsumerToBeStoppedFromStopTime(ctx, consumerId, previousStopTime) + if err := k.Keeper.RemoveConsumerToBeStopped(ctx, consumerId, previousStopTime); err != nil { + return &resp, errorsmod.Wrapf(ccvtypes.ErrInvalidConsumerState, "cannot remove previous stop time: %s", err.Error()) + } } - k.Keeper.SetConsumerStopTime(ctx, consumerId, msg.StopTime) - k.Keeper.AppendConsumerToBeStoppedOnStopTime(ctx, consumerId, msg.StopTime) + if err := k.Keeper.SetConsumerStopTime(ctx, consumerId, msg.StopTime); err != nil { + return &resp, errorsmod.Wrapf(types.ErrInvalidStopTime, "cannot set stop time: %s", err.Error()) + } + if err := k.Keeper.AppendConsumerToBeStopped(ctx, consumerId, msg.StopTime); err != nil { + return &resp, errorsmod.Wrapf(ccvtypes.ErrInvalidConsumerState, "cannot set consumer to be stop: %s", err.Error()) + } - return &types.MsgRemoveConsumerResponse{}, nil + return &resp, nil } // ChangeRewardDenoms defines a rpc handler method for MsgChangeRewardDenoms @@ -311,6 +319,7 @@ func (k msgServer) SetConsumerCommissionRate(goCtx context.Context, msg *types.M // CreateConsumer creates a consumer chain func (k msgServer) CreateConsumer(goCtx context.Context, msg *types.MsgCreateConsumer) (*types.MsgCreateConsumerResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + resp := types.MsgCreateConsumerResponse{} consumerId := k.Keeper.FetchAndIncrementConsumerId(ctx) @@ -319,34 +328,43 @@ func (k msgServer) CreateConsumer(goCtx context.Context, msg *types.MsgCreateCon k.Keeper.SetConsumerPhase(ctx, consumerId, types.ConsumerPhase_CONSUMER_PHASE_REGISTERED) if err := k.Keeper.SetConsumerMetadata(ctx, consumerId, msg.Metadata); err != nil { - return &types.MsgCreateConsumerResponse{}, errorsmod.Wrapf(types.ErrInvalidConsumerMetadata, + return &resp, errorsmod.Wrapf(types.ErrInvalidConsumerMetadata, "cannot set consumer metadata: %s", err.Error()) } - // initialization parameters are optional and hence could be nil + // initialization parameters are optional and hence could be nil; + // in that case, set the default + initializationParameters := types.ConsumerInitializationParameters{} // default params if msg.InitializationParameters != nil { - if err := k.Keeper.SetConsumerInitializationParameters(ctx, consumerId, *msg.InitializationParameters); err != nil { - return &types.MsgCreateConsumerResponse{}, errorsmod.Wrapf(types.ErrInvalidConsumerInitializationParameters, - "cannot set consumer initialization parameters: %s", err.Error()) - } + initializationParameters = *msg.InitializationParameters + } + if err := k.Keeper.SetConsumerInitializationParameters(ctx, consumerId, initializationParameters); err != nil { + return &resp, errorsmod.Wrapf(types.ErrInvalidConsumerInitializationParameters, + "cannot set consumer initialization parameters: %s", err.Error()) } - // power-shaping parameters are optional and hence could be null + // power-shaping parameters are optional and hence could be nil; + // in that case, set the default + powerShapingParameters := types.PowerShapingParameters{} // default params if msg.PowerShapingParameters != nil { - if msg.PowerShapingParameters.Top_N != 0 { - return &types.MsgCreateConsumerResponse{}, errorsmod.Wrap(types.ErrCannotCreateTopNChain, + powerShapingParameters = *msg.PowerShapingParameters + + if powerShapingParameters.Top_N != 0 { + return &resp, errorsmod.Wrap(types.ErrCannotCreateTopNChain, "cannot create a Top N chain using the `MsgCreateConsumer` message; use `MsgUpdateConsumer` instead") } - if err := k.Keeper.SetConsumerPowerShapingParameters(ctx, consumerId, *msg.PowerShapingParameters); err != nil { - return &types.MsgCreateConsumerResponse{}, errorsmod.Wrapf(types.ErrInvalidPowerShapingParameters, - "cannot set power shaping parameters") - } + + // TODO UpdateAllowlist & UpdateDenylist + } + if err := k.Keeper.SetConsumerPowerShapingParameters(ctx, consumerId, powerShapingParameters); err != nil { + return &resp, errorsmod.Wrapf(types.ErrInvalidPowerShapingParameters, + "cannot set power shaping parameters") } if spawnTime, canLaunch := k.Keeper.CanLaunch(ctx, consumerId); canLaunch { k.Keeper.SetConsumerPhase(ctx, consumerId, types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED) if err := k.Keeper.PrepareConsumerForLaunch(ctx, consumerId, time.Time{}, spawnTime); err != nil { - return &types.MsgCreateConsumerResponse{}, errorsmod.Wrapf(types.ErrCannotPrepareForLaunch, + return &resp, errorsmod.Wrapf(types.ErrCannotPrepareForLaunch, "cannot prepare chain with consumer id (%s) for launch", consumerId) } } @@ -357,36 +375,36 @@ func (k msgServer) CreateConsumer(goCtx context.Context, msg *types.MsgCreateCon ), }) - return &types.MsgCreateConsumerResponse{ConsumerId: consumerId}, nil + resp.ConsumerId = consumerId + return &resp, nil } // UpdateConsumer updates the record of a consumer chain func (k msgServer) UpdateConsumer(goCtx context.Context, msg *types.MsgUpdateConsumer) (*types.MsgUpdateConsumerResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + resp := types.MsgUpdateConsumerResponse{} + consumerId := msg.ConsumerId - phase := k.Keeper.GetConsumerPhase(ctx, consumerId) - if phase != types.ConsumerPhase_CONSUMER_PHASE_REGISTERED && - phase != types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED && - phase != types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrInvalidPhase, + if !k.Keeper.IsConsumerActive(ctx, consumerId) { + return &resp, errorsmod.Wrapf(types.ErrInvalidPhase, "cannot update consumer chain that is not in the registered, initialized, or launched phase: %s", consumerId) } ownerAddress, err := k.Keeper.GetConsumerOwnerAddress(ctx, consumerId) if err != nil { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrNoOwnerAddress, "cannot retrieve owner address %s", ownerAddress) + return &resp, errorsmod.Wrapf(types.ErrNoOwnerAddress, "cannot retrieve owner address %s", ownerAddress) } if msg.Signer != ownerAddress { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrUnauthorized, "expected owner address %s, got %s", ownerAddress, msg.Signer) + return &resp, errorsmod.Wrapf(types.ErrUnauthorized, "expected owner address %s, got %s", ownerAddress, msg.Signer) } // The new owner address can be empty, in which case the consumer chain does not change its owner. // However, if the new owner address is not empty, we verify that it's a valid account address. if strings.TrimSpace(msg.NewOwnerAddress) != "" { if _, err := k.accountKeeper.AddressCodec().StringToBytes(msg.NewOwnerAddress); err != nil { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrInvalidNewOwnerAddress, "invalid new owner address %s", msg.NewOwnerAddress) + return &resp, errorsmod.Wrapf(types.ErrInvalidNewOwnerAddress, "invalid new owner address %s", msg.NewOwnerAddress) } k.Keeper.SetConsumerOwnerAddress(ctx, consumerId, msg.NewOwnerAddress) @@ -394,21 +412,22 @@ func (k msgServer) UpdateConsumer(goCtx context.Context, msg *types.MsgUpdateCon if msg.Metadata != nil { if err := k.Keeper.SetConsumerMetadata(ctx, consumerId, *msg.Metadata); err != nil { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrInvalidConsumerMetadata, + return &resp, errorsmod.Wrapf(types.ErrInvalidConsumerMetadata, "cannot set consumer metadata: %s", err.Error()) } } // get the previous spawn time so that we can use it in `PrepareConsumerForLaunch` - var previousSpawnTime time.Time - previousInitializationParameters, err := k.Keeper.GetConsumerInitializationParameters(ctx, msg.ConsumerId) - if err == nil { - previousSpawnTime = previousInitializationParameters.SpawnTime + previousInitializationParameters, err := k.Keeper.GetConsumerInitializationParameters(ctx, consumerId) + if err != nil { + return &resp, errorsmod.Wrapf(ccvtypes.ErrInvalidConsumerState, + "cannot get consumer initialized parameters, consumerId(%s): %s", consumerId, err.Error()) } + previousSpawnTime := previousInitializationParameters.SpawnTime if msg.InitializationParameters != nil { if err = k.Keeper.SetConsumerInitializationParameters(ctx, msg.ConsumerId, *msg.InitializationParameters); err != nil { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrInvalidConsumerInitializationParameters, + return &resp, errorsmod.Wrapf(types.ErrInvalidConsumerInitializationParameters, "cannot set consumer initialization parameters: %s", err.Error()) } } @@ -418,13 +437,19 @@ func (k msgServer) UpdateConsumer(goCtx context.Context, msg *types.MsgUpdateCon // Top N chain, we need two `MsgUpdateConsumer` messages: i) one that would set the `ownerAddress` to the gov module // and ii) one that would set the `Top_N` to something greater than 0. if msg.PowerShapingParameters.Top_N > 0 && ownerAddress != k.GetAuthority() { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrInvalidTransformToTopN, + return &resp, errorsmod.Wrapf(types.ErrInvalidTransformToTopN, "an update to a Top N chain can only be done if chain is owner is the gov module") } - oldTopN := k.Keeper.GetTopN(ctx, consumerId) + oldPowerShapingParameters, err := k.Keeper.GetConsumerPowerShapingParameters(ctx, consumerId) + if err != nil { + return &resp, errorsmod.Wrapf(ccvtypes.ErrInvalidConsumerState, + "cannot get consumer previous power shaping parameters: %s", err.Error()) + } + oldTopN := oldPowerShapingParameters.Top_N + if err = k.Keeper.SetConsumerPowerShapingParameters(ctx, consumerId, *msg.PowerShapingParameters); err != nil { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrInvalidPowerShapingParameters, + return &resp, errorsmod.Wrapf(types.ErrInvalidPowerShapingParameters, "cannot set power shaping parameters") } @@ -432,7 +457,7 @@ func (k msgServer) UpdateConsumer(goCtx context.Context, msg *types.MsgUpdateCon k.Keeper.UpdateDenylist(ctx, consumerId, msg.PowerShapingParameters.Denylist) err = k.Keeper.UpdateMinimumPowerInTopN(ctx, consumerId, oldTopN, msg.PowerShapingParameters.Top_N) if err != nil { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrCannotUpdateMinimumPowerInTopN, + return &resp, errorsmod.Wrapf(types.ErrCannotUpdateMinimumPowerInTopN, "could not update minimum power in top N, oldTopN: %d, newTopN: %d, error: %s", oldTopN, msg.PowerShapingParameters.Top_N, err.Error()) } } @@ -441,28 +466,28 @@ func (k msgServer) UpdateConsumer(goCtx context.Context, msg *types.MsgUpdateCon // remains a Top N chain. currentOwnerAddress, err := k.Keeper.GetConsumerOwnerAddress(ctx, consumerId) if err != nil { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrNoOwnerAddress, "cannot retrieve owner address %s: %s", ownerAddress, err.Error()) + return &resp, errorsmod.Wrapf(ccvtypes.ErrInvalidConsumerState, "cannot retrieve owner address %s: %s", ownerAddress, err.Error()) } currentPowerShapingParameters, err := k.Keeper.GetConsumerPowerShapingParameters(ctx, consumerId) if err != nil { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrInvalidPowerShapingParameters, "cannot retrieve power shaping parameters: %s", err.Error()) + return &resp, errorsmod.Wrapf(ccvtypes.ErrInvalidConsumerState, "cannot retrieve power shaping parameters: %s", err.Error()) } if currentPowerShapingParameters.Top_N != 0 && currentOwnerAddress != k.GetAuthority() { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrInvalidTransformToOptIn, + return &resp, errorsmod.Wrapf(types.ErrInvalidTransformToOptIn, "a move to a new owner address that is not the gov module can only be done if `Top N` is set to 0") } if spawnTime, canLaunch := k.Keeper.CanLaunch(ctx, consumerId); canLaunch { k.Keeper.SetConsumerPhase(ctx, consumerId, types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED) if err := k.Keeper.PrepareConsumerForLaunch(ctx, consumerId, previousSpawnTime, spawnTime); err != nil { - return &types.MsgUpdateConsumerResponse{}, errorsmod.Wrapf(types.ErrCannotPrepareForLaunch, + return &resp, errorsmod.Wrapf(types.ErrCannotPrepareForLaunch, "cannot prepare chain with consumer id (%s) for launch", consumerId) } } - return &types.MsgUpdateConsumerResponse{}, nil + return &resp, nil } func (k msgServer) ConsumerAddition(_ context.Context, _ *types.MsgConsumerAddition) (*types.MsgConsumerAdditionResponse, error) { diff --git a/x/ccv/provider/keeper/msg_server_test.go b/x/ccv/provider/keeper/msg_server_test.go index fd353e38bf..b6f9bb1f71 100644 --- a/x/ccv/provider/keeper/msg_server_test.go +++ b/x/ccv/provider/keeper/msg_server_test.go @@ -31,6 +31,7 @@ func TestCreateConsumer(t *testing.T) { require.NoError(t, err) require.Equal(t, consumerMetadata, actualMetadata) ownerAddress, err := providerKeeper.GetConsumerOwnerAddress(ctx, "0") + require.NoError(t, err) require.Equal(t, "signer", ownerAddress) phase := providerKeeper.GetConsumerPhase(ctx, "0") require.Equal(t, providertypes.ConsumerPhase_CONSUMER_PHASE_REGISTERED, phase) @@ -50,6 +51,7 @@ func TestCreateConsumer(t *testing.T) { require.NoError(t, err) require.Equal(t, consumerMetadata, actualMetadata) ownerAddress, err = providerKeeper.GetConsumerOwnerAddress(ctx, "1") + require.NoError(t, err) require.Equal(t, "signer2", ownerAddress) phase = providerKeeper.GetConsumerPhase(ctx, "1") require.Equal(t, providertypes.ConsumerPhase_CONSUMER_PHASE_REGISTERED, phase) diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go index eccbc5e81a..95667a1d4c 100644 --- a/x/ccv/provider/keeper/partial_set_security.go +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -11,15 +11,13 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v5/x/ccv/types" ) // HandleOptIn prepares validator `providerAddr` to opt in to `consumerId` with an optional `consumerKey` consumer public key. // Note that the validator only opts in at the end of an epoch. func (k Keeper) HandleOptIn(ctx sdk.Context, consumerId string, providerAddr types.ProviderConsAddress, consumerKey string) error { - phase := k.GetConsumerPhase(ctx, consumerId) - if phase != types.ConsumerPhase_CONSUMER_PHASE_REGISTERED && - phase != types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED && - phase != types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED { + if !k.IsConsumerActive(ctx, consumerId) { return errorsmod.Wrapf( types.ErrInvalidPhase, "cannot opt in to a consumer chain that is not in the registered, initialized, or launched phase: %s", consumerId) @@ -68,15 +66,28 @@ func (k Keeper) HandleOptIn(ctx sdk.Context, consumerId string, providerAddr typ // HandleOptOut prepares validator `providerAddr` to opt out from running `consumerId`. // Note that the validator only opts out at the end of an epoch. func (k Keeper) HandleOptOut(ctx sdk.Context, consumerId string, providerAddr types.ProviderConsAddress) error { - if _, found := k.GetConsumerClientId(ctx, consumerId); !found { - // A validator can only opt out from a running chain. We check this by checking the consumer client id, because - // `SetConsumerClientId` is set when the chain starts in `CreateConsumerClientInCachedCtx` of `BeginBlockInit`. + phase := k.GetConsumerPhase(ctx, consumerId) + if phase == types.ConsumerPhase_CONSUMER_PHASE_UNSPECIFIED { return errorsmod.Wrapf( types.ErrUnknownConsumerId, - "opting out of an unknown or not running consumer chain, with id: %s", consumerId) + "opting out of an unknown consumer chain, consumerId(%s)", consumerId, + ) + } + if phase != types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED { + // A validator can only opt out from a running chain + return errorsmod.Wrapf( + types.ErrInvalidPhase, + "opting out of a consumer chain not yet launched, consumerId(%s)", consumerId, + ) } - if topN := k.GetTopN(ctx, consumerId); topN > 0 { + powerShapingParameters, err := k.GetConsumerPowerShapingParameters(ctx, consumerId) + if err != nil { + return errorsmod.Wrapf(ccvtypes.ErrInvalidConsumerState, + "cannot get consumer power shaping parameters: %s", err.Error(), + ) + } + if powerShapingParameters.Top_N > 0 { // a validator cannot opt out from a Top N chain if the validator is in the Top N validators validator, err := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) if err != nil { @@ -202,13 +213,18 @@ func (k Keeper) ComputeMinPowerInTopN(ctx sdk.Context, bondedValidators []stakin // CapValidatorSet caps the provided `validators` if chain with `consumerId` is an Opt In chain with a validator-set cap. // If cap is `k`, `CapValidatorSet` returns the first `k` validators from `validators` with the highest power. -func (k Keeper) CapValidatorSet(ctx sdk.Context, consumerId string, validators []types.ConsensusValidator) []types.ConsensusValidator { - if k.GetTopN(ctx, consumerId) > 0 { +func (k Keeper) CapValidatorSet( + ctx sdk.Context, + powerShapingParameters types.PowerShapingParameters, + validators []types.ConsensusValidator, +) []types.ConsensusValidator { + if powerShapingParameters.Top_N > 0 { // is a no-op if the chain is a Top N chain return validators } - if validatorSetCap := k.GetValidatorSetCap(ctx, consumerId); validatorSetCap != 0 && int(validatorSetCap) < len(validators) { + validatorSetCap := powerShapingParameters.ValidatorSetCap + if validatorSetCap != 0 && int(validatorSetCap) < len(validators) { sort.Slice(validators, func(i, j int) bool { return validators[i].Power > validators[j].Power }) @@ -223,9 +239,13 @@ func (k Keeper) CapValidatorSet(ctx sdk.Context, consumerId string, validators [ // with their new powers. Works on a best-basis effort because there are cases where we cannot guarantee that all validators // on the consumer chain have less power than the set validators-power cap. For example, if we have 10 validators and // the power cap is set to 5%, we need at least one validator to have more than 10% of the voting power on the consumer chain. -func (k Keeper) CapValidatorsPower(ctx sdk.Context, consumerId string, validators []types.ConsensusValidator) []types.ConsensusValidator { - if p := k.GetValidatorsPowerCap(ctx, consumerId); p > 0 { - return NoMoreThanPercentOfTheSum(validators, p) +func (k Keeper) CapValidatorsPower( + ctx sdk.Context, + validatorsPowerCap uint32, + validators []types.ConsensusValidator, +) []types.ConsensusValidator { + if validatorsPowerCap > 0 { + return NoMoreThanPercentOfTheSum(validators, validatorsPowerCap) } else { // is a no-op if power cap is not set for `consumerId` return validators @@ -334,12 +354,18 @@ func NoMoreThanPercentOfTheSum(validators []types.ConsensusValidator, percent ui // CanValidateChain returns true if the validator `providerAddr` is opted-in to chain with `consumerId` and the allowlist // and denylist do not prevent the validator from validating the chain. -func (k Keeper) CanValidateChain(ctx sdk.Context, consumerId string, providerAddr types.ProviderConsAddress, minPowerToOptIn int64) bool { +func (k Keeper) CanValidateChain( + ctx sdk.Context, + consumerId string, + providerAddr types.ProviderConsAddress, + topN uint32, + minPowerToOptIn int64, +) bool { // check if the validator is already opted-in optedIn := k.IsOptedIn(ctx, consumerId, providerAddr) // check if the validator is automatically opted-in for a topN chain - if !optedIn && k.GetTopN(ctx, consumerId) > 0 { + if !optedIn && topN > 0 { optedIn = k.HasMinPower(ctx, providerAddr, minPowerToOptIn) } @@ -355,8 +381,11 @@ func (k Keeper) CanValidateChain(ctx sdk.Context, consumerId string, providerAdd // FulfillsMinStake returns true if the validator `providerAddr` has enough stake to validate chain with `consumerId` // by checking its staked tokens against the minimum stake required to validate the chain. -func (k Keeper) FulfillsMinStake(ctx sdk.Context, consumerId string, providerAddr types.ProviderConsAddress) bool { - minStake := k.GetMinStake(ctx, consumerId) +func (k Keeper) FulfillsMinStake( + ctx sdk.Context, + minStake uint64, + providerAddr types.ProviderConsAddress, +) bool { if minStake == 0 { return true } @@ -372,7 +401,13 @@ func (k Keeper) FulfillsMinStake(ctx sdk.Context, consumerId string, providerAdd } // ComputeNextValidators computes the validators for the upcoming epoch based on the currently `bondedValidators`. -func (k Keeper) ComputeNextValidators(ctx sdk.Context, consumerId string, bondedValidators []stakingtypes.Validator, minPowerToOptIn int64) []types.ConsensusValidator { +func (k Keeper) ComputeNextValidators( + ctx sdk.Context, + consumerId string, + bondedValidators []stakingtypes.Validator, + powerShapingParameters types.PowerShapingParameters, + minPowerToOptIn int64, +) []types.ConsensusValidator { // sort the bonded validators by number of staked tokens in descending order sort.Slice(bondedValidators, func(i, j int) bool { return bondedValidators[i].GetBondedTokens().GT(bondedValidators[j].GetBondedTokens()) @@ -380,8 +415,7 @@ func (k Keeper) ComputeNextValidators(ctx sdk.Context, consumerId string, bonded // if inactive validators are not allowed, only consider the first `MaxProviderConsensusValidators` validators // since those are the ones that participate in consensus - allowInactiveVals := k.AllowsInactiveValidators(ctx, consumerId) - if !allowInactiveVals { + if !powerShapingParameters.AllowInactiveVals { // only leave the first MaxProviderConsensusValidators bonded validators maxProviderConsensusVals := k.GetMaxProviderConsensusValidators(ctx) if len(bondedValidators) > int(maxProviderConsensusVals) { @@ -391,11 +425,12 @@ func (k Keeper) ComputeNextValidators(ctx sdk.Context, consumerId string, bonded nextValidators := k.FilterValidators(ctx, consumerId, bondedValidators, func(providerAddr types.ProviderConsAddress) bool { - return k.CanValidateChain(ctx, consumerId, providerAddr, minPowerToOptIn) && k.FulfillsMinStake(ctx, consumerId, providerAddr) + return k.CanValidateChain(ctx, consumerId, providerAddr, powerShapingParameters.Top_N, minPowerToOptIn) && + k.FulfillsMinStake(ctx, powerShapingParameters.MinStake, providerAddr) }) - nextValidators = k.CapValidatorSet(ctx, consumerId, nextValidators) - return k.CapValidatorsPower(ctx, consumerId, nextValidators) + nextValidators = k.CapValidatorSet(ctx, powerShapingParameters, nextValidators) + return k.CapValidatorsPower(ctx, powerShapingParameters.ValidatorsPowerCap, nextValidators) } // HasMinPower returns true if the `providerAddr` voting power is GTE than the given minimum power diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go index 357811441f..f760599907 100644 --- a/x/ccv/provider/keeper/partial_set_security_test.go +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -109,19 +109,26 @@ func TestHandleOptOut(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() + consumerId := "consumerId" + providerAddr := types.NewProviderConsAddress([]byte("providerAddr")) // trying to opt out from a not running chain returns an error require.Error(t, providerKeeper.HandleOptOut(ctx, "unknownChainID", providerAddr)) - // set a consumer client so that the chain is considered running - providerKeeper.SetConsumerClientId(ctx, "consumerId", "clientID") + // set the phase and power shaping params + providerKeeper.SetConsumerPhase(ctx, consumerId, types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED) + err := providerKeeper.SetConsumerPowerShapingParameters(ctx, consumerId, types.PowerShapingParameters{}) + require.NoError(t, err) // if validator (`providerAddr`) is already opted in, then an opt-out would remove this validator - providerKeeper.SetOptedIn(ctx, "consumerId", providerAddr) - require.True(t, providerKeeper.IsOptedIn(ctx, "consumerId", providerAddr)) - providerKeeper.HandleOptOut(ctx, "consumerId", providerAddr) - require.False(t, providerKeeper.IsOptedIn(ctx, "consumerId", providerAddr)) + providerKeeper.SetOptedIn(ctx, consumerId, providerAddr) + require.True(t, providerKeeper.IsOptedIn(ctx, consumerId, providerAddr)) + err = providerKeeper.AppendOptedInConsumerId(ctx, providerAddr, consumerId) + require.NoError(t, err) + err = providerKeeper.HandleOptOut(ctx, consumerId, providerAddr) + require.NoError(t, err) + require.False(t, providerKeeper.IsOptedIn(ctx, consumerId, providerAddr)) } func TestHandleOptOutFromTopNChain(t *testing.T) { @@ -130,24 +137,25 @@ func TestHandleOptOutFromTopNChain(t *testing.T) { consumerId := "consumerId" - // set a consumer client so that the chain is considered running - providerKeeper.SetConsumerClientId(ctx, consumerId, "clientID") + // set the phase + providerKeeper.SetConsumerPhase(ctx, consumerId, types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED) // set the chain as Top 50 and create 4 validators with 10%, 20%, 30%, and 40% of the total voting power // respectively - providerKeeper.SetConsumerPowerShapingParameters(ctx, consumerId, types.PowerShapingParameters{ + err := providerKeeper.SetConsumerPowerShapingParameters(ctx, consumerId, types.PowerShapingParameters{ Top_N: 50, }) - valA := createStakingValidator(ctx, mocks, 1, 1, 1) // 10% of the total voting power (can opt out) + require.NoError(t, err) + valA := createStakingValidator(ctx, mocks, 1, 1) // 10% of the total voting power (can opt out) valAConsAddr, _ := valA.GetConsAddr() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valAConsAddr).Return(valA, nil).AnyTimes() - valB := createStakingValidator(ctx, mocks, 2, 2, 2) // 20% of the total voting power (can opt out) + valB := createStakingValidator(ctx, mocks, 2, 2) // 20% of the total voting power (can opt out) valBConsAddr, _ := valB.GetConsAddr() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valBConsAddr).Return(valB, nil).AnyTimes() - valC := createStakingValidator(ctx, mocks, 3, 3, 3) // 30% of the total voting power (cannot opt out) + valC := createStakingValidator(ctx, mocks, 3, 3) // 30% of the total voting power (cannot opt out) valCConsAddr, _ := valC.GetConsAddr() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valCConsAddr).Return(valC, nil).AnyTimes() - valD := createStakingValidator(ctx, mocks, 4, 4, 4) // 40% of the total voting power (cannot opt out) + valD := createStakingValidator(ctx, mocks, 4, 4) // 40% of the total voting power (cannot opt out) valDConsAddr, _ := valD.GetConsAddr() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valDConsAddr).Return(valD, nil).AnyTimes() @@ -160,13 +168,17 @@ func TestHandleOptOutFromTopNChain(t *testing.T) { // opt in all validators providerKeeper.SetOptedIn(ctx, consumerId, types.NewProviderConsAddress(valAConsAddr)) - providerKeeper.AppendOptedInConsumerId(ctx, types.NewProviderConsAddress(valAConsAddr), consumerId) + err = providerKeeper.AppendOptedInConsumerId(ctx, types.NewProviderConsAddress(valAConsAddr), consumerId) + require.NoError(t, err) providerKeeper.SetOptedIn(ctx, consumerId, types.NewProviderConsAddress(valBConsAddr)) - providerKeeper.AppendOptedInConsumerId(ctx, types.NewProviderConsAddress(valBConsAddr), consumerId) + err = providerKeeper.AppendOptedInConsumerId(ctx, types.NewProviderConsAddress(valBConsAddr), consumerId) + require.NoError(t, err) providerKeeper.SetOptedIn(ctx, consumerId, types.NewProviderConsAddress(valCConsAddr)) - providerKeeper.AppendOptedInConsumerId(ctx, types.NewProviderConsAddress(valCConsAddr), consumerId) + err = providerKeeper.AppendOptedInConsumerId(ctx, types.NewProviderConsAddress(valCConsAddr), consumerId) + require.NoError(t, err) providerKeeper.SetOptedIn(ctx, consumerId, types.NewProviderConsAddress(valDConsAddr)) - providerKeeper.AppendOptedInConsumerId(ctx, types.NewProviderConsAddress(valDConsAddr), consumerId) + err = providerKeeper.AppendOptedInConsumerId(ctx, types.NewProviderConsAddress(valDConsAddr), consumerId) + require.NoError(t, err) // validators A and B can opt out because they belong the bottom 30% of validators require.NoError(t, providerKeeper.HandleOptOut(ctx, consumerId, types.NewProviderConsAddress(valAConsAddr))) @@ -178,8 +190,9 @@ func TestHandleOptOutFromTopNChain(t *testing.T) { require.Error(t, providerKeeper.HandleOptOut(ctx, consumerId, types.NewProviderConsAddress(valDConsAddr))) // opting out a validator that cannot be found from a Top N chain should also return an error - notFoundValidator := createStakingValidator(ctx, mocks, 5, 5, 5) - notFoundValidatorConsAddr, _ := notFoundValidator.GetConsAddr() + notFoundValidator := createStakingValidator(ctx, mocks, 5, 5) + notFoundValidatorConsAddr, err := notFoundValidator.GetConsAddr() + require.NoError(t, err) mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, notFoundValidatorConsAddr). Return(stakingtypes.Validator{}, stakingtypes.ErrNoValidatorFound) require.Error(t, providerKeeper.HandleOptOut(ctx, consumerId, types.NewProviderConsAddress(notFoundValidatorConsAddr))) @@ -237,13 +250,13 @@ func TestOptInTopNValidators(t *testing.T) { defer ctrl.Finish() // create 4 validators with powers 1, 2, 3, and 1 respectively - valA := createStakingValidator(ctx, mocks, 1, 1, 1) + valA := createStakingValidator(ctx, mocks, 1, 1) valAConsAddr, _ := valA.GetConsAddr() - valB := createStakingValidator(ctx, mocks, 2, 2, 2) + valB := createStakingValidator(ctx, mocks, 2, 2) valBConsAddr, _ := valB.GetConsAddr() - valC := createStakingValidator(ctx, mocks, 3, 3, 3) + valC := createStakingValidator(ctx, mocks, 3, 3) valCConsAddr, _ := valC.GetConsAddr() - valD := createStakingValidator(ctx, mocks, 4, 1, 4) + valD := createStakingValidator(ctx, mocks, 1, 4) valDConsAddr, _ := valD.GetConsAddr() // Start Test 1: opt in all validators with power >= 0 @@ -324,11 +337,11 @@ func TestComputeMinPowerInTopN(t *testing.T) { // 1 => 100% bondedValidators := []stakingtypes.Validator{ - createStakingValidator(ctx, mocks, 1, 5, 1), - createStakingValidator(ctx, mocks, 2, 10, 2), - createStakingValidator(ctx, mocks, 3, 3, 3), - createStakingValidator(ctx, mocks, 4, 1, 4), - createStakingValidator(ctx, mocks, 5, 6, 5), + createStakingValidator(ctx, mocks, 5, 1), + createStakingValidator(ctx, mocks, 10, 2), + createStakingValidator(ctx, mocks, 3, 3), + createStakingValidator(ctx, mocks, 1, 4), + createStakingValidator(ctx, mocks, 6, 5), } m, err := providerKeeper.ComputeMinPowerInTopN(ctx, bondedValidators, 100) @@ -385,43 +398,49 @@ func TestCanValidateChain(t *testing.T) { consumerID := "0" - validator := createStakingValidator(ctx, mocks, 0, 1, 1) // adds GetLastValidatorPower expectation to mocks + validator := createStakingValidator(ctx, mocks, 1, 1) // adds GetLastValidatorPower expectation to mocks consAddr, _ := validator.GetConsAddr() providerAddr := types.NewProviderConsAddress(consAddr) // with no allowlist or denylist, the validator has to be opted in, in order to consider it - require.False(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, 0)) + powerShapingParameters, err := providerKeeper.GetConsumerPowerShapingParameters(ctx, consumerID) + require.Error(t, err) + require.False(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, powerShapingParameters.Top_N, 0)) // with TopN chains, the validator can be considered, mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(gomock.Any(), providerAddr.Address).Return(validator, nil).Times(2) providerKeeper.SetConsumerPowerShapingParameters(ctx, consumerID, types.PowerShapingParameters{Top_N: 50}) + powerShapingParameters, err = providerKeeper.GetConsumerPowerShapingParameters(ctx, consumerID) + require.NoError(t, err) // validator's power is LT the min power - require.False(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, 2)) + require.False(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, powerShapingParameters.Top_N, 2)) // validator's power is GTE the min power - require.True(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, 1)) + require.True(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, powerShapingParameters.Top_N, 1)) // when validator is opted-in it can validate regardless of its min power providerKeeper.SetOptedIn(ctx, consumerID, types.NewProviderConsAddress(consAddr)) - require.True(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, 2)) + require.True(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, powerShapingParameters.Top_N, 2)) // With OptIn chains, validator can validate only if it has already opted-in providerKeeper.SetConsumerPowerShapingParameters(ctx, consumerID, types.PowerShapingParameters{Top_N: 0}) - require.True(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, 2)) + powerShapingParameters, err = providerKeeper.GetConsumerPowerShapingParameters(ctx, consumerID) + require.NoError(t, err) + require.True(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, powerShapingParameters.Top_N, 2)) // create an allow list but do not add the validator `providerAddr` to it - validatorA := createStakingValidator(ctx, mocks, 1, 1, 2) + validatorA := createStakingValidator(ctx, mocks, 1, 2) consAddrA, _ := validatorA.GetConsAddr() providerKeeper.SetAllowlist(ctx, consumerID, types.NewProviderConsAddress(consAddrA)) - require.False(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, 1)) + require.False(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, powerShapingParameters.Top_N, 1)) providerKeeper.SetAllowlist(ctx, consumerID, types.NewProviderConsAddress(consAddr)) - require.True(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, 1)) + require.True(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, powerShapingParameters.Top_N, 1)) // create a denylist but do not add validator `providerAddr` to it providerKeeper.SetDenylist(ctx, consumerID, types.NewProviderConsAddress(consAddrA)) - require.True(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, 1)) + require.True(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, powerShapingParameters.Top_N, 1)) // add validator `providerAddr` to the denylist providerKeeper.SetDenylist(ctx, consumerID, types.NewProviderConsAddress(consAddr)) - require.False(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, 1)) + require.False(t, providerKeeper.CanValidateChain(ctx, consumerID, providerAddr, powerShapingParameters.Top_N, 1)) } func TestCapValidatorSet(t *testing.T) { @@ -447,37 +466,49 @@ func TestCapValidatorSet(t *testing.T) { } validators := []types.ConsensusValidator{validatorA, validatorB, validatorC} - consumerValidators := providerKeeper.CapValidatorSet(ctx, "consumerId", validators) + powerShapingParameters, err := providerKeeper.GetConsumerPowerShapingParameters(ctx, "consumerId") + require.Error(t, err) + consumerValidators := providerKeeper.CapValidatorSet(ctx, powerShapingParameters, validators) require.Equal(t, validators, consumerValidators) providerKeeper.SetConsumerPowerShapingParameters(ctx, "consumerId", types.PowerShapingParameters{ ValidatorSetCap: 0, }) - consumerValidators = providerKeeper.CapValidatorSet(ctx, "consumerId", validators) + powerShapingParameters, err = providerKeeper.GetConsumerPowerShapingParameters(ctx, "consumerId") + require.NoError(t, err) + consumerValidators = providerKeeper.CapValidatorSet(ctx, powerShapingParameters, validators) require.Equal(t, validators, consumerValidators) providerKeeper.SetConsumerPowerShapingParameters(ctx, "consumerId", types.PowerShapingParameters{ ValidatorSetCap: 100, }) - consumerValidators = providerKeeper.CapValidatorSet(ctx, "consumerId", validators) + powerShapingParameters, err = providerKeeper.GetConsumerPowerShapingParameters(ctx, "consumerId") + require.NoError(t, err) + consumerValidators = providerKeeper.CapValidatorSet(ctx, powerShapingParameters, validators) require.Equal(t, validators, consumerValidators) providerKeeper.SetConsumerPowerShapingParameters(ctx, "consumerId", types.PowerShapingParameters{ ValidatorSetCap: 1, }) - consumerValidators = providerKeeper.CapValidatorSet(ctx, "consumerId", validators) + powerShapingParameters, err = providerKeeper.GetConsumerPowerShapingParameters(ctx, "consumerId") + require.NoError(t, err) + consumerValidators = providerKeeper.CapValidatorSet(ctx, powerShapingParameters, validators) require.Equal(t, []types.ConsensusValidator{validatorC}, consumerValidators) providerKeeper.SetConsumerPowerShapingParameters(ctx, "consumerId", types.PowerShapingParameters{ ValidatorSetCap: 2, }) - consumerValidators = providerKeeper.CapValidatorSet(ctx, "consumerId", validators) + powerShapingParameters, err = providerKeeper.GetConsumerPowerShapingParameters(ctx, "consumerId") + require.NoError(t, err) + consumerValidators = providerKeeper.CapValidatorSet(ctx, powerShapingParameters, validators) require.Equal(t, []types.ConsensusValidator{validatorC, validatorB}, consumerValidators) providerKeeper.SetConsumerPowerShapingParameters(ctx, "consumerId", types.PowerShapingParameters{ ValidatorSetCap: 3, }) - consumerValidators = providerKeeper.CapValidatorSet(ctx, "consumerId", validators) + powerShapingParameters, err = providerKeeper.GetConsumerPowerShapingParameters(ctx, "consumerId") + require.NoError(t, err) + consumerValidators = providerKeeper.CapValidatorSet(ctx, powerShapingParameters, validators) require.Equal(t, []types.ConsensusValidator{validatorC, validatorB, validatorA}, consumerValidators) } @@ -525,7 +556,9 @@ func TestCapValidatorsPower(t *testing.T) { } // no capping takes place because validators power-cap is not set - cappedValidators := providerKeeper.CapValidatorsPower(ctx, "consumerId", validators) + powerShapingParameters, err := providerKeeper.GetConsumerPowerShapingParameters(ctx, "consumerId") + require.Error(t, err) + cappedValidators := providerKeeper.CapValidatorsPower(ctx, powerShapingParameters.ValidatorsPowerCap, validators) sortValidators(validators) sortValidators(cappedValidators) require.Equal(t, validators, cappedValidators) @@ -533,7 +566,9 @@ func TestCapValidatorsPower(t *testing.T) { providerKeeper.SetConsumerPowerShapingParameters(ctx, "consumerId", types.PowerShapingParameters{ ValidatorsPowerCap: 33, }) - cappedValidators = providerKeeper.CapValidatorsPower(ctx, "consumerId", validators) + powerShapingParameters, err = providerKeeper.GetConsumerPowerShapingParameters(ctx, "consumerId") + require.NoError(t, err) + cappedValidators = providerKeeper.CapValidatorsPower(ctx, powerShapingParameters.ValidatorsPowerCap, validators) sortValidators(expectedValidators) sortValidators(cappedValidators) require.Equal(t, expectedValidators, cappedValidators) @@ -737,7 +772,7 @@ func findConsumerValidator(t *testing.T, v types.ConsensusValidator, valsAfter [ func createStakingValidatorsAndMocks(ctx sdk.Context, mocks testkeeper.MockedKeepers, powers ...int64) ([]stakingtypes.Validator, []types.ProviderConsAddress) { var validators []stakingtypes.Validator for i, power := range powers { - val := createStakingValidator(ctx, mocks, i, power, i) + val := createStakingValidator(ctx, mocks, power, i) val.Tokens = math.NewInt(power) val.Status = stakingtypes.Bonded validators = append(validators, val) @@ -792,11 +827,8 @@ func TestFulfillsMinStake(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - providerKeeper.SetConsumerPowerShapingParameters(ctx, "consumerId", types.PowerShapingParameters{ - MinStake: tc.minStake, - }) for i, valAddr := range consAddrs { - result := providerKeeper.FulfillsMinStake(ctx, "consumerId", valAddr) + result := providerKeeper.FulfillsMinStake(ctx, tc.minStake, valAddr) require.Equal(t, tc.expectedFulfill[i], result) } }) @@ -834,8 +866,11 @@ func TestIfInactiveValsDisallowedProperty(t *testing.T) { params.MaxProviderConsensusValidators = int64(maxProviderConsensusVals) providerKeeper.SetParams(ctx, params) + powerShapingParameters, err := providerKeeper.GetConsumerPowerShapingParameters(ctx, "consumerId") + require.NoError(t, err) + // Compute the next validators - nextVals := providerKeeper.ComputeNextValidators(ctx, "consumerId", vals, 0) + nextVals := providerKeeper.ComputeNextValidators(ctx, "consumerId", vals, powerShapingParameters, 0) // Check that the length of nextVals is at most maxProviderConsensusVals require.LessOrEqual(r, len(nextVals), int(maxProviderConsensusVals), "The length of nextVals should be at most maxProviderConsensusVals") diff --git a/x/ccv/provider/keeper/permissionless.go b/x/ccv/provider/keeper/permissionless.go index e77d152bd2..f684f8b80d 100644 --- a/x/ccv/provider/keeper/permissionless.go +++ b/x/ccv/provider/keeper/permissionless.go @@ -203,6 +203,14 @@ func (k Keeper) DeleteConsumerPhase(ctx sdk.Context, consumerId string) { store.Delete(types.ConsumerIdToPhaseKey(consumerId)) } +// IsConsumerActive checks if a consumer chain is either registered, initialized, or launched. +func (k Keeper) IsConsumerActive(ctx sdk.Context, consumerId string) bool { + phase := k.GetConsumerPhase(ctx, consumerId) + return phase == types.ConsumerPhase_CONSUMER_PHASE_REGISTERED || + phase == types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED || + phase == types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED +} + // GetConsumerStopTime returns the stop time associated with the to-be-stopped chain with consumer id func (k Keeper) GetConsumerStopTime(ctx sdk.Context, consumerId string) (time.Time, error) { store := ctx.KVStore(k.storeKey) @@ -321,13 +329,13 @@ func (k Keeper) GetConsumersToBeLaunched(ctx sdk.Context, spawnTime time.Time) ( return k.getConsumerIdsBasedOnTime(ctx, types.SpawnTimeToConsumerIdsKey, spawnTime) } -// AppendConsumerToBeLaunchedOnSpawnTime appends the provider consumer id for the given spawn time -func (k Keeper) AppendConsumerToBeLaunchedOnSpawnTime(ctx sdk.Context, consumerId string, spawnTime time.Time) error { +// AppendConsumerToBeLaunched appends the provider consumer id for the given spawn time +func (k Keeper) AppendConsumerToBeLaunched(ctx sdk.Context, consumerId string, spawnTime time.Time) error { return k.appendConsumerIdOnTime(ctx, consumerId, types.SpawnTimeToConsumerIdsKey, spawnTime) } -// RemoveConsumerToBeLaunchedFromSpawnTime removes consumer id from if stored for this specific spawn time -func (k Keeper) RemoveConsumerToBeLaunchedFromSpawnTime(ctx sdk.Context, consumerId string, spawnTime time.Time) error { +// RemoveConsumerToBeLaunched removes consumer id from if stored for this specific spawn time +func (k Keeper) RemoveConsumerToBeLaunched(ctx sdk.Context, consumerId string, spawnTime time.Time) error { return k.removeConsumerIdFromTime(ctx, consumerId, types.SpawnTimeToConsumerIdsKey, spawnTime) } @@ -336,13 +344,13 @@ func (k Keeper) GetConsumersToBeStopped(ctx sdk.Context, stopTime time.Time) (ty return k.getConsumerIdsBasedOnTime(ctx, types.StopTimeToConsumerIdsKey, stopTime) } -// AppendConsumerToBeStoppedOnStopTime appends the provider consumer id for the given stop time -func (k Keeper) AppendConsumerToBeStoppedOnStopTime(ctx sdk.Context, consumerId string, stopTime time.Time) error { +// AppendConsumerToBeStopped appends the provider consumer id for the given stop time +func (k Keeper) AppendConsumerToBeStopped(ctx sdk.Context, consumerId string, stopTime time.Time) error { return k.appendConsumerIdOnTime(ctx, consumerId, types.StopTimeToConsumerIdsKey, stopTime) } -// RemoveConsumerToBeStoppedFromStopTime removes consumer id from if stored for this specific stop time -func (k Keeper) RemoveConsumerToBeStoppedFromStopTime(ctx sdk.Context, consumerId string, stopTime time.Time) error { +// RemoveConsumerToBeStopped removes consumer id from if stored for this specific stop time +func (k Keeper) RemoveConsumerToBeStopped(ctx sdk.Context, consumerId string, stopTime time.Time) error { return k.removeConsumerIdFromTime(ctx, consumerId, types.StopTimeToConsumerIdsKey, stopTime) } @@ -641,12 +649,12 @@ func (k Keeper) PrepareConsumerForLaunch(ctx sdk.Context, consumerId string, pre if !previousSpawnTime.Equal(time.Time{}) { // if this is not the first initialization and hence `previousSpawnTime` does not contain the zero value of `Time` // remove the consumer id from the previous spawn time - err := k.RemoveConsumerToBeLaunchedFromSpawnTime(ctx, consumerId, previousSpawnTime) + err := k.RemoveConsumerToBeLaunched(ctx, consumerId, previousSpawnTime) if err != nil { return err } } - return k.AppendConsumerToBeLaunchedOnSpawnTime(ctx, consumerId, spawnTime) + return k.AppendConsumerToBeLaunched(ctx, consumerId, spawnTime) } // CanLaunch checks on whether the consumer with `consumerId` has set all the initialization parameters set and hence diff --git a/x/ccv/provider/keeper/permissionless_test.go b/x/ccv/provider/keeper/permissionless_test.go index da8412220e..f4eb4922c1 100644 --- a/x/ccv/provider/keeper/permissionless_test.go +++ b/x/ccv/provider/keeper/permissionless_test.go @@ -263,28 +263,28 @@ func TestConsumerStopTime(t *testing.T) { require.Error(t, err) } -// TestConsumersToBeLaunched tests `AppendConsumerToBeLaunchedOnSpawnTime`, `GetConsumersToBeLaunched`, and `RemoveConsumerToBeLaunchedFromSpawnTime` +// TestConsumersToBeLaunched tests `AppendConsumerToBeLaunched`, `GetConsumersToBeLaunched`, and `RemoveConsumerToBeLaunched` func TestConsumersToBeLaunched(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() spawnTime := time.Now() - providerKeeper.AppendConsumerToBeLaunchedOnSpawnTime(ctx, "consumerId1", spawnTime) + providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId1", spawnTime) consumers, err := providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) require.NoError(t, err) require.Equal(t, []string{"consumerId1"}, consumers.Ids) - providerKeeper.AppendConsumerToBeLaunchedOnSpawnTime(ctx, "consumerId2", spawnTime) + providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId2", spawnTime) consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) require.NoError(t, err) require.Equal(t, []string{"consumerId1", "consumerId2"}, consumers.Ids) - providerKeeper.AppendConsumerToBeLaunchedOnSpawnTime(ctx, "consumerId3", spawnTime) + providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId3", spawnTime) consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) require.NoError(t, err) require.Equal(t, []string{"consumerId1", "consumerId2", "consumerId3"}, consumers.Ids) - err = providerKeeper.RemoveConsumerToBeLaunchedFromSpawnTime(ctx, "consumerId2", spawnTime) + err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId2", spawnTime) require.NoError(t, err) consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) require.NoError(t, err) @@ -292,7 +292,7 @@ func TestConsumersToBeLaunched(t *testing.T) { // also add consumer ids under a different spawn time and verify everything under the original spawn time is still there spawnTimePlusOneHour := spawnTime.Add(time.Hour) - providerKeeper.AppendConsumerToBeLaunchedOnSpawnTime(ctx, "consumerId4", spawnTimePlusOneHour) + providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId4", spawnTimePlusOneHour) consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTimePlusOneHour) require.NoError(t, err) require.Equal(t, []string{"consumerId4"}, consumers.Ids) @@ -302,51 +302,51 @@ func TestConsumersToBeLaunched(t *testing.T) { require.Equal(t, []string{"consumerId1", "consumerId3"}, consumers.Ids) // start removing all consumers from `spawnTime` - err = providerKeeper.RemoveConsumerToBeLaunchedFromSpawnTime(ctx, "consumerId3", spawnTime) + err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId3", spawnTime) require.NoError(t, err) - err = providerKeeper.RemoveConsumerToBeLaunchedFromSpawnTime(ctx, "consumerId1", spawnTime) + err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId1", spawnTime) require.NoError(t, err) consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) require.NoError(t, err) require.Empty(t, consumers.Ids) // remove from `spawnTimePlusOneHour` - err = providerKeeper.RemoveConsumerToBeLaunchedFromSpawnTime(ctx, "consumerId4", spawnTimePlusOneHour) + err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId4", spawnTimePlusOneHour) require.NoError(t, err) consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTimePlusOneHour) require.NoError(t, err) require.Empty(t, consumers.Ids) // add another consumer for `spawnTime` - err = providerKeeper.AppendConsumerToBeLaunchedOnSpawnTime(ctx, "consumerId5", spawnTime) + err = providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId5", spawnTime) require.NoError(t, err) consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) require.NoError(t, err) require.Equal(t, []string{"consumerId5"}, consumers.Ids) } -// TestConsumersToBeStopped tests `AppendConsumerToBeLaunchedOnSpawnTime`, `GetConsumersToBeLaunched`, and `RemoveConsumerToBeLaunchedFromSpawnTime` +// TestConsumersToBeStopped tests `AppendConsumerToBeLaunched`, `GetConsumersToBeLaunched`, and `RemoveConsumerToBeLaunched` func TestConsumersToBeStopped(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() stopTime := time.Now() - providerKeeper.AppendConsumerToBeStoppedOnStopTime(ctx, "consumerId1", stopTime) + providerKeeper.AppendConsumerToBeStopped(ctx, "consumerId1", stopTime) consumers, err := providerKeeper.GetConsumersToBeStopped(ctx, stopTime) require.NoError(t, err) require.Equal(t, []string{"consumerId1"}, consumers.Ids) - providerKeeper.AppendConsumerToBeStoppedOnStopTime(ctx, "consumerId2", stopTime) + providerKeeper.AppendConsumerToBeStopped(ctx, "consumerId2", stopTime) consumers, err = providerKeeper.GetConsumersToBeStopped(ctx, stopTime) require.NoError(t, err) require.Equal(t, []string{"consumerId1", "consumerId2"}, consumers.Ids) - providerKeeper.AppendConsumerToBeStoppedOnStopTime(ctx, "consumerId3", stopTime) + providerKeeper.AppendConsumerToBeStopped(ctx, "consumerId3", stopTime) consumers, err = providerKeeper.GetConsumersToBeStopped(ctx, stopTime) require.NoError(t, err) require.Equal(t, []string{"consumerId1", "consumerId2", "consumerId3"}, consumers.Ids) - err = providerKeeper.RemoveConsumerToBeStoppedFromStopTime(ctx, "consumerId2", stopTime) + err = providerKeeper.RemoveConsumerToBeStopped(ctx, "consumerId2", stopTime) require.NoError(t, err) consumers, err = providerKeeper.GetConsumersToBeStopped(ctx, stopTime) require.NoError(t, err) @@ -354,7 +354,7 @@ func TestConsumersToBeStopped(t *testing.T) { // also add consumer ids under a different stop time and verify everything under the original stop time is still there stopTimePlusOneHour := stopTime.Add(time.Hour) - providerKeeper.AppendConsumerToBeStoppedOnStopTime(ctx, "consumerId4", stopTimePlusOneHour) + providerKeeper.AppendConsumerToBeStopped(ctx, "consumerId4", stopTimePlusOneHour) consumers, err = providerKeeper.GetConsumersToBeStopped(ctx, stopTimePlusOneHour) require.NoError(t, err) require.Equal(t, []string{"consumerId4"}, consumers.Ids) @@ -364,23 +364,23 @@ func TestConsumersToBeStopped(t *testing.T) { require.Equal(t, []string{"consumerId1", "consumerId3"}, consumers.Ids) // start removing all consumers from `stopTime` - err = providerKeeper.RemoveConsumerToBeStoppedFromStopTime(ctx, "consumerId3", stopTime) + err = providerKeeper.RemoveConsumerToBeStopped(ctx, "consumerId3", stopTime) require.NoError(t, err) - err = providerKeeper.RemoveConsumerToBeStoppedFromStopTime(ctx, "consumerId1", stopTime) + err = providerKeeper.RemoveConsumerToBeStopped(ctx, "consumerId1", stopTime) require.NoError(t, err) consumers, err = providerKeeper.GetConsumersToBeStopped(ctx, stopTime) require.NoError(t, err) require.Empty(t, consumers.Ids) // remove from `stopTimePlusOneHour` - err = providerKeeper.RemoveConsumerToBeStoppedFromStopTime(ctx, "consumerId4", stopTimePlusOneHour) + err = providerKeeper.RemoveConsumerToBeStopped(ctx, "consumerId4", stopTimePlusOneHour) require.NoError(t, err) consumers, err = providerKeeper.GetConsumersToBeStopped(ctx, stopTimePlusOneHour) require.NoError(t, err) require.Empty(t, consumers.Ids) // add another consumer for `stopTime` - err = providerKeeper.AppendConsumerToBeStoppedOnStopTime(ctx, "consumerId5", stopTime) + err = providerKeeper.AppendConsumerToBeStopped(ctx, "consumerId5", stopTime) require.NoError(t, err) consumers, err = providerKeeper.GetConsumersToBeStopped(ctx, stopTime) require.NoError(t, err) @@ -474,9 +474,9 @@ func TestGetInitializedConsumersReadyToLaunch(t *testing.T) { // no chains to-be-launched exist require.Empty(t, providerKeeper.GetInitializedConsumersReadyToLaunch(ctx, 5)) - providerKeeper.AppendConsumerToBeLaunchedOnSpawnTime(ctx, "consumerId1", time.Unix(10, 0)) - providerKeeper.AppendConsumerToBeLaunchedOnSpawnTime(ctx, "consumerId2", time.Unix(20, 0)) - providerKeeper.AppendConsumerToBeLaunchedOnSpawnTime(ctx, "consumerId3", time.Unix(30, 0)) + providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId1", time.Unix(10, 0)) + providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId2", time.Unix(20, 0)) + providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId3", time.Unix(30, 0)) // time has not yet reached the spawn time of "consumerId1" ctx = ctx.WithBlockTime(time.Unix(9, 999999999)) @@ -507,9 +507,9 @@ func TestGetLaunchedConsumersReadyToStop(t *testing.T) { // no chains to-be-stopped exist require.Empty(t, providerKeeper.GetLaunchedConsumersReadyToStop(ctx, 3)) - providerKeeper.AppendConsumerToBeStoppedOnStopTime(ctx, "consumerId1", time.Unix(10, 0)) - providerKeeper.AppendConsumerToBeStoppedOnStopTime(ctx, "consumerId2", time.Unix(20, 0)) - providerKeeper.AppendConsumerToBeStoppedOnStopTime(ctx, "consumerId3", time.Unix(30, 0)) + providerKeeper.AppendConsumerToBeStopped(ctx, "consumerId1", time.Unix(10, 0)) + providerKeeper.AppendConsumerToBeStopped(ctx, "consumerId2", time.Unix(20, 0)) + providerKeeper.AppendConsumerToBeStopped(ctx, "consumerId3", time.Unix(30, 0)) // time has not yet reached the stop time of "consumerId1" ctx = ctx.WithBlockTime(time.Unix(9, 999999999)) @@ -585,9 +585,9 @@ func TestUpdateMinimumPowerInTopN(t *testing.T) { // test cases where Top N > 0 and for this we mock some validators powers := []int64{10, 20, 30} validators := []stakingtypes.Validator{ - createStakingValidator(ctx, mocks, 1, powers[0], 1), // this validator has ~16 of the total voting power - createStakingValidator(ctx, mocks, 2, powers[1], 2), // this validator has ~33% of the total voting gpower - createStakingValidator(ctx, mocks, 3, powers[2], 3), // this validator has 50% of the total voting power + createStakingValidator(ctx, mocks, powers[0], 1), // this validator has ~16 of the total voting power + createStakingValidator(ctx, mocks, powers[1], 2), // this validator has ~33% of the total voting gpower + createStakingValidator(ctx, mocks, powers[2], 3), // this validator has 50% of the total voting power } mocks.MockStakingKeeper.EXPECT().GetBondedValidatorsByPower(gomock.Any()).Return(validators, nil).AnyTimes() @@ -736,6 +736,7 @@ func TestHasAtMostOnceCorrectMsgUpdateConsumer(t *testing.T) { // a proposal with 2 `MsgUpdateConsumer` messages invalidProposal, err := govv1.NewProposal([]sdk.Msg{&expectedMsgUpdateConsumer, &expectedMsgUpdateConsumer}, 1, time.Now(), time.Now().Add(1*time.Hour), "metadata", "title", "summary", sdk.AccAddress{}, false) + require.NoError(t, err) actualMsgUpdateConsumer, err = providerKeeper.HasAtMostOnceCorrectMsgUpdateConsumer(ctx, &invalidProposal) require.ErrorContains(t, err, "proposal can contain at most one") require.Nil(t, actualMsgUpdateConsumer) diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 03e1293645..6d95a1679f 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -183,21 +183,13 @@ func (k Keeper) MakeConsumerGenesis( ) (gen ccv.ConsumerGenesisState, nextValidatorsHash []byte, err error) { initializationRecord, err := k.GetConsumerInitializationParameters(ctx, consumerId) if err != nil { - return gen, nil, errorsmod.Wrapf(types.ErrInvalidConsumerInitializationParameters, - "initialization record for consumer id: %s is missing", consumerId) - + return gen, nil, errorsmod.Wrapf(ccv.ErrInvalidConsumerState, + "cannot retrieve initialization parameters: %s", err.Error()) } powerShapingParameters, err := k.GetConsumerPowerShapingParameters(ctx, consumerId) if err != nil { - powerShapingParameters = types.PowerShapingParameters{ - Top_N: 0, - ValidatorsPowerCap: 0, - ValidatorSetCap: 0, - Allowlist: []string{}, - Denylist: []string{}, - MinStake: 0, - AllowInactiveVals: false, - } + return gen, nil, errorsmod.Wrapf(ccv.ErrInvalidConsumerState, + "cannot retrieve power shaping parameters: %s", err.Error()) } providerUnbondingPeriod, err := k.stakingKeeper.UnbondingTime(ctx) @@ -256,7 +248,7 @@ func (k Keeper) MakeConsumerGenesis( } // need to use the bondedValidators, not activeValidators, here since the chain might be opt-in and allow inactive vals - nextValidators := k.ComputeNextValidators(ctx, consumerId, bondedValidators, minPower) + nextValidators := k.ComputeNextValidators(ctx, consumerId, bondedValidators, powerShapingParameters, minPower) k.SetConsumerValSet(ctx, consumerId, nextValidators) // get the initial updates with the latest set consumer public keys @@ -307,7 +299,7 @@ func (k Keeper) BeginBlockInit(ctx sdk.Context) { } // Remove consumer to prevent re-trying launching this chain. // We would only try to re-launch this chain if a new `MsgUpdateConsumer` message is sent. - k.RemoveConsumerToBeLaunchedFromSpawnTime(ctx, consumerId, record.SpawnTime) + k.RemoveConsumerToBeLaunched(ctx, consumerId, record.SpawnTime) cachedCtx, writeFn := ctx.CacheContext() err = k.LaunchConsumer(cachedCtx, consumerId) @@ -334,7 +326,7 @@ func (k Keeper) BeginBlockCCR(ctx sdk.Context) { stopTime, err := k.GetConsumerStopTime(ctx, consumerId) if err != nil { - k.Logger(ctx).Info("chain could not be stopped", + k.Logger(ctx).Error("chain could not be stopped", "consumerId", consumerId, "err", err.Error()) continue @@ -349,7 +341,7 @@ func (k Keeper) BeginBlockCCR(ctx sdk.Context) { } k.SetConsumerPhase(cachedCtx, consumerId, types.ConsumerPhase_CONSUMER_PHASE_STOPPED) - k.RemoveConsumerToBeStoppedFromStopTime(ctx, consumerId, stopTime) + k.RemoveConsumerToBeStopped(ctx, consumerId, stopTime) // The cached context is created with a new EventManager so we merge the event into the original context ctx.EventManager().EmitEvents(cachedCtx.EventManager().Events()) diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index eaa7d76ec9..9e5e094eb2 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -348,6 +348,7 @@ func TestMakeConsumerGenesis(t *testing.T) { providerKeeper.SetConsumerChainId(ctx, "0", "testchain1") providerKeeper.SetConsumerMetadata(ctx, "0", consumerMetadata) providerKeeper.SetConsumerInitializationParameters(ctx, "0", initializationParameters) + providerKeeper.SetConsumerPowerShapingParameters(ctx, "0", providertypes.PowerShapingParameters{}) actualGenesis, _, err := providerKeeper.MakeConsumerGenesis(ctx, "0") require.NoError(t, err) @@ -612,7 +613,7 @@ func TestBeginBlockInit(t *testing.T) { providerKeeper.SetConsumerInitializationParameters(ctx, fmt.Sprintf("%d", i), r) // set up the chains in their initialized phase, hence they could launch providerKeeper.SetConsumerPhase(ctx, fmt.Sprintf("%d", i), providertypes.ConsumerPhase_CONSUMER_PHASE_INITIALIZED) - providerKeeper.AppendConsumerToBeLaunchedOnSpawnTime(ctx, fmt.Sprintf("%d", i), r.SpawnTime) + providerKeeper.AppendConsumerToBeLaunched(ctx, fmt.Sprintf("%d", i), r.SpawnTime) } for i, r := range powerShapingParameters { providerKeeper.SetConsumerPowerShapingParameters(ctx, fmt.Sprintf("%d", i), r) @@ -677,17 +678,17 @@ func TestBeginBlockCCR(t *testing.T) { chainIds := []string{"chain1", "chain2", "chain3"} consumerIds := []string{"consumerId1", "consumerId2", "consumerId3"} providerKeeper.SetConsumerStopTime(ctx, consumerIds[0], now.Add(-time.Hour)) - providerKeeper.AppendConsumerToBeStoppedOnStopTime(ctx, consumerIds[0], now.Add(-time.Hour)) + providerKeeper.AppendConsumerToBeStopped(ctx, consumerIds[0], now.Add(-time.Hour)) providerKeeper.SetConsumerStopTime(ctx, consumerIds[1], now) - providerKeeper.AppendConsumerToBeStoppedOnStopTime(ctx, consumerIds[1], now) + providerKeeper.AppendConsumerToBeStopped(ctx, consumerIds[1], now) providerKeeper.SetConsumerStopTime(ctx, consumerIds[2], now.Add(time.Hour)) - providerKeeper.AppendConsumerToBeStoppedOnStopTime(ctx, consumerIds[2], now.Add(time.Hour)) + providerKeeper.AppendConsumerToBeStopped(ctx, consumerIds[2], now.Add(time.Hour)) // // Mock expectations // expectations := []*gomock.Call{} - for i, _ := range consumerIds { + for i := range consumerIds { chainId := chainIds[i] // A consumer chain is setup corresponding to each consumerId, making these mocks necessary testkeeper.SetupMocksForLastBondedValidatorsExpectation(mocks.MockStakingKeeper, 0, []stakingtypes.Validator{}, 1) diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index cfa8ec5a9f..88369568ff 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -15,7 +15,6 @@ import ( abci "github.com/cometbft/cometbft/abci/types" - "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" providertypes "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" ccv "github.com/cosmos/interchain-security/v5/x/ccv/types" ) @@ -95,7 +94,7 @@ func (k Keeper) ProviderValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate panic(fmt.Errorf("failed to get last provider consensus validator set: %w", err)) } - nextValidators := []types.ConsensusValidator{} + nextValidators := []providertypes.ConsensusValidator{} maxValidators := k.GetMaxProviderConsensusValidators(ctx) // avoid out of range errors by bounding the max validators to the number of bonded validators if maxValidators > int64(len(bondedValidators)) { @@ -195,24 +194,27 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { for _, consumerId := range k.GetAllRegisteredConsumerIds(ctx) { currentValidators, err := k.GetConsumerValSet(ctx, consumerId) if err != nil { - panic(fmt.Errorf("failed to get consumer validators: %w", err)) + panic(fmt.Errorf("failed to get consumer validators, consumerId(%s): %w", consumerId, err)) + } + powerShapingParameters, err := k.GetConsumerPowerShapingParameters(ctx, consumerId) + if err != nil { + panic(fmt.Errorf("failed to get consumer power shaping parameters: %w", err)) } - topN := k.GetTopN(ctx, consumerId) minPower := int64(0) - if topN > 0 { + if powerShapingParameters.Top_N > 0 { // in a Top-N chain, we automatically opt in all validators that belong to the top N // of the active validators activeValidators, err := k.GetLastProviderConsensusActiveValidators(ctx) if err != nil { // something must be broken in the bonded validators, so we have to panic since there is no realistic way to proceed - panic(fmt.Errorf("failed to get active validators: %w", err)) + panic(fmt.Errorf("failed to get active validators, consumerId(%s): %w", consumerId, err)) } - minPower, err = k.ComputeMinPowerInTopN(ctx, activeValidators, topN) + minPower, err = k.ComputeMinPowerInTopN(ctx, activeValidators, powerShapingParameters.Top_N) if err != nil { // we panic, since the only way to proceed would be to opt in all validators, which is not the intended behavior - panic(fmt.Errorf("failed to compute min power to opt in for chain %v: %w", consumerId, err)) + panic(fmt.Errorf("failed to compute min power to opt in, consumerId(%s): %w", consumerId, err)) } // set the minimal power of validators in the top N in the store @@ -221,7 +223,7 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { k.OptInTopNValidators(ctx, consumerId, activeValidators, minPower) } - nextValidators := k.ComputeNextValidators(ctx, consumerId, bondedValidators, minPower) + nextValidators := k.ComputeNextValidators(ctx, consumerId, bondedValidators, powerShapingParameters, minPower) valUpdates := DiffValidators(currentValidators, nextValidators) k.SetConsumerValSet(ctx, consumerId, nextValidators) diff --git a/x/ccv/provider/keeper/relay_test.go b/x/ccv/provider/keeper/relay_test.go index db8c991d7f..fb27247263 100644 --- a/x/ccv/provider/keeper/relay_test.go +++ b/x/ccv/provider/keeper/relay_test.go @@ -94,11 +94,11 @@ func TestQueueVSCPacketsDoesNotResetConsumerValidatorsHeights(t *testing.T) { providerKeeper.SetParams(ctx, providertypes.DefaultParams()) // mock 2 bonded validators - valA := createStakingValidator(ctx, mocks, 1, 1, 1) + valA := createStakingValidator(ctx, mocks, 1, 1) valAConsAddr, _ := valA.GetConsAddr() valAPubKey, _ := valA.TmConsPublicKey() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valAConsAddr).Return(valA, nil).AnyTimes() - valB := createStakingValidator(ctx, mocks, 2, 2, 2) + valB := createStakingValidator(ctx, mocks, 2, 2) valBConsAddr, _ := valB.GetConsAddr() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valBConsAddr).Return(valB, nil).AnyTimes() testkeeper.SetupMocksForLastBondedValidatorsExpectation(mocks.MockStakingKeeper, 2, []stakingtypes.Validator{valA, valB}, -1) @@ -120,6 +120,10 @@ func TestQueueVSCPacketsDoesNotResetConsumerValidatorsHeights(t *testing.T) { // validator for the first time after the `QueueVSCPackets` call. providerKeeper.SetOptedIn(ctx, "consumerId", providertypes.NewProviderConsAddress(valBConsAddr)) + // set power shaping params + err := providerKeeper.SetConsumerPowerShapingParameters(ctx, "consumerId", providertypes.PowerShapingParameters{}) + require.NoError(t, err) + providerKeeper.QueueVSCPackets(ctx) // the height of consumer validator A should not be modified because A was already a consumer validator @@ -596,14 +600,12 @@ func TestEndBlockVSU(t *testing.T) { // create 4 sample lastValidators var lastValidators []stakingtypes.Validator - var powers []int64 for i := 0; i < 4; i++ { validator := cryptotestutil.NewCryptoIdentityFromIntSeed(i).SDKStakingValidator() lastValidators = append(lastValidators, validator) valAdrr, err := sdk.ValAddressFromBech32(validator.GetOperator()) require.NoError(t, err) mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), valAdrr).Return(int64(i+1), nil).AnyTimes() - powers = append(powers, int64(i+1)) } testkeeper.SetupMocksForLastBondedValidatorsExpectation(mocks.MockStakingKeeper, 5, lastValidators, -1) @@ -649,14 +651,14 @@ func TestProviderValidatorUpdates(t *testing.T) { // Mocking bonded validators in the staking keeper. // be aware that the powers need to be in descending order validators := []stakingtypes.Validator{ - createStakingValidator(ctx, mocks, 3, 30, 3), - createStakingValidator(ctx, mocks, 2, 20, 2), - createStakingValidator(ctx, mocks, 1, 10, 1), + createStakingValidator(ctx, mocks, 30, 3), + createStakingValidator(ctx, mocks, 20, 2), + createStakingValidator(ctx, mocks, 10, 1), } mocks.MockStakingKeeper.EXPECT().GetBondedValidatorsByPower(ctx).Return(validators, nil).Times(1) // set up a validator that we will only use for the last provider consensus validator set - removedValidator := createStakingValidator(ctx, mocks, 4, 40, 4) + removedValidator := createStakingValidator(ctx, mocks, 40, 4) // Set up the last provider consensus validators consensusVals := make([]providertypes.ConsensusValidator, 0, len(validators)) @@ -715,22 +717,22 @@ func TestQueueVSCPacketsWithPowerCapping(t *testing.T) { providerKeeper.SetValidatorSetUpdateId(ctx, 1) - valA := createStakingValidator(ctx, mocks, 1, 1, 1) // 3.125% of the total voting power + valA := createStakingValidator(ctx, mocks, 1, 1) // 3.125% of the total voting power valAConsAddr, _ := valA.GetConsAddr() valAPubKey, _ := valA.TmConsPublicKey() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valAConsAddr).Return(valA, nil).AnyTimes() - valB := createStakingValidator(ctx, mocks, 2, 3, 2) // 9.375% of the total voting power + valB := createStakingValidator(ctx, mocks, 3, 2) // 9.375% of the total voting power valBConsAddr, _ := valB.GetConsAddr() valBPubKey, _ := valB.TmConsPublicKey() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valBConsAddr).Return(valB, nil).AnyTimes() - valC := createStakingValidator(ctx, mocks, 3, 4, 3) // 12.5% of the total voting power + valC := createStakingValidator(ctx, mocks, 4, 3) // 12.5% of the total voting power valCConsAddr, _ := valC.GetConsAddr() valCPubKey, _ := valC.TmConsPublicKey() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valCConsAddr).Return(valC, nil).AnyTimes() - valD := createStakingValidator(ctx, mocks, 4, 8, 4) // 25% of the total voting power + valD := createStakingValidator(ctx, mocks, 8, 4) // 25% of the total voting power valDConsAddr, _ := valD.GetConsAddr() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valDConsAddr).Return(valD, nil).AnyTimes() - valE := createStakingValidator(ctx, mocks, 5, 16, 5) // 50% of the total voting power + valE := createStakingValidator(ctx, mocks, 16, 5) // 50% of the total voting power valEConsAddr, _ := valE.GetConsAddr() valEPubKey, _ := valE.TmConsPublicKey() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valEConsAddr).Return(valE, nil).AnyTimes() diff --git a/x/ccv/provider/keeper/staking_keeper_interface_test.go b/x/ccv/provider/keeper/staking_keeper_interface_test.go index 2eb9c9f1bd..40cfb5c0d7 100644 --- a/x/ccv/provider/keeper/staking_keeper_interface_test.go +++ b/x/ccv/provider/keeper/staking_keeper_interface_test.go @@ -6,7 +6,6 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/staking/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" testkeeper "github.com/cosmos/interchain-security/v5/testutil/keeper" "github.com/golang/mock/gomock" @@ -107,7 +106,7 @@ func TestStakingKeeperInterface(t *testing.T) { return nil }).AnyTimes() actualValPowers := []int64{} - err := providerKeeper.IterateBondedValidatorsByPower(ctx, func(index int64, validator types.ValidatorI) (stop bool) { + err := providerKeeper.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) { counter++ actualValPowers = append(actualValPowers, validator.GetTokens().Int64()) return false diff --git a/x/ccv/provider/keeper/validator_set_update_test.go b/x/ccv/provider/keeper/validator_set_update_test.go index 58ef556aec..aa9ae6abba 100644 --- a/x/ccv/provider/keeper/validator_set_update_test.go +++ b/x/ccv/provider/keeper/validator_set_update_test.go @@ -111,7 +111,7 @@ func createConsumerValidator(index int, power int64, seed int) (types.ConsensusV } // createStakingValidator helper function to generate a validator with the given power and with a provider address based on index -func createStakingValidator(ctx sdk.Context, mocks testkeeper.MockedKeepers, index int, power int64, seed int) stakingtypes.Validator { +func createStakingValidator(ctx sdk.Context, mocks testkeeper.MockedKeepers, power int64, seed int) stakingtypes.Validator { providerConsPubKey := cryptotestutil.NewCryptoIdentityFromIntSeed(seed).TMProtoCryptoPublicKey() pk, _ := cryptocodec.FromCmtProtoPublicKey(providerConsPubKey) @@ -348,7 +348,7 @@ func TestFilterValidatorsConsiderAll(t *testing.T) { var expectedValidators []types.ConsensusValidator // create a staking validator A that has not set a consumer public key - valA := createStakingValidator(ctx, mocks, 1, 1, 1) + valA := createStakingValidator(ctx, mocks, 1, 1) // because validator A has no consumer key set, the `PublicKey` we expect is the key on the provider chain valAConsAddr, _ := valA.GetConsAddr() valAPublicKey, _ := valA.TmConsPublicKey() @@ -359,7 +359,7 @@ func TestFilterValidatorsConsiderAll(t *testing.T) { }) // create a staking validator B that has set a consumer public key - valB := createStakingValidator(ctx, mocks, 2, 2, 2) + valB := createStakingValidator(ctx, mocks, 2, 2) // validator B has set a consumer key, the `PublicKey` we expect is the key set by `SetValidatorConsumerPubKey` valBConsumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() valBConsAddr, _ := valB.GetConsAddr() @@ -390,7 +390,7 @@ func TestFilterValidatorsConsiderOnlyOptIn(t *testing.T) { var expectedValidators []types.ConsensusValidator // create a staking validator A that has not set a consumer public key - valA := createStakingValidator(ctx, mocks, 1, 1, 1) + valA := createStakingValidator(ctx, mocks, 1, 1) // because validator A has no consumer key set, the `PublicKey` we expect is the key on the provider chain valAConsAddr, _ := valA.GetConsAddr() valAPublicKey, _ := valA.TmConsPublicKey() @@ -402,7 +402,7 @@ func TestFilterValidatorsConsiderOnlyOptIn(t *testing.T) { expectedValidators = append(expectedValidators, expectedValAConsumerValidator) // create a staking validator B that has set a consumer public key - valB := createStakingValidator(ctx, mocks, 2, 2, 2) + valB := createStakingValidator(ctx, mocks, 2, 2) // validator B has set a consumer key, the `PublicKey` we expect is the key set by `SetValidatorConsumerPubKey` valBConsumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() valBConsAddr, _ := valB.GetConsAddr() @@ -437,7 +437,7 @@ func TestFilterValidatorsConsiderOnlyOptIn(t *testing.T) { require.Equal(t, expectedValidators, actualValidators) // create a staking validator C that is not opted in, hence `expectedValidators` remains the same - valC := createStakingValidator(ctx, mocks, 3, 3, 3) + valC := createStakingValidator(ctx, mocks, 3, 3) bondedValidators = []stakingtypes.Validator{valA, valB, valC} actualValidators = providerKeeper.FilterValidators(ctx, "consumerId", bondedValidators, func(providerAddr types.ProviderConsAddress) bool { @@ -456,7 +456,7 @@ func TestCreateConsumerValidator(t *testing.T) { chainID := "consumerId" // create a validator which has set a consumer public key - valA := createStakingValidator(ctx, mocks, 0, 1, 1) + valA := createStakingValidator(ctx, mocks, 1, 1) valAConsumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() valAConsAddr, _ := valA.GetConsAddr() valAProviderConsAddr := types.NewProviderConsAddress(valAConsAddr) @@ -471,7 +471,7 @@ func TestCreateConsumerValidator(t *testing.T) { require.NoError(t, err) // create a validator which has not set a consumer public key - valB := createStakingValidator(ctx, mocks, 1, 2, 2) + valB := createStakingValidator(ctx, mocks, 2, 2) valBConsAddr, _ := valB.GetConsAddr() valBProviderConsAddr := types.NewProviderConsAddress(valBConsAddr) valBPublicKey, _ := valB.TmConsPublicKey() diff --git a/x/ccv/provider/migrations/v5/migration_test.go b/x/ccv/provider/migrations/v5/migration_test.go index dce390ad71..1ae0e98a20 100644 --- a/x/ccv/provider/migrations/v5/migration_test.go +++ b/x/ccv/provider/migrations/v5/migration_test.go @@ -16,13 +16,14 @@ func TestMigrateParams(t *testing.T) { provKeeper.SetConsumerClientId(ctx, "chainID", "clientID") // initially top N should not exist - topN := provKeeper.GetTopN(ctx, "chainID") - require.Zero(t, topN) + _, err := provKeeper.GetConsumerPowerShapingParameters(ctx, "chainID") + require.Error(t, err) // migrate MigrateTopNForRegisteredChains(ctx, provKeeper) // after migration, top N should be 95 - topN = provKeeper.GetTopN(ctx, "chainID") - require.Equal(t, uint32(95), topN) + powerShapingParameters, err := provKeeper.GetConsumerPowerShapingParameters(ctx, "chainID") + require.NoError(t, err) + require.Equal(t, uint32(95), powerShapingParameters.Top_N) } diff --git a/x/ccv/provider/migrations/v6/migrations.go b/x/ccv/provider/migrations/v6/migrations.go index 933c5b9853..efbded3d02 100644 --- a/x/ccv/provider/migrations/v6/migrations.go +++ b/x/ccv/provider/migrations/v6/migrations.go @@ -23,8 +23,12 @@ func MigrateMinPowerInTopN(ctx sdk.Context, providerKeeper providerkeeper.Keeper for _, chain := range registeredConsumerChains { // get the top N - topN := providerKeeper.GetTopN(ctx, chain) - if topN == 0 { + powerShapingParameters, err := providerKeeper.GetConsumerPowerShapingParameters(ctx, chain) + if err != nil { + providerKeeper.Logger(ctx).Error("failed to get power shaping parameters", "chain", chain, "error", err) + continue + } + if powerShapingParameters.Top_N == 0 { providerKeeper.Logger(ctx).Info("top N is 0, not setting minimal power", "chain", chain) } else { // set the minimal power in the top N @@ -33,9 +37,9 @@ func MigrateMinPowerInTopN(ctx sdk.Context, providerKeeper providerkeeper.Keeper providerKeeper.Logger(ctx).Error("failed to get last bonded validators", "chain", chain, "error", err) continue } - minPower, err := providerKeeper.ComputeMinPowerInTopN(ctx, bondedValidators, topN) + minPower, err := providerKeeper.ComputeMinPowerInTopN(ctx, bondedValidators, powerShapingParameters.Top_N) if err != nil { - providerKeeper.Logger(ctx).Error("failed to compute min power in top N", "chain", chain, "topN", topN, "error", err) + providerKeeper.Logger(ctx).Error("failed to compute min power in top N", "chain", chain, "topN", powerShapingParameters.Top_N, "error", err) continue } providerKeeper.SetMinimumPowerInTopN(ctx, chain, minPower) diff --git a/x/ccv/provider/types/errors.go b/x/ccv/provider/types/errors.go index b7ab44d029..57ab5c2a99 100644 --- a/x/ccv/provider/types/errors.go +++ b/x/ccv/provider/types/errors.go @@ -48,4 +48,5 @@ var ( ErrInvalidTransformToOptIn = errorsmod.Register(ModuleName, 40, "invalid transform to Opt In chain") ErrCannotCreateTopNChain = errorsmod.Register(ModuleName, 41, "cannot create Top N chain outside permissionlessly") ErrCannotPrepareForLaunch = errorsmod.Register(ModuleName, 42, "cannot prepare chain for launch") + ErrInvalidStopTime = errorsmod.Register(ModuleName, 43, "invalid stop time") )