Skip to content

Commit

Permalink
test: Add e2e tests for inactive vals (#2064)
Browse files Browse the repository at this point in the history
* Start adding e2e test for governance

* Debug gov with inactive vals test

* Outline for test scenarios where they are tested

* Add MaxRank steps

* Add e2e tests for min stake and max rank

* Revert formatting change

* Refactor stepsOptIn

* Use adjusted config for e2e tests

* Write for more scenarios where they are tested

* Add test for mint

* Add docstrings for e2e steps
  • Loading branch information
p-offtermatt authored Jul 22, 2024
1 parent 4f42e54 commit 7f541ba
Show file tree
Hide file tree
Showing 7 changed files with 767 additions and 93 deletions.
54 changes: 47 additions & 7 deletions docs/docs/adrs/adr-017-allowing-inactive-validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,34 +88,46 @@ In the following,
### Scenario 1: Inactive validators should not be considered by governance

Inactive validators should not be considered for the purpose of governance.
In particular, the governance module should not allow inactive validators to vote on proposals,
and the quorum depends only on the stake bonded by active validators.
In particular, the quorum should depend only on active validators.

This can be tested by creating a governance proposal, then trying to vote on it with inactive validators.
The proposal should not pass.
Afterwards, we create another proposal and vote on it with active validators, too.
Then, the proposal should pass.
We test this by:
* creating a provider chain (either with 3 active validators, or with only 1 active validator), a quorum of 50%, and 3 validators with alice=300, bob=299, charlie=299 stake
* we create a governance proposal
* alice votes for the proposal
* we check that the proposal has the right status:
* in the scenario where we have 3 active validators, the proposal *should not* have passed, because alice alone is not enough to fulfill the quorum
* in the scenario where we have 1 active validator, the proposal *should* have passed, because alice is the only active validator, and thus fulfills the quorum

Tested by the e2e tests `inactive-provider-validators-governance` (scenario with 1 active val) and `inactive-provider-validators-governance-basecase` (scenario with 3 active vals).

### Scenario 2: Inactive validators should not get rewards from the provider chain

Inactive validators should not get rewards from the provider chain.

This can be tested by starting a provider chain with inactive validators and checking the rewards of inactive validators.

Checked as part of the e2e test `inactive-provider-validators-on-consumer`.

### Scenario 3: Inactive validators should get rewards from consumer chains

An inactive validator that is validating on a consumer chain should receive rewards in the consumer chain token.

Checked as part of the e2e test `inactive-provider-validators-on-consumer`.

### Scenario 4: Inactive validators should not get slashed/jailed for downtime on the provider chain

This can be tested by having an inactive validator go offline on the provider chain for long enough to accrue downtime.
The validator should be neither slashed nor jailed for downtime.

Checked as part of the e2e test `inactive-provider-validators-on-consumer`.

### Scenario 5: Inactive validators *should* get jailed for downtime on the provider chain

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.

Checked as part of the e2e test `inactive-provider-validators-on-consumer`.

### Scenario 6: Inactive validators should not be counted when computing the minimum power in the top N

This can be tested like this:
Expand All @@ -125,7 +137,35 @@ This can be tested like this:
* Without inactive validators, this means both alice and bob have to validate. But since charlie is inactive, this means bob is *not* in the top N
* Verify that alice is in the top N, but bob is not

* **Mint**:
Checked as part of the e2e test `inactive-vals-topN`.

### Scenario 7: 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 change over subsequent blocks.

We start a provider chain with these arguments
* 3 validators with powers alice=290, bob=280, charlie=270
* either 1 or 3 active validators
* a bonded goal of 300 tokens (this is given in percent, but we simplify here)

If we have 3 validators active, then the inflation rate should *decrease* between blocks, because the bonded goal is exceeded as all validators are bonded.
If we have only 1 validator active, then the inflation rate should *increase* between blocks, because the bonded goal is not met.

Checked as part of the e2e tests `inactive-vals-mint` (scenario with 1 active val) and `mint-basecase` (scenario with 3 active vals).

### Scenarios 8: 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)

Checked as part of the e2e test `inactive-provider-validators-on-consumer`.

### Scenario 9: MinStake and MaxRank parameters are respected

Validators that don't meet the criteria for a consumer chain cannot validate on it.

Checked in the e2e tests `min-stake` and `max-rank`.

## Consequences

Expand Down
68 changes: 67 additions & 1 deletion tests/e2e/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ const (
CompatibilityTestCfg TestConfigType = "compatibility"
SmallMaxValidatorsTestCfg TestConfigType = "small-max-validators"
InactiveProviderValsTestCfg TestConfigType = "inactive-provider-vals"
GovTestCfg TestConfigType = "gov"
InactiveValsGovTestCfg TestConfigType = "inactive-vals-gov"
InactiveValsMintTestCfg TestConfigType = "inactive-vals-mint"
MintTestCfg TestConfigType = "mint"
)

type TestConfig struct {
Expand Down Expand Up @@ -183,6 +187,14 @@ func GetTestConfig(cfgType TestConfigType, providerVersion, consumerVersion stri
testCfg = SmallMaxValidatorsTestConfig()
case InactiveProviderValsTestCfg:
testCfg = InactiveProviderValsTestConfig()
case GovTestCfg:
testCfg = GovTestConfig()
case InactiveValsGovTestCfg:
testCfg = InactiveValsGovTestConfig()
case InactiveValsMintTestCfg:
testCfg = InactiveValsMintTestConfig()
case MintTestCfg:
testCfg = MintTestConfig()
default:
panic(fmt.Sprintf("Invalid test config: %s", cfgType))
}
Expand Down Expand Up @@ -573,7 +585,7 @@ func InactiveProviderValsTestConfig() TestConfig {
tr.chainConfigs[ChainID("provi")] = proviConfig
tr.chainConfigs[ChainID("consu")] = consuConfig

// make is to that carol does not use a consumer key
// make it so that carol does not use a consumer key
carolConfig := tr.validatorConfigs[ValidatorID("carol")]
carolConfig.UseConsumerKey = false
tr.validatorConfigs[ValidatorID("carol")] = carolConfig
Expand All @@ -597,6 +609,60 @@ func SmallMaxValidatorsTestConfig() TestConfig {
return cfg
}

func GovTestConfig() TestConfig {
cfg := DefaultTestConfig()

// set the quorum to 50%
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 := GovTestConfig()

// set the MaxValidators to 1
proviConfig := cfg.chainConfigs[ChainID("provi")]
proviConfig.GenesisChanges += "| .app_state.staking.params.max_validators = 1"
cfg.chainConfigs[ChainID("provi")] = proviConfig

return cfg
}

func MintTestConfig() TestConfig {
cfg := GovTestConfig()
AdjustMint(cfg)

return cfg
}

func InactiveValsMintTestConfig() TestConfig {
cfg := InactiveValsGovTestConfig()
AdjustMint(cfg)

return cfg
}

// AdjustMint adjusts the mint parameters to have a very low goal bonded amount
// and a high inflation rate change
func AdjustMint(cfg TestConfig) {
proviConfig := cfg.chainConfigs[ChainID("provi")]
// total supply is 30000000000stake; we want to set the mint bonded goal to
// a small fraction of that
proviConfig.GenesisChanges += "| .app_state.mint.params.goal_bonded = \"0.001\"" +
"| .app_state.mint.params.inflation_rate_change = \"1\"" +
"| .app_state.mint.params.inflation_max = \"0.5\"" +
"| .app_state.mint.params.inflation_min = \"0.1\""
cfg.chainConfigs[ChainID("provi")] = proviConfig
}

func MultiConsumerTestConfig() TestConfig {
tr := TestConfig{
name: string(MulticonsumerTestCfg),
Expand Down
38 changes: 38 additions & 0 deletions tests/e2e/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,42 @@ var stepChoices = map[string]StepChoice{
description: "test inactive validators on topN chain",
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: stepsInactiveProviderValidatorsGovernanceBasecase(),
description: "comparison for governance when there are *no* inactive validators, to verify the difference to the governance test *with* inactive validators",
testConfig: GovTestCfg,
},
"max-rank": {
name: "max-rank",
steps: stepsMaxRank(),
description: "checks that the max rank parameter for consumer chains is respected",
testConfig: GovTestCfg, // can reuse the GovTestCfg because all parameters there are ok to use here
},
"min-stake": {
name: "min-stake",
steps: stepsMinStake(),
description: "checks that the min stake parameter for consumer chains is respected",
testConfig: GovTestCfg, // see above: we reuse the GovTestCfg for convenience
},
"inactive-vals-mint": {
name: "inactive-vals-mint",
steps: stepsInactiveValsMint(),
description: "test minting with inactive validators",
testConfig: InactiveValsMintTestCfg,
},
"mint-basecase": {
name: "mint-basecase",
steps: stepsMintBasecase(),
description: "test minting without inactive validators as a sanity check",
testConfig: MintTestCfg,
},
}

func getTestCaseUsageString() string {
Expand Down Expand Up @@ -306,6 +342,8 @@ func getTestCases(selectedPredefinedTests, selectedTestFiles TestSet, providerVe
"partial-set-security-validators-allowlisted", "partial-set-security-validators-denylisted",
"partial-set-security-modification-proposal",
"active-set-changes", "inactive-vals-topN",
"inactive-provider-validators-on-consumer", "inactive-provider-validators-governance",
"max-rank", "min-stake", "inactive-vals-mint",
}
if includeMultiConsumer != nil && *includeMultiConsumer {
selectedPredefinedTests = append(selectedPredefinedTests, "multiconsumer")
Expand Down
44 changes: 44 additions & 0 deletions tests/e2e/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,29 @@ func (tr Chain) GetChainState(chain ChainID, modelState ChainState) ChainState {
chainState.HasToValidate = &hasToValidate
}

if modelState.InflationRateChange != nil {
// get the inflation rate now
inflationRateNow := tr.target.GetInflationRate(chain)

// wait a block
tr.waitBlocks(chain, 1, 10*time.Second)

// get the new inflation rate
inflationRateAfter := tr.target.GetInflationRate(chain)

// calculate the change
inflationRateChange := inflationRateAfter - inflationRateNow
var inflationRateChangeDirection int
if inflationRateChange > 0 {
inflationRateChangeDirection = 1
} else if inflationRateChange < 0 {
inflationRateChangeDirection = -1
} else {
inflationRateChangeDirection = 0
}
chainState.InflationRateChange = &inflationRateChangeDirection
}

if modelState.ConsumerPendingPacketQueueSize != nil {
pendingPacketQueueSize := tr.target.GetPendingPacketQueueSize(chain)
chainState.ConsumerPendingPacketQueueSize = &pendingPacketQueueSize
Expand Down Expand Up @@ -302,6 +325,10 @@ func uintPtr(i uint) *uint {
return &i
}

func intPtr(i int) *int {
return &i
}

type Commands struct {
containerConfig ContainerConfig // FIXME only needed for 'Now' time tracking
validatorConfigs map[ValidatorID]ValidatorConfig
Expand Down Expand Up @@ -867,6 +894,23 @@ func (tr Commands) GetHasToValidate(
return chains
}

func (tr Commands) GetInflationRate(
chain ChainID,
) float64 {
binaryName := tr.chainConfigs[chain].BinaryName
bz, err := tr.target.ExecCommand(binaryName,
"query", "mint", "inflation",
`--node`, tr.GetQueryNode(chain),
`-o`, `json`,
).CombinedOutput()
if err != nil {
log.Fatal(err, "\n", string(bz))
}

inflationRate := gjson.Get(string(bz), "inflation").Float()
return inflationRate
}

func (tr Commands) GetTrustedHeight(
chain ChainID,
clientID string,
Expand Down
Loading

0 comments on commit 7f541ba

Please sign in to comment.