diff --git a/docs/docs/adrs/adr-017-allowing-inactive-validators.md b/docs/docs/adrs/adr-017-allowing-inactive-validators.md index 42eec9978b..46d5453f1e 100644 --- a/docs/docs/adrs/adr-017-allowing-inactive-validators.md +++ b/docs/docs/adrs/adr-017-allowing-inactive-validators.md @@ -113,7 +113,20 @@ The validator should be neither slashed nor jailed for downtime. This can be tested by having an inactive validator go offline on a consumer chain for long enough to accrue downtime. The consumer chain should send a SlashPacket to the provider chain, which should jail the validator. -* **Mint**: +### Scenario 6: Mint does not consider inactive validators + +To compute the inflation rate, only the active validators should be considered. + +We can check this by querying the inflation rate from the mint module twice: +Once with all validators active, and once with a lot of stake inactive. + +### Scenarios 7: Inactive validators can validate on consumer chains + +An inactive validator can opt in and validate on consumer chains (if min stake and max rank allow it) + +### Scenario 8: MinStake and MaxRank parameters are respected + +Validators that don't meet the criteria for a consumer chain cannot validate on it. ## Consequences diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 19fca5da88..ff7227012e 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -93,6 +93,8 @@ const ( CompatibilityTestCfg TestConfigType = "compatibility" SmallMaxValidatorsTestCfg TestConfigType = "small-max-validators" InactiveProviderValsTestCfg TestConfigType = "inactive-provider-vals" + GovTestCfg TestConfigType = "gov" + InactiveValsGovTestCfg TestConfigType = "inactive-vals-gov" ) type TestConfig struct { @@ -183,6 +185,10 @@ func GetTestConfig(cfgType TestConfigType, providerVersion, consumerVersion stri testCfg = SmallMaxValidatorsTestConfig() case InactiveProviderValsTestCfg: testCfg = InactiveProviderValsTestConfig() + case GovTestCfg: + testCfg = GovTestConfig() + case InactiveValsGovTestCfg: + testCfg = InactiveValsGovTestConfig() default: panic(fmt.Sprintf("Invalid test config: %s", cfgType)) } @@ -597,6 +603,38 @@ func SmallMaxValidatorsTestConfig() TestConfig { return cfg } +func GovTestConfig() TestConfig { + cfg := DefaultTestConfig() + + // set the MaxValidators to 2 + proviConfig := cfg.chainConfigs[ChainID("provi")] + proviConfig.GenesisChanges += "| .app_state.gov.params.quorum = \"0.5\"" + 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 InactiveValsGovTestConfig() TestConfig { + cfg := InactiveProviderValsTestConfig() + + // set the MaxValidators to 2 + proviConfig := cfg.chainConfigs[ChainID("provi")] + proviConfig.GenesisChanges += "| .app_state.gov.params.quorum = \"0.5\"" + 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 f1dd891751..81a7623df3 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -210,6 +210,18 @@ var stepChoices = map[string]StepChoice{ description: "test inactive validators on consumer", testConfig: InactiveProviderValsTestCfg, }, + "inactive-provider-validators-governance": { + name: "inactive-provider-validators-governance", + steps: stepsInactiveProviderValidatorsGovernance(), + description: "test governance with inactive validators", + testConfig: InactiveValsGovTestCfg, + }, + "inactive-provider-validators-governance-basecase": { + name: "inactive-provider-validators-governance-basecase", + steps: stepsInactiveProviderValidatorsGovernanceComparison(), + description: "comparison for governance when there are *no* inactive validators, to verify the difference to the governance test *with* inactive validators", + testConfig: GovTestCfg, + }, } func getTestCaseUsageString() string { diff --git a/tests/e2e/steps_inactive_vals.go b/tests/e2e/steps_inactive_vals.go index 7e6f7a3b4a..ba21c46511 100644 --- a/tests/e2e/steps_inactive_vals.go +++ b/tests/e2e/steps_inactive_vals.go @@ -499,3 +499,171 @@ func setupOptInChain() []Step { }, } } + +func stepsInactiveProviderValidatorsGovernance() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // max consensus validators is 1, so alice and bob should not be in power + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + }, + []Step{ + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 51, + }, + 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: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + // vote for it with carol + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("carol")}, + Vote: []string{"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}, + // the proposal should have passed because carol voted for it. + // carol alone is enough to pass the quorum, because stake of the other validators is not counted + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, + }, + ) + + return s +} + +func stepsInactiveProviderValidatorsGovernanceComparison() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 290, + ValidatorID("bob"): 290, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + }, + []Step{ + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 51, + }, + 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: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + // vote for it with carol + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("carol")}, + Vote: []string{"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}, + // the proposal should *not* have passed because only carol voted for it, + // and carol is not enough to pass the quorum + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_FAILED)), + }, + }, + }, + }, + }, + }, + ) + + return s +} diff --git a/tests/e2e/steps_partial_set_security.go b/tests/e2e/steps_partial_set_security.go index 9a0daa5acf..0d949fdf22 100644 --- a/tests/e2e/steps_partial_set_security.go +++ b/tests/e2e/steps_partial_set_security.go @@ -1511,8 +1511,10 @@ func stepsValidatorsAllowlistedChain() []Step { InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, TopN: 0, // only "alice" and "bob" are allowlisted (see `getDefaultValidators` in `tests/e2e/config.go`) - Allowlist: []string{"cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", - "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39"}, + Allowlist: []string{ + "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", + }, }, State: State{ ChainID("provi"): ChainState{ @@ -2249,8 +2251,10 @@ func stepsModifyChain() []Step { Deposit: 10000001, ConsumerChain: ChainID("consu"), // only "alice" and "carol" are allowlisted (see `getDefaultValidators` in `tests/e2e/config.go`) - Allowlist: []string{"cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", - "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6"}, + Allowlist: []string{ + "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + }, }, State: State{ ChainID("provi"): ChainState{ @@ -2437,7 +2441,8 @@ func stepsModifyChain() []Step { ExpectError: true, // because this chain is now Top 100%, no validator can opt out }, State: State{}, - }} + }, + } return s }