diff --git a/.github/workflows/nightly-e2e.yml b/.github/workflows/nightly-e2e.yml index 7f200b7f97..e872f10230 100644 --- a/.github/workflows/nightly-e2e.yml +++ b/.github/workflows/nightly-e2e.yml @@ -293,6 +293,22 @@ jobs: go-version: "1.21" # The Go version to download (if necessary) and use. - name: E2E partial set security modification proposal run: go run ./tests/e2e/... --tc partial-set-security-modification-proposal + active-set-changes-test: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/setup-go@v5 + with: + go-version: "1.21" + - uses: actions/checkout@v4 + - name: Checkout LFS objects + run: git lfs checkout + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.21" # The Go version to download (if necessary) and use. + - name: E2E active set changes + run: go run ./tests/e2e/... --tc active-set-changes nightly-test-fail: needs: @@ -311,7 +327,11 @@ jobs: - partial-set-security-validators-power-cap-test - partial-set-security-validators-allowlisted-test - partial-set-security-validators-denylisted-test +<<<<<<< HEAD + - active-set-changes-test +======= - partial-set-security-modification-proposal +>>>>>>> main if: ${{ failure() }} runs-on: ubuntu-latest steps: diff --git a/tests/e2e/config.go b/tests/e2e/config.go index a5b86e85f9..c172c8584d 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -87,6 +87,7 @@ const ( MulticonsumerTestCfg TestConfigType = "multi-consumer" ConsumerMisbehaviourTestCfg TestConfigType = "consumer-misbehaviour" CompatibilityTestCfg TestConfigType = "compatibility" + SmallMaxValidatorsTestCfg TestConfigType = "small-max-validators" ) // Attributes that are unique to a validator. Allows us to map (part of) @@ -264,6 +265,8 @@ func GetTestConfig(cfgType TestConfigType, providerVersion, consumerVersion stri testCfg = ConsumerMisbehaviourTestConfig() case CompatibilityTestCfg: testCfg = CompatibilityTestConfig(pv, cv) + case SmallMaxValidatorsTestCfg: + testCfg = SmallMaxValidatorsTestConfig() default: panic(fmt.Sprintf("Invalid test config: %s", cfgType)) } @@ -605,6 +608,22 @@ func DemocracyTestConfig(allowReward bool) TestConfig { return tr } +func SmallMaxValidatorsTestConfig() TestConfig { + cfg := DefaultTestConfig() + + // set the MaxValidators to 2 + proviConfig := cfg.chainConfigs[ChainID("provi")] + proviConfig.GenesisChanges += "| .app_state.staking.params.max_validators = 2" + cfg.chainConfigs[ChainID("provi")] = proviConfig + + carolConfig := cfg.validatorConfigs["carol"] + // make carol use her own key + carolConfig.UseConsumerKey = false + cfg.validatorConfigs["carol"] = carolConfig + + return cfg +} + func MultiConsumerTestConfig() TestConfig { tr := TestConfig{ name: string(MulticonsumerTestCfg), diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 6a7523ac05..5a1ee681e0 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -192,6 +192,12 @@ var stepChoices = map[string]StepChoice{ description: "test partial set security for an Opt-In chain that has a validator denylisted", testConfig: DefaultTestCfg, }, + "active-set-changes": { + name: "active-set-changes", + steps: stepsActiveSetChanges(), + description: "This is a regression test related to the issue discussed here: https://forum.cosmos.network/t/cosmos-hub-v17-1-chain-halt-post-mortem/13899. The test ensures that the protocol works as expected when MaxValidators is smaller than the number of potential validators.", + testConfig: SmallMaxValidatorsTestCfg, + }, "partial-set-security-modification-proposal": { name: "partial-set-security-modification-proposal", steps: stepsModifyChain(), @@ -286,6 +292,7 @@ func getTestCases(selectedPredefinedTests, selectedTestFiles TestSet, providerVe "consumer-double-downtime", "partial-set-security-opt-in", "partial-set-security-top-n", "partial-set-security-validator-set-cap", "partial-set-security-validators-power-cap", "partial-set-security-validators-allowlisted", "partial-set-security-validators-denylisted", + "active-set-changes", "partial-set-security-modification-proposal", } if includeMultiConsumer != nil && *includeMultiConsumer { diff --git a/tests/e2e/steps_active_set_changes.go b/tests/e2e/steps_active_set_changes.go new file mode 100644 index 0000000000..153e7a07b4 --- /dev/null +++ b/tests/e2e/steps_active_set_changes.go @@ -0,0 +1,138 @@ +package main + +import clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + +// stepsActiveSetChanges starts a top N provider chain and causes a change in the active set +func stepsActiveSetChanges() []Step { + s := []Step{ + // === setup provider chain, consumer chain with top N = 100, and start IBC connections === + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 700000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // MaxValidators is set to 2, so alice is not part of the validator set + ValidatorID("bob"): 200, + ValidatorID("carol"): 700, + }, + }, + }, + }, + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 100, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: "PROPOSAL_STATUS_VOTING_PERIOD", + }, + }, + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, + ValidatorID("bob"): {}, + ValidatorID("carol"): {}, + }, + }, + }, + }, + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, + Vote: []string{"yes", "yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: "PROPOSAL_STATUS_PASSED", + }, + }, + }, + }, + }, + { + // we start all the validators but only "alice" and "bob" have opted in and hence + // only "alice" and "bob" are validating blocks + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 700000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{}, + }, + { + Action: AddIbcConnectionAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + ClientA: 0, + ClientB: 0, + }, + State: State{}, + }, + { + Action: AddIbcChannelAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + ConnectionA: 0, + PortA: "consumer", + PortB: "provider", + Order: "ordered", + }, + State: State{}, + }, + // === setup ends, ready for the actual test === + // bob incurs downtime on the provider and gets jailed + { + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // alice goes into the active set + ValidatorID("bob"): 0, + ValidatorID("carol"): 700, + }, + }, + }, + }, + } + + return s +}