Skip to content

Commit

Permalink
add integration tests for equivocation proposals (#710)
Browse files Browse the repository at this point in the history
* add integration test for equivocation proposal

* fixup
  • Loading branch information
MSalopek authored Feb 6, 2023
1 parent 50957d0 commit 9469dae
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 1 deletion.
71 changes: 71 additions & 0 deletions tests/integration/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import (
"sync"
"time"

evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types"
consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types"

"github.com/cosmos/interchain-security/x/ccv/provider/client"
"github.com/cosmos/interchain-security/x/ccv/provider/types"
"github.com/tidwall/gjson"
)

Expand Down Expand Up @@ -398,6 +400,75 @@ func (tr TestRun) submitParamChangeProposal(
}
}

type submitEquivocationProposalAction struct {
chain chainID
height int64
time time.Time
power int64
validator validatorID
deposit uint
from validatorID
}

func (tr TestRun) submitEquivocationProposal(action submitEquivocationProposalAction, verbose bool) {
val := tr.validatorConfigs[action.validator]
providerChain := tr.chainConfigs[chainID("provi")]

prop := client.EquivocationProposalJSON{
EquivocationProposal: types.EquivocationProposal{
Title: "Validator equivocation!",
Description: fmt.Sprintf("Validator: %s has committed an equivocation infraction on chainID: %s", action.validator, action.chain),
Equivocations: []*evidencetypes.Equivocation{
{
Height: action.height,
Time: action.time,
Power: action.power,
ConsensusAddress: val.valconsAddress,
},
},
},
Deposit: fmt.Sprint(action.deposit) + `stake`,
}

bz, err := json.Marshal(prop)
if err != nil {
log.Fatal(err)
}

jsonStr := string(bz)
if strings.Contains(jsonStr, "'") {
log.Fatal("prop json contains single quote")
}

//#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments.
bz, err = exec.Command("docker", "exec", tr.containerConfig.instanceName,
"/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/equivocation-proposal.json")).CombinedOutput()

if err != nil {
log.Fatal(err, "\n", string(bz))
}

//#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments.
bz, err = exec.Command("docker", "exec", tr.containerConfig.instanceName, providerChain.binaryName,

"tx", "gov", "submit-proposal", "equivocation",
"/equivocation-proposal.json",

`--from`, `validator`+fmt.Sprint(action.from),
`--chain-id`, string(providerChain.chainId),
`--home`, tr.getValidatorHome(providerChain.chainId, action.from),
`--node`, tr.getValidatorNode(providerChain.chainId, action.from),
`--gas`, "9000000",
`--keyring-backend`, `test`,
`-b`, `block`,
`-y`,
).CombinedOutput()

if err != nil {
log.Fatal(err, "\n", string(bz))
}
}

type voteGovProposalAction struct {
chain chainID
from []validatorID
Expand Down
41 changes: 41 additions & 0 deletions tests/integration/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,47 @@ func MultiConsumerTestRun() TestRun {
}
}

func EquivocationProposalTestRun() TestRun {
return TestRun{
name: "equivocation",
containerConfig: ContainerConfig{
containerName: "interchain-security-equiv-container",
instanceName: "interchain-security-equiv-instance",
ccvVersion: "1",
now: time.Now(),
},
validatorConfigs: getDefaultValidators(),
chainConfigs: map[chainID]ChainConfig{
chainID("provi"): {
chainId: chainID("provi"),
binaryName: "interchain-security-pd",
ipPrefix: "7.7.7",
votingWaitTime: 20,
genesisChanges: ".app_state.gov.voting_params.voting_period = \"20s\" | " +
// Custom slashing parameters for testing validator downtime functionality
// See https://docs.cosmos.network/main/modules/slashing/04_begin_block.html#uptime-tracking
".app_state.slashing.params.signed_blocks_window = \"2\" | " +
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"2s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " +
".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling
".app_state.provider.params.slash_meter_replenish_period = \"3s\"",
},
chainID("consu"): {
chainId: chainID("consu"),
binaryName: "interchain-security-cd",
ipPrefix: "7.7.8",
votingWaitTime: 20,
genesisChanges: ".app_state.gov.voting_params.voting_period = \"20s\" | " +
".app_state.slashing.params.signed_blocks_window = \"15\" | " +
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"2s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"",
},
},
}
}

func (s *TestRun) SetLocalSDKPath(path string) {
if path != "" {
fmt.Println("USING LOCAL SDK", path)
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func main() {
{DefaultTestRun(), happyPathSteps},
{DemocracyTestRun(), democracySteps},
{SlashThrottleTestRun(), slashThrottleSteps},
{EquivocationProposalTestRun(), equivocationProposalSteps},
}
if includeMultiConsumer != nil && *includeMultiConsumer {
testRuns = append(testRuns, testRunWithSteps{MultiConsumerTestRun(), multipleConsumers})
Expand Down Expand Up @@ -93,6 +94,8 @@ func (tr *TestRun) runStep(step Step, verbose bool) {
tr.submitConsumerAdditionProposal(action, verbose)
case submitConsumerRemovalProposalAction:
tr.submitConsumerRemovalProposal(action, verbose)
case submitEquivocationProposalAction:
tr.submitEquivocationProposal(action, verbose)
case submitParamChangeProposalAction:
tr.submitParamChangeProposal(action, verbose)
case voteGovProposalAction:
Expand Down
19 changes: 19 additions & 0 deletions tests/integration/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ type ConsumerRemovalProposal struct {

func (p ConsumerRemovalProposal) isProposal() {}

type EquivocationProposal struct {
Height uint
Power uint
ConsensusAddress string
Deposit uint
Status string
}

func (p EquivocationProposal) isProposal() {}

type Rewards struct {
IsRewarded map[validatorID]bool
//if true it will calculate if the validator/delegator is rewarded between 2 successive blocks,
Expand Down Expand Up @@ -397,6 +407,15 @@ func (tr TestRun) getProposal(chain chainID, proposal uint) Proposal {
StopTime: int(stopTime.Milliseconds()),
}

case "/interchain_security.ccv.provider.v1.EquivocationProposal":
return EquivocationProposal{
Deposit: uint(deposit),
Status: status,
Height: uint(gjson.Get(string(bz), `content.equivocations.0.height`).Uint()),
Power: uint(gjson.Get(string(bz), `content.equivocations.0.power`).Uint()),
ConsensusAddress: gjson.Get(string(bz), `content.equivocations.0.consensus_address`).String(),
}

case "/cosmos.params.v1beta1.ParameterChangeProposal":
return ParamsProposal{
Deposit: uint(deposit),
Expand Down
7 changes: 6 additions & 1 deletion tests/integration/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ var democracySteps = concatSteps(
stepsDemocracy("democ"),
)

//nolint
var multipleConsumers = concatSteps(
stepsStartChains([]string{"consu", "densu"}, false),
stepsMultiConsumerDelegate("consu", "densu"),
Expand All @@ -48,3 +47,9 @@ var multipleConsumers = concatSteps(
stepsMultiConsumerDowntimeFromProvider("consu", "densu"),
stepsDoubleSign("consu", "densu"), // double sign on one of the chains
)

var equivocationProposalSteps = concatSteps(
stepsStartChains([]string{"consu"}, false),
stepsDelegate("consu"),
stepsSubmitEquivocationProposal("consu"),
)
108 changes: 108 additions & 0 deletions tests/integration/steps_submit_equivocation_proposal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package main

import "time"

// submits an equivocation proposal, votes on it, and tomstones the equivocating validator
func stepsSubmitEquivocationProposal(consumerName string) []Step {
s := []Step{
{
// bob submits a proposal to slash himself
action: submitEquivocationProposalAction{
chain: chainID("consu"),
from: validatorID("bob"),
deposit: 10000001,
height: 10,
time: time.Now(), // not sure what time in equivocations means
power: 500,
validator: validatorID("bob"),
},
state: State{
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 511,
validatorID("bob"): 500,
validatorID("carol"): 500,
},
ValBalances: &map[validatorID]uint{
validatorID("bob"): 9489999999,
},
Proposals: &map[uint]Proposal{
2: EquivocationProposal{
Deposit: 10000001,
Status: "PROPOSAL_STATUS_VOTING_PERIOD",
ConsensusAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39",
Power: 500,
Height: 10,
},
},
},
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 511,
validatorID("bob"): 500,
validatorID("carol"): 500,
},
},
},
},
{
action: voteGovProposalAction{
chain: chainID("provi"),
from: []validatorID{validatorID("alice"), validatorID("bob"), validatorID("carol")},
vote: []string{"yes", "yes", "yes"},
propNumber: 2,
},
state: State{
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 511,
validatorID("bob"): 0, // bob is slashed after proposal passes
validatorID("carol"): 500,
},
Proposals: &map[uint]Proposal{
2: EquivocationProposal{
Deposit: 10000001,
Status: "PROPOSAL_STATUS_PASSED",
ConsensusAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39",
Power: 500,
Height: 10,
},
},
},
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 511,
validatorID("bob"): 500, // slash not reflected in consumer chain
validatorID("carol"): 500,
},
},
},
},
{
// relay power change to consumer1
action: relayPacketsAction{
chain: chainID("provi"),
port: "provider",
channel: 0,
},
state: State{
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 511,
validatorID("bob"): 0,
validatorID("carol"): 500,
},
},
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 511,
validatorID("bob"): 0, // slash relayed to consumer chain
validatorID("carol"): 500,
},
},
},
},
}

return s
}

0 comments on commit 9469dae

Please sign in to comment.