From 9901d8c95724c95d0666b7c63a6aaf13bbe80cd4 Mon Sep 17 00:00:00 2001 From: insumity Date: Thu, 28 Mar 2024 12:42:09 +0100 Subject: [PATCH] init commit --- tests/e2e/actions.go | 104 ++++- tests/e2e/main.go | 12 + tests/e2e/steps_partial_set_security.go | 482 ++++++++++++++++++++++++ tests/e2e/test_driver.go | 4 + 4 files changed, 599 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/steps_partial_set_security.go diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index e49c37ae71..2ca395ab93 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -253,6 +253,7 @@ type SubmitConsumerAdditionProposalAction struct { SpawnTime uint InitialHeight clienttypes.Height DistributionChannel string + TopN uint32 } func (tr TestConfig) submitConsumerAdditionProposal( @@ -278,7 +279,7 @@ func (tr TestConfig) submitConsumerAdditionProposal( UnbondingPeriod: params.UnbondingPeriod, Deposit: fmt.Sprint(action.Deposit) + `stake`, DistributionTransmissionChannel: action.DistributionChannel, - TopN: 100, + TopN: action.TopN, } bz, err := json.Marshal(prop) @@ -292,9 +293,14 @@ func (tr TestConfig) submitConsumerAdditionProposal( } //#nosec G204 -- bypass unsafe quoting warning (no production code) - bz, err = target.ExecCommand( + cmd := target.ExecCommand( "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/temp-proposal.json"), - ).CombinedOutput() + ) + bz, err = cmd.CombinedOutput() + + if verbose { + log.Println("submitConsumerAdditionProposal cmd: ", cmd.String()) + } if err != nil { log.Fatal(err, "\n", string(bz)) @@ -2292,6 +2298,98 @@ func (tc TestConfig) startConsumerEvidenceDetector( tc.waitBlocks("provi", 10, 2*time.Minute) } +type OptInAction struct { + Chain ChainID + Validator ValidatorID +} + +func (tr TestConfig) optIn(action OptInAction, target ExecutionTarget, verbose bool) { + // Note: to get error response reported back from this command '--gas auto' needs to be set. + gas := "auto" + // Unfortunately, --gas auto does not work with CometMock. so when using CometMock, just use --gas 9000000 then + if tr.useCometmock { + gas = "9000000" + } + + // Use: "opt-in [consumer-chain-id] [consumer-pubkey]", + optIn := fmt.Sprintf( + `%s tx provider opt-in %s --from validator%s --chain-id %s --home %s --node %s --gas %s --keyring-backend test -y -o json`, + tr.chainConfigs[ChainID("provi")].BinaryName, + string(tr.chainConfigs[action.Chain].ChainId), + action.Validator, + tr.chainConfigs[ChainID("provi")].ChainId, + tr.getValidatorHome(ChainID("provi"), action.Validator), + tr.getValidatorNode(ChainID("provi"), action.Validator), + gas, + ) + + cmd := target.ExecCommand( + "/bin/bash", "-c", + optIn, + ) + + if verbose { + fmt.Println("optIn cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + _, _ = bz, err + if !tr.useCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore + if verbose { + fmt.Printf("got expected error during opt in | err: %s | output: %s \n", err, string(bz)) + } + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(ChainID("provi"), 2, 30*time.Second) +} + +type OptOutAction struct { + Chain ChainID + Validator ValidatorID +} + +func (tr TestConfig) optOut(action OptOutAction, target ExecutionTarget, verbose bool) { + // Note: to get error response reported back from this command '--gas auto' needs to be set. + gas := "auto" + // Unfortunately, --gas auto does not work with CometMock. so when using CometMock, just use --gas 9000000 then + if tr.useCometmock { + gas = "9000000" + } + + // Use: "opt-out [consumer-chain-id]", + optIn := fmt.Sprintf( + `%s tx provider opt-out %s --from validator%s --chain-id %s --home %s --node %s --gas %s --keyring-backend test -y -o json`, + tr.chainConfigs[ChainID("provi")].BinaryName, + string(tr.chainConfigs[action.Chain].ChainId), + action.Validator, + tr.chainConfigs[ChainID("provi")].ChainId, + tr.getValidatorHome(ChainID("provi"), action.Validator), + tr.getValidatorNode(ChainID("provi"), action.Validator), + gas, + ) + + cmd := target.ExecCommand( + "/bin/bash", "-c", + optIn, + ) + + if verbose { + fmt.Println("optOut cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + _, _ = bz, err + if !tr.useCometmock { // error report only works with --gas auto, which does not work with CometMock, so ignore + if verbose { + fmt.Printf("got expected error during opt out | err: %s | output: %s \n", err, string(bz)) + } + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(ChainID("provi"), 2, 30*time.Second) +} + // WaitTime waits for the given duration. // To make sure that the new timestamp is visible on-chain, it also waits until at least one block has been // produced on each chain after waiting. diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 7b2ba26278..f09d7244ba 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -156,6 +156,18 @@ var stepChoices = map[string]StepChoice{ description: `Minimal set of test steps to perform compatibility tests`, testConfig: CompatibilityTestCfg, }, + "partial-set-security-opt-in": { + name: "partial-set-security-opt-in", + steps: stepsOptInChain(), + description: "test partial set security for an Opt-In chain", + testConfig: DefaultTestCfg, + }, + "partial-set-security-top-n": { + name: "partial-set-security-top-n", + steps: stepsTopNChain(), + description: "test partial set security for a Top-N chain", + testConfig: DefaultTestCfg, + }, } func getTestCaseUsageString() string { diff --git a/tests/e2e/steps_partial_set_security.go b/tests/e2e/steps_partial_set_security.go new file mode 100644 index 0000000000..ae79a5b06c --- /dev/null +++ b/tests/e2e/steps_partial_set_security.go @@ -0,0 +1,482 @@ +package main + +import clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + +// stepsOptInChain starts a provider chain and an Opt-In chain and opts in and out validators +func stepsOptInChain() []Step { + s := []Step{ + { + 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: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + }, + }, + }, + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + }, + 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", + }, + }, + }, + }, + }, + // Οpt in "alice" and "bob" so the chain is not empty when it is about to start. Note, that "alice" and "bob" use + // the provider's public key (see `UseConsumerKey` is set to `false` in `getDefaultValidators`) and hence do not + // need a consumer-key assigment. + { + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("alice"), + }, + State: State{}, + }, + { + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("bob"), + }, + State: State{}, + }, + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"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: 300000000, 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{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 200, + // carol has not yet opted in + ValidatorID("carol"): 0, + }, + }, + }, + }, + { + 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{}, + }, + { + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("carol"), + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 200, + // "carol" has not yet opted in, the VSCPacket capturing the opt-in should first be relayed + ValidatorID("carol"): 0, + }, + }, + }, + }, + { + // assign the consumer key "carol" is using on the consumer chain to be the one "carol" uses when opting in + Action: AssignConsumerPubKeyAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("carol"), + // reconfigure the node -> validator was using provider key + // until this point -> key matches config.consumerValPubKey for "bob" + ConsumerPubkey: getDefaultValidators()[ValidatorID("carol")].ConsumerValPubKey, + ReconfigureNode: true, + }, + State: State{}, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 200, + // carol has now opted in + ValidatorID("carol"): 300, + }, + }, + }, + }, + { + Action: OptOutAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + // "bob" has not yet opted out from the consumer chain because the VSCPacket has not yet been relayed + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + }, + }, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + // bob has now opted out + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, + }, + }, + }, + }, + { + // re opt-in "bob" + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + // "bob" has not yet been opted in from the consumer chain because the VSCPacket has not yet been relayed + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, + }, + }, + }, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + // bob has now opted in + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + }, + }, + }, + } + + return s +} + +// stepsTopNChain starts a provider chain and a Top-N chain and opts in and out validators +func stepsTopNChain() []Step { + s := []Step{ + { + // start a new chain where "alice", "bob", and "carol" have 20%, 30%, and 50% of + // the total voting power respectively + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 300000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 500000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 200, + ValidatorID("bob"): 300, + ValidatorID("carol"): 500, + }, + }, + }, + }, + { + // propose a Top-N chain with N = 51% and hence both "carol" and "bob" have to validate + 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: "PROPOSAL_STATUS_VOTING_PERIOD", + }, + }, + }, + }, + }, + { + // change the consumer key "carol" is using on the consumer chain to be the one "carol" uses when opting in + Action: AssignConsumerPubKeyAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("carol"), + // reconfigure the node -> validator was using provider key + // until this point -> key matches config.consumerValPubKey for "bob" + ConsumerPubkey: getDefaultValidators()[ValidatorID("carol")].ConsumerValPubKey, + ReconfigureNode: true, + }, + State: State{}, + }, + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"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: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 300000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 500000000, 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{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 300, + ValidatorID("carol"): 500, + }, + }, + }, + }, + { + 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{}, + }, + { + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + // "alice" is not yet opted in because the VSCPacket has not yet been relayed + ValidatorID("alice"): 0, + ValidatorID("bob"): 300, + ValidatorID("carol"): 500, + }, + }, + }, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + // "alice" is now opted in + ValidatorID("alice"): 200, + ValidatorID("bob"): 300, + ValidatorID("carol"): 500, + }, + }, + }, + }, + { + Action: OptOutAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("carol"), + }, + State: State{}, + }, + { + Action: OptOutAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("bob"), + }, + State: State{}, + }, + { + // opting out "bob" or "carol" does not work because they belong to the Top N validators + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 200, + ValidatorID("bob"): 300, + ValidatorID("carol"): 500, + }, + }, + }, + }, + { + Action: OptOutAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("alice"), + }, + State: State{}, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + // "alice" has now opted out + ValidatorID("alice"): 0, + ValidatorID("bob"): 300, + ValidatorID("carol"): 500, + }, + }, + }, + }, + } + + return s +} diff --git a/tests/e2e/test_driver.go b/tests/e2e/test_driver.go index 052559b3ba..a12dc7862e 100644 --- a/tests/e2e/test_driver.go +++ b/tests/e2e/test_driver.go @@ -138,6 +138,10 @@ func (td *DefaultDriver) runAction(action interface{}) error { td.testCfg.startConsumerEvidenceDetector(action, td.target, td.verbose) case SubmitChangeRewardDenomsProposalAction: td.testCfg.submitChangeRewardDenomsProposal(action, td.target, td.verbose) + case OptInAction: + td.testCfg.optIn(action, td.target, td.verbose) + case OptOutAction: + td.testCfg.optOut(action, td.target, td.verbose) default: log.Fatalf("unknown action in testRun %s: %#v", td.testCfg.name, action) }