diff --git a/tests/interchain/chainsuite/chain_spec_provider.go b/tests/interchain/chainsuite/chain_spec_provider.go index 38469bcb6c..92b184ef8c 100644 --- a/tests/interchain/chainsuite/chain_spec_provider.go +++ b/tests/interchain/chainsuite/chain_spec_provider.go @@ -51,5 +51,6 @@ func providerModifiedGenesis() []cosmos.GenesisKV { cosmos.NewGenesisKV("app_state.provider.params.slash_meter_replenish_period", ProviderReplenishPeriod), cosmos.NewGenesisKV("app_state.provider.params.slash_meter_replenish_fraction", ProviderReplenishFraction), cosmos.NewGenesisKV("app_state.provider.params.blocks_per_epoch", "1"), + cosmos.NewGenesisKV("app_state.staking.params.max_validators", "1"), } } diff --git a/tests/interchain/chainsuite/config.go b/tests/interchain/chainsuite/config.go index df77c86696..5567ba75d8 100644 --- a/tests/interchain/chainsuite/config.go +++ b/tests/interchain/chainsuite/config.go @@ -32,12 +32,14 @@ const ( CommitTimeout = 2 * time.Second TotalValidatorFunds = 11_000_000_000 ValidatorFunds = 30_000_000 - ValidatorCount = 1 - FullNodeCount = 0 - ChainSpawnWait = 155 * time.Second - CosmosChainType = "cosmos" - GovModuleAddress = "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" - TestWalletsNumber = 15 // Ensure that test accounts are used in a way that maintains the mutual independence of tests + // ValidatorCount is set to 2, so we have one active and one inactive (i.e., outside the active set) validator. + // Note that the provider has at most 1 validator (see `chain_spec_provider.go`). + ValidatorCount = 2 + FullNodeCount = 0 + ChainSpawnWait = 155 * time.Second + CosmosChainType = "cosmos" + GovModuleAddress = "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" + TestWalletsNumber = 15 // Ensure that test accounts are used in a way that maintains the mutual independence of tests ) func DefaultConfigToml() testutil.Toml { @@ -51,13 +53,14 @@ func DefaultConfigToml() testutil.Toml { } func DefaultGenesisAmounts(denom string) func(i int) (sdktypes.Coin, sdktypes.Coin) { + // Returns an amount of funds per validator, so validator with val index 0 has the most funds, then validator 1, then validator 2, etc. return func(i int) (sdktypes.Coin, sdktypes.Coin) { return sdktypes.Coin{ Denom: denom, - Amount: sdkmath.NewInt(TotalValidatorFunds), + Amount: sdkmath.NewInt(TotalValidatorFunds / int64(i+1)), }, sdktypes.Coin{ Denom: denom, - Amount: sdkmath.NewInt(ValidatorFunds), + Amount: sdkmath.NewInt(ValidatorFunds / int64(i+1)), } } } diff --git a/tests/interchain/provider_test.go b/tests/interchain/provider_test.go index 7a3a7796fb..3c95384bd7 100644 --- a/tests/interchain/provider_test.go +++ b/tests/interchain/provider_test.go @@ -33,7 +33,7 @@ func (s *ProviderSuite) TestProviderCreateConsumer() { testAcc := s.Provider.TestWallets[0].FormattedAddress() testAccKey := s.Provider.TestWallets[0].KeyName() - // Confirm that a chain can be create with the minimum params (metadata) + // Confirm that a chain can be created with the minimum params (metadata) chainName := "minParamAddConsumer" createConsumerMsg := msgCreateConsumer(chainName, nil, nil, testAcc) consumerId, err := s.Provider.CreateConsumer(s.GetContext(), createConsumerMsg, testAccKey) @@ -101,6 +101,52 @@ func (s *ProviderSuite) TestProviderCreateConsumerRejection() { s.Require().Error(err) } +// TestOptInChainCanOnlyStartIfActiveValidatorOptedIn tests that only if an active validator opts in to an Opt-In chain, the chain can launch. +// Scenario 1: Inactive validators opts in, the chain does not launch. +// Scenario 2: Active validator opts in, the chain launches. +func (s *ProviderSuite) TestOptInChainCanOnlyStartIfActiveValidatorOptedIn() { + testAcc := s.Provider.TestWallets[2].FormattedAddress() + testAccKey := s.Provider.TestWallets[2].KeyName() + + activeValIndex := 0 + inactiveValIndex := 1 + + // Scenario 1: Inactive validators opts in, the chain does not launch. + chainName := "optInScenario1" + spawnTime := time.Now().Add(time.Hour) + consumerInitParams := consumerInitParamsTemplate(&spawnTime) + createConsumerMsg := msgCreateConsumer(chainName, consumerInitParams, powerShapingParamsTemplate(), testAcc) + consumerId, err := s.Provider.CreateConsumer(s.GetContext(), createConsumerMsg, testAccKey) + s.Require().NoError(err) + consumerChain, err := s.Provider.GetConsumerChain(s.GetContext(), consumerId) + s.Require().NoError(err) + // inactive validator opts in + s.Require().NoError(s.Provider.OptIn(s.GetContext(), consumerChain.ConsumerID, inactiveValIndex)) + consumerInitParams.SpawnTime = time.Now() + upgradeMsg := &providertypes.MsgUpdateConsumer{ + Owner: testAcc, + ConsumerId: consumerChain.ConsumerID, + NewOwnerAddress: testAcc, + InitializationParameters: consumerInitParams, + PowerShapingParameters: powerShapingParamsTemplate(), + } + s.Require().NoError(s.Provider.UpdateConsumer(s.GetContext(), upgradeMsg, testAccKey)) + s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 1, s.Provider)) + consumerChain, err = s.Provider.GetConsumerChain(s.GetContext(), consumerId) + s.Require().NoError(err) + s.Require().Equal(providertypes.CONSUMER_PHASE_REGISTERED.String(), consumerChain.Phase) + + // Scenario 2: Active validator opts in, the chain launches. + // active validator opts in + s.Require().NoError(s.Provider.OptIn(s.GetContext(), consumerChain.ConsumerID, activeValIndex)) + + s.Require().NoError(s.Provider.UpdateConsumer(s.GetContext(), upgradeMsg, testAccKey)) + s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 1, s.Provider)) + consumerChain, err = s.Provider.GetConsumerChain(s.GetContext(), consumerId) + s.Require().NoError(err) + s.Require().Equal(providertypes.CONSUMER_PHASE_LAUNCHED.String(), consumerChain.Phase) +} + // Test Opting in validators to a chain (MsgOptIn) // Confirm that a chain can be created and validators can be opted in // Scenario 1: Validators opted in, MsgUpdateConsumer called to set spawn time in the past -> chain should start. @@ -403,12 +449,12 @@ func (s *ProviderSuite) TestProviderTransformTopNtoOptIn() { s.Require().Equal(testAcc, optInChain.OwnerAddress) } -// TestOptOut tests removin validator from consumer-opted-in-validators +// TestOptOut tests removing validator from consumer-opted-in-validators func (s *ProviderSuite) TestOptOut() { testAcc := s.Provider.TestWallets[7].FormattedAddress() testAccKey := s.Provider.TestWallets[7].KeyName() - // Add consume chain + // Add consumer chain chainName := "TestOptOut" spawnTime := time.Now().Add(time.Hour) consumerInitParams := consumerInitParamsTemplate(&spawnTime) diff --git a/x/ccv/provider/keeper/consumer_lifecycle.go b/x/ccv/provider/keeper/consumer_lifecycle.go index 5a656928b0..42c8efa8ae 100644 --- a/x/ccv/provider/keeper/consumer_lifecycle.go +++ b/x/ccv/provider/keeper/consumer_lifecycle.go @@ -194,14 +194,14 @@ func (k Keeper) ConsumeIdsFromTimeQueue( func (k Keeper) HasActiveConsumerValidator(ctx sdk.Context, consumerId string, activeValidators []stakingtypes.Validator) (bool, error) { currentValidatorSet, err := k.GetConsumerValSet(ctx, consumerId) if err != nil { - return false, err + return false, fmt.Errorf("getting consumer validator set of chain with consumerId (%s): %w", consumerId, err) } isActiveValidator := make(map[string]bool) for _, val := range activeValidators { consAddr, err := val.GetConsAddr() if err != nil { - return false, fmt.Errorf("creating consumer genesis state, consumerId(%s): %w", consumerId, err) + return false, fmt.Errorf("getting consensus address of validator (%+v), consumerId (%s): %w", val, consumerId, err) } providerConsAddr := types.NewProviderConsAddress(consAddr) isActiveValidator[providerConsAddr.String()] = true