From a77eea15c913d1f401ee4cbac0e08d5fbf39565c Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 14 Aug 2023 23:24:41 +0200 Subject: [PATCH 01/40] feat!: add ICS misbehaviour handling (#826) * define msg to submit misbehaviour to provider implement msg handling logic e2e test msg handling logic * wip: get byzantine validators in misbehavioiur handling * add tx handler * format HandleConsumerMisbehaviour * add tx handler * add debugging stuff * Add misbehaviour handler * create message for consumer double voting evidence * add DRAFT double vote handler * Add cli cmd for submit consumer double voting * Add double-vote handler * add last update * fix jailing * pass first jailing integration test * format tests * doc * save * update e2e tests' * fix typo and improve docs * remove unwanted tm evidence protofile * fix typos * update submit-consumer-misbehaviour cli description * check that header1 and header2 have the same TrustedValidators * feat: add e2e tests for ICS misbehaviour (#1118) * remove unwanted changes * fix hermes config with assigned key * revert unwanted changes * revert local setup * remove log file * typo * update doc * update ICS misbehaviour test * update ICS misbehaviour test * revert mixed commits * add doc * lint * update to handle only equivocations * improve doc * update doc * update E2E tests comment * optimize signatures check * doc * update e2e tests * linter * remove todo * Feat: avoid race condition in ICS misbehaviour handling (#1148) * remove unwanted changes * fix hermes config with assigned key * revert unwanted changes * revert local setup * remove log file * typo * update doc * update ICS misbehaviour test * update ICS misbehaviour test * revert mixed commits * update ICS misbehaviour test * update ICS misbehaviour test * Add test for MsgSubmitConsumerMisbehaviour parsing * fix linter * save progress * add CheckMisbehaviourAndUpdateState * update integration tests * typo * remove e2e tests from another PRs * cleaning' * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir * update integration tests * save * save * nits * remove todo * lint * Update x/ccv/provider/keeper/misbehaviour.go --------- Co-authored-by: Anca Zamfir Co-authored-by: Marius Poke * Update x/ccv/provider/client/cli/tx.go Co-authored-by: Anca Zamfir * Update x/ccv/provider/client/cli/tx.go Co-authored-by: Anca Zamfir * add attributes to EventTypeSubmitConsumerMisbehaviour * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir * apply review suggestions * fix docstring * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir * fix link * apply review suggestions * update docstring --------- Co-authored-by: Anca Zamfir Co-authored-by: Marius Poke --- Dockerfile | 2 +- .../ccv/provider/v1/tx.proto | 18 +- tests/e2e/actions.go | 28 +- tests/e2e/actions_consumer_misbehaviour.go | 92 ++++ tests/e2e/config.go | 84 ++++ tests/e2e/main.go | 5 + tests/e2e/state.go | 39 ++ tests/e2e/steps.go | 7 + tests/e2e/steps_consumer_misbehaviour.go | 253 ++++++++++ tests/e2e/testnet-scripts/fork-consumer.sh | 112 +++++ tests/e2e/testnet-scripts/hermes-config.toml | 18 +- tests/e2e/testnet-scripts/start-chain.sh | 9 +- tests/integration/misbehaviour.go | 394 ++++++++++++++++ testutil/integration/debug_test.go | 16 + testutil/keeper/mocks.go | 55 +++ x/ccv/provider/client/cli/tx.go | 49 ++ x/ccv/provider/handler.go | 3 + x/ccv/provider/keeper/misbehaviour.go | 164 +++++++ x/ccv/provider/keeper/msg_server.go | 20 + x/ccv/provider/types/codec.go | 10 + x/ccv/provider/types/msg.go | 48 +- x/ccv/provider/types/tx.pb.go | 444 ++++++++++++++++-- x/ccv/types/events.go | 15 +- x/ccv/types/expected_keepers.go | 4 + 24 files changed, 1842 insertions(+), 47 deletions(-) create mode 100644 tests/e2e/actions_consumer_misbehaviour.go create mode 100644 tests/e2e/steps_consumer_misbehaviour.go create mode 100644 tests/e2e/testnet-scripts/fork-consumer.sh create mode 100644 tests/integration/misbehaviour.go create mode 100644 x/ccv/provider/keeper/misbehaviour.go diff --git a/Dockerfile b/Dockerfile index 4d81392316..03939617be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ RUN go mod tidy RUN make install # Get Hermes build -FROM ghcr.io/informalsystems/hermes:1.4.1 AS hermes-builder +FROM otacrew/hermes-ics:latest AS hermes-builder # Get CometMock FROM informalofftermatt/cometmock:latest as cometmock-builder diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 705e155317..61be3064ea 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -7,11 +7,13 @@ import "google/api/annotations.proto"; import "gogoproto/gogo.proto"; import "cosmos_proto/cosmos.proto"; import "google/protobuf/any.proto"; +import "ibc/lightclients/tendermint/v1/tendermint.proto"; // Msg defines the Msg service. service Msg { rpc AssignConsumerKey(MsgAssignConsumerKey) returns (MsgAssignConsumerKeyResponse); rpc RegisterConsumerRewardDenom(MsgRegisterConsumerRewardDenom) returns (MsgRegisterConsumerRewardDenomResponse); + rpc SubmitConsumerMisbehaviour(MsgSubmitConsumerMisbehaviour) returns (MsgSubmitConsumerMisbehaviourResponse); } message MsgAssignConsumerKey { @@ -42,4 +44,18 @@ message MsgRegisterConsumerRewardDenom { } // MsgRegisterConsumerRewardDenomResponse defines the Msg/RegisterConsumerRewardDenom response type. -message MsgRegisterConsumerRewardDenomResponse {} \ No newline at end of file +message MsgRegisterConsumerRewardDenomResponse {} + +// MsgSubmitConsumerMisbehaviour defines a message that reports a misbehaviour +// observed on a consumer chain +// Note that the misbheaviour' headers must contain the same trusted states +message MsgSubmitConsumerMisbehaviour { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + string submitter = 1; + // The Misbehaviour of the consumer chain wrapping + // two conflicting IBC headers + ibc.lightclients.tendermint.v1.Misbehaviour misbehaviour = 2; +} + +message MsgSubmitConsumerMisbehaviourResponse {} diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 06bcb7f5f7..9c7d41af17 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -63,7 +63,7 @@ type StartChainAction struct { validators []StartChainValidator // Genesis changes specific to this action, appended to genesis changes defined in chain config genesisChanges string - skipGentx bool + isConsumer bool } type StartChainValidator struct { @@ -133,7 +133,7 @@ func (tr TestRun) startChain( cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash", "/testnet-scripts/start-chain.sh", chainConfig.binaryName, string(vals), string(chainConfig.chainId), chainConfig.ipPrefix, genesisChanges, - fmt.Sprint(action.skipGentx), + fmt.Sprint(action.isConsumer), // override config/config.toml for each node on chain // usually timeout_commit and peer_gossip_sleep_duration are changed to vary the test run duration // lower timeout_commit means the blocks are produced faster making the test run shorter @@ -170,6 +170,7 @@ func (tr TestRun) startChain( tr.addChainToRelayer(addChainToRelayerAction{ chain: action.chain, validator: action.validators[0].id, + consumer: action.isConsumer, }, verbose) } @@ -280,6 +281,8 @@ func (tr TestRun) submitConsumerAdditionProposal( if err != nil { log.Fatal(err, "\n", string(bz)) } + + tr.waitBlocks(action.chain, 1, 5*time.Second) } type submitConsumerRemovalProposalAction struct { @@ -521,7 +524,7 @@ func (tr TestRun) voteGovProposal( } wg.Wait() - time.Sleep(time.Duration(tr.chainConfigs[action.chain].votingWaitTime) * time.Second) + time.Sleep((time.Duration(tr.chainConfigs[action.chain].votingWaitTime)) * time.Second) } type startConsumerChainAction struct { @@ -564,7 +567,7 @@ func (tr TestRun) startConsumerChain( chain: action.consumerChain, validators: action.validators, genesisChanges: consumerGenesis, - skipGentx: true, + isConsumer: true, }, verbose) } @@ -698,6 +701,7 @@ func (tr TestRun) startChangeover( type addChainToRelayerAction struct { chain chainID validator validatorID + consumer bool } const hermesChainConfigTemplate = ` @@ -715,6 +719,7 @@ rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "14days" websocket_addr = "%s" +ccv_consumer_chain = %v [chains.gas_price] denom = "stake" @@ -813,7 +818,7 @@ func (tr TestRun) addChainToHermes( keyName, rpcAddr, wsAddr, - // action.consumer, + action.consumer, ) bashCommand := fmt.Sprintf(`echo '%s' >> %s`, chainConfig, "/root/.hermes/config.toml") @@ -827,7 +832,16 @@ func (tr TestRun) addChainToHermes( } // Save mnemonic to file within container - saveMnemonicCommand := fmt.Sprintf(`echo '%s' > %s`, tr.validatorConfigs[action.validator].mnemonic, "/root/.hermes/mnemonic.txt") + var mnemonic string + if tr.validatorConfigs[action.validator].useConsumerKey && action.consumer { + mnemonic = tr.validatorConfigs[action.validator].consumerMnemonic + } else { + mnemonic = tr.validatorConfigs[action.validator].mnemonic + } + + saveMnemonicCommand := fmt.Sprintf(`echo '%s' > %s`, mnemonic, "/root/.hermes/mnemonic.txt") + fmt.Println("Add to hermes", action.validator) + fmt.Println(mnemonic) //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. bz, err = exec.Command("docker", "exec", tr.containerConfig.instanceName, "bash", "-c", saveMnemonicCommand, @@ -1767,6 +1781,8 @@ func (tr TestRun) assignConsumerPubKey(action assignConsumerPubKeyAction, verbos valCfg.useConsumerKey = true tr.validatorConfigs[action.validator] = valCfg } + + time.Sleep(1 * time.Second) } // slashThrottleDequeue polls slash queue sizes until nextQueueSize is achieved diff --git a/tests/e2e/actions_consumer_misbehaviour.go b/tests/e2e/actions_consumer_misbehaviour.go new file mode 100644 index 0000000000..2b01c2818e --- /dev/null +++ b/tests/e2e/actions_consumer_misbehaviour.go @@ -0,0 +1,92 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "os/exec" + "time" +) + +type forkConsumerChainAction struct { + consumerChain chainID + providerChain chainID + validator validatorID + relayerConfig string +} + +func (tr TestRun) forkConsumerChain(action forkConsumerChainAction, verbose bool) { + valCfg := tr.validatorConfigs[action.validator] + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + configureNodeCmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash", + "/testnet-scripts/fork-consumer.sh", tr.chainConfigs[action.consumerChain].binaryName, + string(action.validator), string(action.consumerChain), + tr.chainConfigs[action.consumerChain].ipPrefix, + tr.chainConfigs[action.providerChain].ipPrefix, + valCfg.mnemonic, + action.relayerConfig, + ) + + if verbose { + fmt.Println("forkConsumerChain - reconfigure node cmd:", configureNodeCmd.String()) + } + + cmdReader, err := configureNodeCmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + configureNodeCmd.Stderr = configureNodeCmd.Stdout + + if err := configureNodeCmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("fork consumer validator : " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + + time.Sleep(5 * time.Second) +} + +type updateLightClientAction struct { + hostChain chainID + relayerConfig string + clientID string +} + +func (tr TestRun) updateLightClient( + action updateLightClientAction, + verbose bool, +) { + // hermes clear packets ibc0 transfer channel-13 + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", + "--config", action.relayerConfig, + "update", + "client", + "--client", action.clientID, + "--host-chain", string(action.hostChain), + ) + if verbose { + log.Println("updateLightClientAction cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + tr.waitBlocks(action.hostChain, 5, 30*time.Second) +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 3038f69f4e..efed270ce7 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -376,6 +376,90 @@ func ChangeoverTestRun() TestRun { } } +func ConsumerMisbehaviourTestRun() TestRun { + return TestRun{ + name: "misbehaviour", + containerConfig: ContainerConfig{ + containerName: "interchain-security-container", + instanceName: "interchain-security-instance", + ccvVersion: "1", + now: time.Now(), + }, + validatorConfigs: map[validatorID]ValidatorConfig{ + validatorID("alice"): { + mnemonic: "pave immune ethics wrap gain ceiling always holiday employ earth tumble real ice engage false unable carbon equal fresh sick tattoo nature pupil nuclear", + delAddress: "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + valoperAddress: "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", + valconsAddress: "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + privValidatorKey: `{"address":"06C0F3E47CC5C748269088DC2F36411D3AAA27C6","pub_key":{"type":"tendermint/PubKeyEd25519","value":"RrclQz9bIhkIy/gfL485g3PYMeiIku4qeo495787X10="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uX+ZpDMg89a6gtqs/+MQpCTSqlkZ0nJQJOhLlCJvwvdGtyVDP1siGQjL+B8vjzmDc9gx6IiS7ip6jj3nvztfXQ=="}}`, + nodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"fjw4/DAhyRPnwKgXns5SV7QfswRSXMWJpHS7TyULDmJ8ofUc5poQP8dgr8bZRbCV5RV8cPqDq3FPdqwpmUbmdA=="}}`, + ipSuffix: "4", + + // consumer chain assigned key + consumerMnemonic: "exile install vapor thing little toss immune notable lounge december final easy strike title end program interest quote cloth forget forward job october twenty", + consumerDelAddress: "cosmos1eeeggku6dzk3mv7wph3zq035rhtd890sjswszd", + consumerValoperAddress: "cosmosvaloper1eeeggku6dzk3mv7wph3zq035rhtd890shy69w7", + consumerValconsAddress: "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe", + consumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="}`, + consumerPrivValidatorKey: `{"address":"DF090A4880B54CD57B2A79E64D9E969BD7514B09","pub_key":{"type":"tendermint/PubKeyEd25519","value":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TRJgf7lkTjs/sj43pyweEOanyV7H7fhnVivOi0A4yjW6NjXgCCilX3TshiA8CT/nHxz3brtLh9B/z2fJ4I9N6w=="}}`, + consumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"F966RL9pi20aXRzEBe4D0xRQJtZt696Xxz44XUON52cFc83FMn1WXJbP6arvA2JPyn2LA3DLKCFHSgALrCGXGA=="}}`, + useConsumerKey: true, + }, + validatorID("bob"): { + mnemonic: "glass trip produce surprise diamond spin excess gaze wash drum human solve dress minor artefact canoe hard ivory orange dinner hybrid moral potato jewel", + delAddress: "cosmos1dkas8mu4kyhl5jrh4nzvm65qz588hy9qcz08la", + valoperAddress: "cosmosvaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qakmjnw", + valconsAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", + privValidatorKey: `{"address":"99BD3A72EF12CD024E7584B3AC900AE3743C6ADF","pub_key":{"type":"tendermint/PubKeyEd25519","value":"mAN6RXYxSM4MNGSIriYiS7pHuwAcOHDQAy9/wnlSzOI="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"QePcwfWtOavNK7pBJrtoLMzarHKn6iBWfWPFeyV+IdmYA3pFdjFIzgw0ZIiuJiJLuke7ABw4cNADL3/CeVLM4g=="}}`, + nodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TQ4vHcO/vKdzGtWpelkX53WdMQd4kTsWGFrdcatdXFvWyO215Rewn5IRP0FszPLWr2DqPzmuH8WvxYGk5aeOXw=="}}`, + ipSuffix: "5", + + // consumer chain assigned key + consumerMnemonic: "grunt list hour endless observe better spoil penalty lab duck only layer vague fantasy satoshi record demise topple space shaft solar practice donor sphere", + consumerDelAddress: "cosmos1q90l6j6lzzgt460ehjj56azknlt5yrd4s38n97", + consumerValoperAddress: "cosmosvaloper1q90l6j6lzzgt460ehjj56azknlt5yrd449nxfd", + consumerValconsAddress: "cosmosvalcons1uuec3cjxajv5te08p220usrjhkfhg9wyvqn0tm", + consumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="}`, + consumerPrivValidatorKey: `{"address":"E73388E246EC9945E5E70A94FE4072BD937415C4","pub_key":{"type":"tendermint/PubKeyEd25519","value":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"OFR4w+FC6EMw5fAGTrHVexyPrjzQ7QfqgZOMgVf0izlCUb6Jh7oDJim9jXP1E0koJWUfXhD+pLPgSMZ0YKu7eg=="}}`, + consumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uhPCqnL2KE8m/8OFNLQ5bN3CJr6mds+xfBi0E4umT/s2uWiJhet+vbYx88DHSdof3gGFNTIzAIxSppscBKX96w=="}}`, + useConsumerKey: false, + }, + }, + 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 = \"10\" | " + + ".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\"", + }, + }, + tendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + + `s/peer_gossip_sleep_duration = "100ms"/peer_gossip_sleep_duration = "50ms"/;` + + // Required to start consumer chain by running a single big validator + `s/fast_sync = true/fast_sync = false/;`, + } +} + func (s *TestRun) SetDockerConfig(localSdkPath string, useGaia bool, gaiaTag string) { if localSdkPath != "" { fmt.Println("USING LOCAL SDK", localSdkPath) diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 737a318de2..4c7b6722cd 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -62,6 +62,7 @@ func main() { {DemocracyTestRun(true), democracySteps}, {DemocracyTestRun(false), rewardDenomConsumerSteps}, {SlashThrottleTestRun(), slashThrottleSteps}, + {ConsumerMisbehaviourTestRun(), consumerMisbehaviourSteps}, } if includeMultiConsumer != nil && *includeMultiConsumer { testRuns = append(testRuns, testRunWithSteps{MultiConsumerTestRun(), multipleConsumers}) @@ -173,6 +174,10 @@ func (tr *TestRun) runStep(step Step, verbose bool) { tr.startRelayer(action, verbose) case registerConsumerRewardDenomAction: tr.registerConsumerRewardDenom(action, verbose) + case forkConsumerChainAction: + tr.forkConsumerChain(action, verbose) + case updateLightClientAction: + tr.updateLightClient(action, verbose) default: log.Fatalf("unknown action in testRun %s: %#v", tr.name, action) } diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 15500dd01f..8d9ba9a81e 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -28,6 +28,7 @@ type ChainState struct { ConsumerChainQueueSizes *map[chainID]uint GlobalSlashQueueSize *uint RegisteredConsumerRewardDenoms *[]string + ClientsFrozenHeights *map[string]clienttypes.Height } type Proposal interface { @@ -184,6 +185,14 @@ func (tr TestRun) getChainState(chain chainID, modelState ChainState) ChainState chainState.RegisteredConsumerRewardDenoms = ®isteredConsumerRewardDenoms } + if modelState.ClientsFrozenHeights != nil { + chainClientsFrozenHeights := map[string]clienttypes.Height{} + for id := range *modelState.ClientsFrozenHeights { + chainClientsFrozenHeights[id] = tr.getClientFrozenHeight(chain, id) + } + chainState.ClientsFrozenHeights = &chainClientsFrozenHeights + } + return chainState } @@ -737,3 +746,33 @@ func (tr TestRun) getQueryNodeIP(chain chainID) string { } return fmt.Sprintf("%s.253", tr.chainConfigs[chain].ipPrefix) } + +// getClientFrozenHeight returns the frozen height for a client with the given client ID +// by querying the hosting chain with the given chainID +func (tr TestRun) getClientFrozenHeight(chain chainID, clientID string) clienttypes.Height { + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, tr.chainConfigs[chainID("provi")].binaryName, + "query", "ibc", "client", "state", clientID, + `--node`, tr.getQueryNode(chainID("provi")), + `-o`, `json`, + ) + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + frozenHeight := gjson.Get(string(bz), "client_state.frozen_height") + + revHeight, err := strconv.Atoi(frozenHeight.Get("revision_height").String()) + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + revNumber, err := strconv.Atoi(frozenHeight.Get("revision_number").String()) + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + return clienttypes.Height{RevisionHeight: uint64(revHeight), RevisionNumber: uint64(revNumber)} +} diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 7613b05558..78a56654e6 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -87,3 +87,10 @@ var changeoverSteps = concatSteps( stepsPostChangeoverDelegate("sover"), ) + +var consumerMisbehaviourSteps = concatSteps( + // start provider and consumer chain + stepsStartChainsWithSoftOptOut("consu"), + // make consumer validator to misbehave and get jail + stepsCauseConsumerMisbehaviour("consu"), +) diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go new file mode 100644 index 0000000000..7d7fda94b1 --- /dev/null +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -0,0 +1,253 @@ +package main + +import ( + clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" +) + +// starts a provider chain and a consumer chain with two validators, +// where the voting power is distributed in order that the smallest validator +// can soft opt-out of validating the consumer chain. +func stepsStartChainsWithSoftOptOut(consumerName string) []Step { + s := []Step{ + { + // Create a provider chain with two validators, where one validator holds 96% of the voting power + // and the other validator holds 4% of the voting power. + action: StartChainAction{ + chain: chainID("provi"), + validators: []StartChainValidator{ + {id: validatorID("alice"), stake: 500000000, allocation: 10000000000}, + {id: validatorID("bob"), stake: 20000000, allocation: 10000000000}, + }, + }, + state: State{ + chainID("provi"): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9500000000, + validatorID("bob"): 9980000000, + }, + }, + }, + }, + { + action: submitConsumerAdditionProposalAction{ + chain: chainID("provi"), + from: validatorID("alice"), + deposit: 10000001, + consumerChain: chainID(consumerName), + spawnTime: 0, + initialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + }, + state: State{ + chainID("provi"): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9489999999, + validatorID("bob"): 9980000000, + }, + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: chainID(consumerName), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: "PROPOSAL_STATUS_VOTING_PERIOD", + }, + }, + }, + }, + }, + // add a consumer key before the chain starts + // the key will be present in consumer genesis initial_val_set + { + action: assignConsumerPubKeyAction{ + chain: chainID(consumerName), + validator: validatorID("alice"), + consumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="}`, + // consumer chain has not started + // we don't need to reconfigure the node + // since it will start with consumer key + reconfigureNode: false, + }, + state: State{ + chainID(consumerName): ChainState{ + AssignedKeys: &map[validatorID]string{ + validatorID("alice"): "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe", + }, + ProviderKeys: &map[validatorID]string{ + validatorID("alice"): "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + }, + }, + }, + }, + { + 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(consumerName), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: "PROPOSAL_STATUS_PASSED", + }, + }, + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9500000000, + validatorID("bob"): 9980000000, + }, + }, + }, + }, + { + // start a consumer chain using a single big validator knowing that it holds more than 2/3 of the voting power + // and that the other validators hold less than 5% so they won't get jailed thanks to the sof opt-out mechanism. + action: startConsumerChainAction{ + consumerChain: chainID(consumerName), + providerChain: chainID("provi"), + validators: []StartChainValidator{ + {id: validatorID("alice"), 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("provi"): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9500000000, + validatorID("bob"): 9980000000, + }, + }, + chainID(consumerName): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 10000000000, + }, + }, + }, + }, + { + action: addIbcConnectionAction{ + chainA: chainID(consumerName), + chainB: chainID("provi"), + clientA: 0, + clientB: 0, + }, + state: State{}, + }, + { + action: addIbcChannelAction{ + chainA: chainID(consumerName), + chainB: chainID("provi"), + connectionA: 0, + portA: "consumer", // TODO: check port mapping + portB: "provider", + order: "ordered", + }, + state: State{}, + }, + // delegate some token and relay the resulting VSC packets + // in oder to initiates the CCV channel + { + action: delegateTokensAction{ + chain: chainID("provi"), + from: validatorID("alice"), + to: validatorID("alice"), + amount: 11000000, + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 20, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 20, + }, + }, + }, + }, + { + action: relayPacketsAction{ + chainA: chainID("provi"), + chainB: chainID(consumerName), + port: "provider", + channel: 0, + }, + state: State{ + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 20, + }, + }, + }, + }, + } + + return s +} + +// stepsCauseConsumerMisbehaviour causes a ICS misbehaviour by forking a consumer chain. +func stepsCauseConsumerMisbehaviour(consumerName string) []Step { + consumerClientID := "07-tendermint-0" + forkRelayerConfig := "/root/.hermes/config_fork.toml" + return []Step{ + { + // fork the consumer chain by cloning of its validator node + action: forkConsumerChainAction{ + consumerChain: chainID(consumerName), + providerChain: chainID("provi"), + validator: validatorID("alice"), + relayerConfig: forkRelayerConfig, + }, + state: State{}, + }, + { + // start relayer to detect ICS misbehaviour + action: startRelayerAction{}, + state: State{}, + }, + { + // update the fork consumer client to create a light client attack + // which should trigger a ICS misbehaviour message + action: updateLightClientAction{ + hostChain: chainID("provi"), + relayerConfig: forkRelayerConfig, // this relayer config uses the "forked" consumer + clientID: consumerClientID, + }, + state: State{ + chainID("provi"): ChainState{ + // validator should be jailed on the provider + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 0, + validatorID("bob"): 20, + }, + // The consumer light client should not be frozen + ClientsFrozenHeights: &map[string]clienttypes.Height{ + "07-tendermint-0": { + RevisionNumber: 0, + RevisionHeight: 0, + }, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 20, + }, + }, + }, + }, + } +} diff --git a/tests/e2e/testnet-scripts/fork-consumer.sh b/tests/e2e/testnet-scripts/fork-consumer.sh new file mode 100644 index 0000000000..0bf96fcb79 --- /dev/null +++ b/tests/e2e/testnet-scripts/fork-consumer.sh @@ -0,0 +1,112 @@ +#!/bin/bash +set -eux + +# The gaiad binary +BIN=$1 + +# the validator ID used to perform the fork +VAL_ID=$2 + +# The consumer chain ID +CHAIN_ID=$3 + +# chain's IP address prefix; $PROV_CHAIN_PREFIX, $CONS_CHAIN_PREFIX... +# see chain config for details +CONS_CHAIN_PREFIX=$4 + +PROV_CHAIN_PREFIX=$5 + +VAL_MNEMONIC=$6 + +FORK_HERMES_CONFIG=$7 + +FORK_NODE_DIR=/$CHAIN_ID/validatorfork + +# create directory for forking/double-signing node +mkdir $FORK_NODE_DIR +cp -r /$CHAIN_ID/validator$VAL_ID/* $FORK_NODE_DIR + +# remove persistent peers +rm -f $FORK_NODE_DIR/addrbook.json + +# add fork to hermes relayer +tee $FORK_HERMES_CONFIG< mnemonic.txt + +# Start the validator forking the consumer chain +# using the sybil IP allocation +ip netns exec $CHAIN_ID-sybil $BIN \ + --home $FORK_NODE_DIR \ + --address tcp://$CONS_CHAIN_PREFIX.252:26655 \ + --rpc.laddr tcp://$CONS_CHAIN_PREFIX.252:26658 \ + --grpc.address $CONS_CHAIN_PREFIX.252:9091 \ + --log_level info \ + --p2p.laddr tcp://$CONS_CHAIN_PREFIX.252:26656 \ + --grpc-web.enable=false start &> /consu/validatorfork/logs & + diff --git a/tests/e2e/testnet-scripts/hermes-config.toml b/tests/e2e/testnet-scripts/hermes-config.toml index eb8154d95b..89c1f0a0bb 100644 --- a/tests/e2e/testnet-scripts/hermes-config.toml +++ b/tests/e2e/testnet-scripts/hermes-config.toml @@ -1,2 +1,18 @@ [global] - log_level = "info" \ No newline at end of file +log_level = "debug" + +[mode] + +[mode.clients] +enabled = true +refresh = true +misbehaviour = true + +[mode.connections] +enabled = false + +[mode.channels] +enabled = false + +[mode.packets] +enabled = true \ No newline at end of file diff --git a/tests/e2e/testnet-scripts/start-chain.sh b/tests/e2e/testnet-scripts/start-chain.sh index 9d6e73fdbb..8bc0dbadca 100644 --- a/tests/e2e/testnet-scripts/start-chain.sh +++ b/tests/e2e/testnet-scripts/start-chain.sh @@ -198,6 +198,7 @@ do #'s/foo/bar/;s/abc/def/' sed -i "$TENDERMINT_CONFIG_TRANSFORM" $CHAIN_ID/validator$VAL_ID/config/config.toml fi + done @@ -257,8 +258,12 @@ do fi done - # Remove leading comma and concat to flag - PERSISTENT_PEERS="--p2p.persistent_peers ${PERSISTENT_PEERS:1}" + + if [ "$PERSISTENT_PEERS" != "" ]; then + # Remove leading comma and concat to flag + PERSISTENT_PEERS="--p2p.persistent_peers ${PERSISTENT_PEERS:1}" + fi + ARGS="$GAIA_HOME $LISTEN_ADDRESS $RPC_ADDRESS $GRPC_ADDRESS $LOG_LEVEL $P2P_ADDRESS $ENABLE_WEBGRPC $PERSISTENT_PEERS" if [[ "$USE_COMETMOCK" == "true" ]]; then diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go new file mode 100644 index 0000000000..c3b590d5c1 --- /dev/null +++ b/tests/integration/misbehaviour.go @@ -0,0 +1,394 @@ +package integration + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" + + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +// TestHandleConsumerMisbehaviour tests that handling a valid misbehaviour, +// with conflicting headers forming an equivocation, results in the jailing and tombstoning of the validators +func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { + s.SetupCCVChannel(s.path) + // required to have the consumer client revision height greater than 0 + s.SendEmptyVSCPacket() + + for _, v := range s.providerChain.Vals.Validators { + s.setDefaultValSigningInfo(*v) + } + + altTime := s.providerCtx().BlockTime().Add(time.Minute) + + clientHeight := s.consumerChain.LastHeader.TrustedHeight + clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) + clientSigners := s.consumerChain.Signers + + misb := &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + // create a different header by changing the header timestamp only + // in order to create an equivocation, i.e. both headers have the same deterministic states + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(10*time.Second), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + + err := s.providerApp.GetProviderKeeper().HandleConsumerMisbehaviour(s.providerCtx(), *misb) + s.NoError(err) + + // verify that validators are jailed and tombstoned + for _, v := range clientTMValset.Validators { + consuAddr := sdk.ConsAddress(v.Address.Bytes()) + provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, types.NewConsumerConsAddress(consuAddr)) + val, ok := s.providerApp.GetTestStakingKeeper().GetValidatorByConsAddr(s.providerCtx(), provAddr.Address) + s.Require().True(ok) + s.Require().True(val.Jailed) + s.Require().True(s.providerApp.GetTestSlashingKeeper().IsTombstoned(s.providerCtx(), provAddr.Address)) + } +} + +func (s *CCVTestSuite) TestGetByzantineValidators() { + s.SetupCCVChannel(s.path) + // required to have the consumer client revision height greater than 0 + s.SendEmptyVSCPacket() + + altTime := s.providerCtx().BlockTime().Add(time.Minute) + + clientHeight := s.consumerChain.LastHeader.TrustedHeight + clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) + clientSigners := s.consumerChain.Signers + + // Create a validator set subset + altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:3]) + altSigners := make(map[string]tmtypes.PrivValidator, 1) + altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] + altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] + altSigners[clientTMValset.Validators[2].Address.String()] = clientSigners[clientTMValset.Validators[2].Address.String()] + + // TODO: figure out how to test an amnesia cases for "amnesia" attack + testCases := []struct { + name string + misbehaviour *ibctmtypes.Misbehaviour + expByzantineValidators []*tmtypes.Validator + expPass bool + }{ + { + "invalid misbehaviour - Header1 is empty", + &ibctmtypes.Misbehaviour{ + Header1: &ibctmtypes.Header{}, + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + nil, + false, + }, + { + "invalid headers - Header2 is empty", + &ibctmtypes.Misbehaviour{ + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: &ibctmtypes.Header{}, + }, + nil, + false, + }, + { + "invalid light client attack - lunatic attack", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + // Expect to get only the validators + // who signed both headers are returned + altValset.Validators, + true, + }, + { + "valid light client attack - equivocation", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Minute), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + }, + // Expect to get the entire valset since + // all validators double-signed + clientTMValset.Validators, + true, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + byzantineValidators, err := s.providerApp.GetProviderKeeper().GetByzantineValidators( + s.providerCtx(), + *tc.misbehaviour, + ) + if tc.expPass { + s.NoError(err) + // For both lunatic and equivocation attack all the validators + // who signed the bad header (Header2) should be in returned in the evidence + h2Valset := tc.misbehaviour.Header2.ValidatorSet + + s.Equal(len(h2Valset.Validators), len(byzantineValidators)) + + vs, err := tmtypes.ValidatorSetFromProto(tc.misbehaviour.Header2.ValidatorSet) + s.NoError(err) + + for _, v := range tc.expByzantineValidators { + idx, _ := vs.GetByAddress(v.Address) + s.True(idx >= 0) + } + + } else { + s.Error(err) + } + }) + } +} + +func (s *CCVTestSuite) TestCheckMisbehaviour() { + s.SetupCCVChannel(s.path) + // required to have the consumer client revision height greater than 0 + s.SendEmptyVSCPacket() + + // create signing info for all validators + for _, v := range s.providerChain.Vals.Validators { + s.setDefaultValSigningInfo(*v) + } + + // create a new header timestamp + headerTs := s.providerCtx().BlockTime().Add(time.Minute) + + // get trusted validators and height + clientHeight := s.consumerChain.LastHeader.TrustedHeight + clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) + clientSigners := s.consumerChain.Signers + + // create an alternative validator set using more than 1/3 of the trusted validator set + altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:2]) + altSigners := make(map[string]tmtypes.PrivValidator, 1) + altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] + altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] + testCases := []struct { + name string + misbehaviour *ibctmtypes.Misbehaviour + expPass bool + }{ + { + "client state not found - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: "clientID", + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + false, + }, + { + "invalid misbehaviour with empty header1 - shouldn't pass", + &ibctmtypes.Misbehaviour{ + Header1: &ibctmtypes.Header{}, + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + false, + }, + { + "invalid misbehaviour with different header height - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+2), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + false, + }, + { + "valid misbehaviour - should pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + // create header using a different validator set + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + true, + }, + { + "valid misbehaviour with already frozen client - should pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + // the resulting Header2 will have a different BlockID + // than Header1 since doesn't share the same valset and signers + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + true, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + err := s.providerApp.GetProviderKeeper().CheckMisbehaviour(s.providerCtx(), *tc.misbehaviour) + cs, ok := s.providerApp.GetIBCKeeper().ClientKeeper.GetClientState(s.providerCtx(), s.path.EndpointA.ClientID) + s.Require().True(ok) + // verify that the client wasn't frozen + s.Require().Zero(cs.(*ibctmtypes.ClientState).FrozenHeight) + if tc.expPass { + s.NoError(err) + } else { + s.Error(err) + } + }) + } +} diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index b6456663a9..2d9421300b 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -256,3 +256,19 @@ func TestQueueAndSendVSCMaturedPackets(t *testing.T) { func TestRecycleTransferChannel(t *testing.T) { runCCVTestByName(t, "TestRecycleTransferChannel") } + +// +// Misbehaviour test +// + +func TestHandleConsumerMisbehaviour(t *testing.T) { + runCCVTestByName(t, "TestHandleConsumerMisbehaviour") +} + +func TestGetByzantineValidators(t *testing.T) { + runCCVTestByName(t, "TestGetByzantineValidators") +} + +func TestCheckMisbehaviour(t *testing.T) { + runCCVTestByName(t, "TestCheckMisbehaviour") +} diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 4f931d8152..1e792e3a4a 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -678,6 +678,34 @@ func (m *MockClientKeeper) EXPECT() *MockClientKeeperMockRecorder { return m.recorder } +// CheckMisbehaviourAndUpdateState mocks base method. +func (m *MockClientKeeper) CheckMisbehaviourAndUpdateState(ctx types.Context, misbehaviour exported.Misbehaviour) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckMisbehaviourAndUpdateState", ctx, misbehaviour) + ret0, _ := ret[0].(error) + return ret0 +} + +// CheckMisbehaviourAndUpdateState indicates an expected call of CheckMisbehaviourAndUpdateState. +func (mr *MockClientKeeperMockRecorder) CheckMisbehaviourAndUpdateState(ctx, misbehaviour interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckMisbehaviourAndUpdateState", reflect.TypeOf((*MockClientKeeper)(nil).CheckMisbehaviourAndUpdateState), ctx, misbehaviour) +} + +// ClientStore mocks base method. +func (m *MockClientKeeper) ClientStore(ctx types.Context, clientID string) types.KVStore { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientStore", ctx, clientID) + ret0, _ := ret[0].(types.KVStore) + return ret0 +} + +// ClientStore indicates an expected call of ClientStore. +func (mr *MockClientKeeperMockRecorder) ClientStore(ctx, clientID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientStore", reflect.TypeOf((*MockClientKeeper)(nil).ClientStore), ctx, clientID) +} + // CreateClient mocks base method. func (m *MockClientKeeper) CreateClient(ctx types.Context, clientState exported.ClientState, consensusState exported.ConsensusState) (string, error) { m.ctrl.T.Helper() @@ -693,6 +721,21 @@ func (mr *MockClientKeeperMockRecorder) CreateClient(ctx, clientState, consensus return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateClient", reflect.TypeOf((*MockClientKeeper)(nil).CreateClient), ctx, clientState, consensusState) } +// GetClientConsensusState mocks base method. +func (m *MockClientKeeper) GetClientConsensusState(ctx types.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClientConsensusState", ctx, clientID, height) + ret0, _ := ret[0].(exported.ConsensusState) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetClientConsensusState indicates an expected call of GetClientConsensusState. +func (mr *MockClientKeeperMockRecorder) GetClientConsensusState(ctx, clientID, height interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClientConsensusState", reflect.TypeOf((*MockClientKeeper)(nil).GetClientConsensusState), ctx, clientID, height) +} + // GetClientState mocks base method. func (m *MockClientKeeper) GetClientState(ctx types.Context, clientID string) (exported.ClientState, bool) { m.ctrl.T.Helper() @@ -775,6 +818,18 @@ func (mr *MockDistributionKeeperMockRecorder) FundCommunityPool(ctx, amount, sen return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FundCommunityPool", reflect.TypeOf((*MockDistributionKeeper)(nil).FundCommunityPool), ctx, amount, sender) } +// SetClientState mocks base method. +func (m *MockClientKeeper) SetClientState(ctx types.Context, clientID string, clientState exported.ClientState) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetClientState", ctx, clientID, clientState) +} + +// SetClientState indicates an expected call of SetClientState. +func (mr *MockClientKeeperMockRecorder) SetClientState(ctx, clientID, clientState interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetClientState", reflect.TypeOf((*MockClientKeeper)(nil).SetClientState), ctx, clientID, clientState) +} + // MockConsumerHooks is a mock of ConsumerHooks interface. type MockConsumerHooks struct { ctrl *gomock.Controller diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 73b1df34c3..1cad8b38ce 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/version" + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" @@ -27,6 +28,7 @@ func GetTxCmd() *cobra.Command { cmd.AddCommand(NewAssignConsumerKeyCmd()) cmd.AddCommand(NewRegisterConsumerRewardDenomCmd()) + cmd.AddCommand(NewSubmitConsumerMisbehaviourCmd()) return cmd } @@ -99,3 +101,50 @@ $ %s tx provider register-consumer-reward-denom untrn --from mykey return cmd } + +func NewSubmitConsumerMisbehaviourCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "submit-consumer-misbehaviour [misbehaviour]", + Short: "submit an IBC misbehaviour for a consumer chain", + Long: strings.TrimSpace( + fmt.Sprintf(`Submit an IBC misbehaviour detected on a consumer chain. +An IBC misbehaviour contains two conflicting IBC client headers, which are used to form a light client attack evidence. +The misbehaviour type definition can be found in the IBC client messages, see ibc-go/proto/ibc/core/client/v1/tx.proto. + +Examples: +%s tx provider submit-consumer-misbehaviour [path/to/misbehaviour.json] --from node0 --home ../node0 --chain-id $CID + `, version.AppName)), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()). + WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + submitter := clientCtx.GetFromAddress() + var misbehaviour ibctmtypes.Misbehaviour + if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(args[1]), &misbehaviour); err != nil { + return err + } + + msg, err := types.NewMsgSubmitConsumerMisbehaviour(submitter, &misbehaviour) + if err != nil { + return err + } + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + _ = cmd.MarkFlagRequired(flags.FlagFrom) + + return cmd +} diff --git a/x/ccv/provider/handler.go b/x/ccv/provider/handler.go index dc0c8cbc4f..6fa38b5ddf 100644 --- a/x/ccv/provider/handler.go +++ b/x/ccv/provider/handler.go @@ -21,6 +21,9 @@ func NewHandler(k *keeper.Keeper) sdk.Handler { case *types.MsgRegisterConsumerRewardDenom: res, err := msgServer.RegisterConsumerRewardDenom(sdk.WrapSDKContext(ctx), msg) return sdk.WrapServiceResult(ctx, res, err) + case *types.MsgSubmitConsumerMisbehaviour: + res, err := msgServer.SubmitConsumerMisbehaviour(sdk.WrapSDKContext(ctx), msg) + return sdk.WrapServiceResult(ctx, res, err) default: return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) } diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go new file mode 100644 index 0000000000..4d8f517e95 --- /dev/null +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -0,0 +1,164 @@ +package keeper + +import ( + "sort" + + "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + ibcclienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +// HandleConsumerMisbehaviour checks if the given IBC misbehaviour corresponds to an equivocation light client attack, +// and in this case, jails and tombstones the Byzantine validators +func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { + logger := k.Logger(ctx) + + // Check that the misbehaviour is valid and that the client consensus states at trusted heights are within trusting period + if err := k.CheckMisbehaviour(ctx, misbehaviour); err != nil { + logger.Info("Misbehaviour rejected", err.Error()) + + return err + } + + // Since the misbehaviour packet was received within the trusting period + // w.r.t to the trusted consensus states the infraction age + // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go + + // Get Byzantine validators from the conflicting headers + byzantineValidators, err := k.GetByzantineValidators(ctx, misbehaviour) + if err != nil { + return err + } + + // jail and tombstone the Byzantine validators + for _, v := range byzantineValidators { + // convert consumer consensus address + consumerAddr := types.NewConsumerConsAddress(sdk.ConsAddress(v.Address.Bytes())) + providerAddr := k.GetProviderAddrFromConsumerAddr(ctx, misbehaviour.Header1.Header.ChainID, consumerAddr) + val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) + + if !ok || val.IsUnbonded() { + logger.Error("validator not found or is unbonded", providerAddr.String()) + continue + } + + // jail validator if not already + if !val.IsJailed() { + k.stakingKeeper.Jail(ctx, providerAddr.ToSdkConsAddr()) + } + + // tombstone validator if not already + if !k.slashingKeeper.IsTombstoned(ctx, providerAddr.ToSdkConsAddr()) { + k.slashingKeeper.Tombstone(ctx, providerAddr.ToSdkConsAddr()) + k.Logger(ctx).Info("validator tombstoned", "provider cons addr", providerAddr.String()) + } + + // update jail time to end after double sign jail duration + k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime) + } + + logger.Info( + "confirmed equivocation light client attack", + "byzantine validators", byzantineValidators, + ) + + return nil +} + +// GetByzantineValidators returns the validators that signed both headers. +// If the misbehavior is an equivocation light client attack, then these +// validators are the Byzantine validators. +func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) ([]*tmtypes.Validator, error) { + // construct the trusted and conflicted light blocks + lightBlock1, err := headerToLightBlock(*misbehaviour.Header1) + if err != nil { + return nil, err + } + lightBlock2, err := headerToLightBlock(*misbehaviour.Header2) + if err != nil { + return nil, err + } + + var validators []*tmtypes.Validator + + // compare the signatures of the headers + // and return the intersection of validators who signed both + + // create a map with the validators' address that signed header1 + header1Signers := map[string]struct{}{} + for _, sign := range lightBlock1.Commit.Signatures { + if sign.Absent() { + continue + } + header1Signers[sign.ValidatorAddress.String()] = struct{}{} + } + + // iterate over the header2 signers and check if they signed header1 + for _, sign := range lightBlock2.Commit.Signatures { + if sign.Absent() { + continue + } + if _, ok := header1Signers[sign.ValidatorAddress.String()]; ok { + _, val := lightBlock1.ValidatorSet.GetByAddress(sign.ValidatorAddress) + validators = append(validators, val) + } + } + + sort.Sort(tmtypes.ValidatorsByVotingPower(validators)) + return validators, nil +} + +// headerToLightBlock returns a CometBFT light block from the given IBC header +func headerToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { + sh, err := tmtypes.SignedHeaderFromProto(h.SignedHeader) + if err != nil { + return nil, err + } + + vs, err := tmtypes.ValidatorSetFromProto(h.ValidatorSet) + if err != nil { + return nil, err + } + + return &tmtypes.LightBlock{ + SignedHeader: sh, + ValidatorSet: vs, + }, nil +} + +// CheckMisbehaviour checks that headers in the given misbehaviour forms +// a valid light client attack and that the corresponding light client isn't expired +func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { + if err := misbehaviour.ValidateBasic(); err != nil { + return err + } + + clientState, found := k.clientKeeper.GetClientState(ctx, misbehaviour.GetClientID()) + if !found { + return sdkerrors.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot check misbehaviour for client with ID %s", misbehaviour.GetClientID()) + } + + clientStore := k.clientKeeper.ClientStore(ctx, misbehaviour.GetClientID()) + + // Check that the headers are at the same height to ensure that + // the misbehaviour is for a light client attack and not a time violation, + // see https://github.com/cosmos/ibc-go/blob/8f53c21361f9d65448a850c2eafcf3ab3c384a61/modules/light-clients/07-tendermint/types/misbehaviour_handle.go#L56 + if !misbehaviour.Header1.GetHeight().EQ(misbehaviour.Header2.GetHeight()) { + return sdkerrors.Wrap(ibcclienttypes.ErrInvalidMisbehaviour, "headers are not at same height") + } + + // CheckMisbehaviourAndUpdateState verifies the misbehaviour against the trusted consensus states + // but does NOT update the light client state. + // Note CheckMisbehaviourAndUpdateState returns an error if the trusted consensus states are expired + _, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbehaviour) + if err != nil { + return err + } + + return nil +} diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index ea5ff66220..80f325cc1b 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -126,3 +126,23 @@ func (k msgServer) RegisterConsumerRewardDenom(goCtx context.Context, msg *types return &types.MsgRegisterConsumerRewardDenomResponse{}, nil } + +func (k msgServer) SubmitConsumerMisbehaviour(goCtx context.Context, msg *types.MsgSubmitConsumerMisbehaviour) (*types.MsgSubmitConsumerMisbehaviourResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + if err := k.Keeper.HandleConsumerMisbehaviour(ctx, *msg.Misbehaviour); err != nil { + return &types.MsgSubmitConsumerMisbehaviourResponse{}, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + ccvtypes.EventTypeSubmitConsumerMisbehaviour, + sdk.NewAttribute(ccvtypes.AttributeConsumerMisbehaviour, msg.Misbehaviour.String()), + sdk.NewAttribute(ccvtypes.AttributeSubmitterAddress, msg.Submitter), + sdk.NewAttribute(ccvtypes.AttributeMisbehaviourClientId, msg.Misbehaviour.ClientId), + sdk.NewAttribute(ccvtypes.AttributeMisbehaviourHeight1, msg.Misbehaviour.Header1.GetHeight().String()), + sdk.NewAttribute(ccvtypes.AttributeMisbehaviourHeight2, msg.Misbehaviour.Header2.GetHeight().String()), + ), + }) + + return &types.MsgSubmitConsumerMisbehaviourResponse{}, nil +} diff --git a/x/ccv/provider/types/codec.go b/x/ccv/provider/types/codec.go index 3e4b34dd42..2f28b398f5 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/ibc-go/v4/modules/core/exported" ) // RegisterLegacyAminoCodec registers the necessary x/ibc transfer interfaces and concrete types @@ -36,6 +37,15 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { &EquivocationProposal{}, ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgSubmitConsumerMisbehaviour{}, + ) + registry.RegisterInterface( + "ibc.core.client.v1.Misbehaviour", + (*exported.Misbehaviour)(nil), + ) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 67ad99d10c..5c217f53b8 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -5,15 +5,21 @@ import ( "strings" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" ) // provider message types const ( TypeMsgAssignConsumerKey = "assign_consumer_key" TypeMsgRegisterConsumerRewardDenom = "register_consumer_reward_denom" + TypeMsgSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" ) -var _ sdk.Msg = &MsgAssignConsumerKey{} +var ( + _ sdk.Msg = &MsgAssignConsumerKey{} + _ sdk.Msg = &MsgSubmitConsumerMisbehaviour{} +) // NewMsgAssignConsumerKey creates a new MsgAssignConsumerKey instance. // Delegator address and validator address are the same. @@ -139,3 +145,43 @@ func (msg MsgRegisterConsumerRewardDenom) ValidateBasic() error { return nil } + +func NewMsgSubmitConsumerMisbehaviour(submitter sdk.AccAddress, misbehaviour *ibctmtypes.Misbehaviour) (*MsgSubmitConsumerMisbehaviour, error) { + return &MsgSubmitConsumerMisbehaviour{Submitter: submitter.String(), Misbehaviour: misbehaviour}, nil +} + +// Route implements the sdk.Msg interface. +func (msg MsgSubmitConsumerMisbehaviour) Route() string { return RouterKey } + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerMisbehaviour) Type() string { + return TypeMsgSubmitConsumerMisbehaviour +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerMisbehaviour) ValidateBasic() error { + if msg.Submitter == "" { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Submitter) + } + + if err := msg.Misbehaviour.ValidateBasic(); err != nil { + return err + } + return nil +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerMisbehaviour) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(&msg) + return sdk.MustSortJSON(bz) +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerMisbehaviour) GetSigners() []sdk.AccAddress { + addr, err := sdk.AccAddressFromBech32(msg.Submitter) + if err != nil { + // same behavior as in cosmos-sdk + panic(err) + } + return []sdk.AccAddress{addr} +} diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index 3603695359..f27ab52533 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -7,6 +7,7 @@ import ( context "context" fmt "fmt" _ "github.com/cosmos/cosmos-sdk/codec/types" + types "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" _ "github.com/gogo/protobuf/gogoproto" grpc1 "github.com/gogo/protobuf/grpc" proto "github.com/gogo/protobuf/proto" @@ -191,11 +192,92 @@ func (m *MsgRegisterConsumerRewardDenomResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgRegisterConsumerRewardDenomResponse proto.InternalMessageInfo +// MsgSubmitConsumerMisbehaviour defines a message that reports a misbehaviour +// observed on a consumer chain +// Note that the misbheaviour' headers must contain the same trusted states +type MsgSubmitConsumerMisbehaviour struct { + Submitter string `protobuf:"bytes,1,opt,name=submitter,proto3" json:"submitter,omitempty"` + // The Misbehaviour of the consumer chain wrapping + // two conflicting IBC headers + Misbehaviour *types.Misbehaviour `protobuf:"bytes,2,opt,name=misbehaviour,proto3" json:"misbehaviour,omitempty"` +} + +func (m *MsgSubmitConsumerMisbehaviour) Reset() { *m = MsgSubmitConsumerMisbehaviour{} } +func (m *MsgSubmitConsumerMisbehaviour) String() string { return proto.CompactTextString(m) } +func (*MsgSubmitConsumerMisbehaviour) ProtoMessage() {} +func (*MsgSubmitConsumerMisbehaviour) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{4} +} +func (m *MsgSubmitConsumerMisbehaviour) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSubmitConsumerMisbehaviour) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSubmitConsumerMisbehaviour.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSubmitConsumerMisbehaviour) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSubmitConsumerMisbehaviour.Merge(m, src) +} +func (m *MsgSubmitConsumerMisbehaviour) XXX_Size() int { + return m.Size() +} +func (m *MsgSubmitConsumerMisbehaviour) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSubmitConsumerMisbehaviour.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSubmitConsumerMisbehaviour proto.InternalMessageInfo + +type MsgSubmitConsumerMisbehaviourResponse struct { +} + +func (m *MsgSubmitConsumerMisbehaviourResponse) Reset() { *m = MsgSubmitConsumerMisbehaviourResponse{} } +func (m *MsgSubmitConsumerMisbehaviourResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSubmitConsumerMisbehaviourResponse) ProtoMessage() {} +func (*MsgSubmitConsumerMisbehaviourResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{5} +} +func (m *MsgSubmitConsumerMisbehaviourResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSubmitConsumerMisbehaviourResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSubmitConsumerMisbehaviourResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSubmitConsumerMisbehaviourResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSubmitConsumerMisbehaviourResponse.Merge(m, src) +} +func (m *MsgSubmitConsumerMisbehaviourResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSubmitConsumerMisbehaviourResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSubmitConsumerMisbehaviourResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSubmitConsumerMisbehaviourResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgAssignConsumerKey)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKey") proto.RegisterType((*MsgAssignConsumerKeyResponse)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKeyResponse") proto.RegisterType((*MsgRegisterConsumerRewardDenom)(nil), "interchain_security.ccv.provider.v1.MsgRegisterConsumerRewardDenom") proto.RegisterType((*MsgRegisterConsumerRewardDenomResponse)(nil), "interchain_security.ccv.provider.v1.MsgRegisterConsumerRewardDenomResponse") + proto.RegisterType((*MsgSubmitConsumerMisbehaviour)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviour") + proto.RegisterType((*MsgSubmitConsumerMisbehaviourResponse)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviourResponse") } func init() { @@ -203,36 +285,43 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 453 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x3d, 0x6b, 0x14, 0x41, - 0x18, 0xc7, 0x77, 0x13, 0xd4, 0x64, 0x8c, 0x82, 0xc3, 0x15, 0x97, 0xf3, 0xd8, 0xd3, 0x15, 0x24, - 0x85, 0xee, 0x90, 0x58, 0x88, 0x01, 0x8b, 0x4b, 0x6c, 0x24, 0x5c, 0xb3, 0x8d, 0x60, 0xe1, 0xb1, - 0x37, 0x33, 0x4e, 0x06, 0xb3, 0xf3, 0x2c, 0xf3, 0xcc, 0xad, 0xd9, 0x6f, 0x60, 0xa9, 0x95, 0x6d, - 0xbe, 0x81, 0x5f, 0x43, 0xb0, 0x49, 0x69, 0x25, 0x72, 0xd7, 0x58, 0xfb, 0x09, 0x64, 0xdf, 0x3c, - 0xc5, 0xe3, 0x08, 0x92, 0xee, 0x79, 0xdb, 0xff, 0xff, 0xb7, 0x33, 0xf3, 0x90, 0x07, 0xda, 0x38, - 0x69, 0xf9, 0x71, 0xa2, 0xcd, 0x18, 0x25, 0x9f, 0x5a, 0xed, 0x0a, 0xc6, 0x79, 0xce, 0x32, 0x0b, - 0xb9, 0x16, 0xd2, 0xb2, 0x7c, 0x97, 0xb9, 0xd3, 0x28, 0xb3, 0xe0, 0x80, 0xde, 0x5b, 0x32, 0x1d, - 0x71, 0x9e, 0x47, 0xed, 0x74, 0x94, 0xef, 0xf6, 0xfa, 0x0a, 0x40, 0x9d, 0x48, 0x96, 0x64, 0x9a, - 0x25, 0xc6, 0x80, 0x4b, 0x9c, 0x06, 0x83, 0xb5, 0x44, 0xaf, 0xa3, 0x40, 0x41, 0x15, 0xb2, 0x32, - 0x6a, 0xaa, 0xdb, 0x1c, 0x30, 0x05, 0x1c, 0xd7, 0x8d, 0x3a, 0x69, 0x5b, 0x8d, 0x5c, 0x95, 0x4d, - 0xa6, 0xaf, 0x59, 0x62, 0x8a, 0xba, 0x15, 0x7e, 0xf4, 0x49, 0x67, 0x84, 0x6a, 0x88, 0xa8, 0x95, - 0x39, 0x04, 0x83, 0xd3, 0x54, 0xda, 0x23, 0x59, 0xd0, 0x6d, 0xb2, 0x51, 0x43, 0x6a, 0xd1, 0xf5, - 0xef, 0xf8, 0x3b, 0x9b, 0xf1, 0xb5, 0x2a, 0x7f, 0x2e, 0xe8, 0x63, 0x72, 0xa3, 0x85, 0x1d, 0x27, - 0x42, 0xd8, 0xee, 0x5a, 0xd9, 0x3f, 0xa0, 0x3f, 0xbf, 0x0d, 0x6e, 0x16, 0x49, 0x7a, 0xb2, 0x1f, - 0x96, 0x55, 0x89, 0x18, 0xc6, 0x5b, 0xed, 0xe0, 0x50, 0x08, 0x4b, 0xef, 0x92, 0x2d, 0xde, 0x58, - 0x8c, 0xdf, 0xc8, 0xa2, 0xbb, 0x5e, 0xe9, 0x5e, 0xe7, 0x0b, 0xdb, 0xfd, 0x8d, 0x77, 0x67, 0x03, - 0xef, 0xc7, 0xd9, 0xc0, 0x0b, 0x03, 0xd2, 0x5f, 0x06, 0x16, 0x4b, 0xcc, 0xc0, 0xa0, 0x0c, 0x5f, - 0x91, 0x60, 0x84, 0x2a, 0x96, 0x4a, 0xa3, 0x93, 0xb6, 0x9d, 0x88, 0xe5, 0xdb, 0xc4, 0x8a, 0x67, - 0xd2, 0x40, 0x4a, 0x3b, 0xe4, 0x8a, 0x28, 0x83, 0x86, 0xbf, 0x4e, 0x68, 0x9f, 0x6c, 0x0a, 0x99, - 0x01, 0x6a, 0x07, 0x0d, 0x79, 0xbc, 0x28, 0xfc, 0xe1, 0xbf, 0x43, 0xee, 0xaf, 0xd6, 0x6f, 0x49, - 0xf6, 0xbe, 0xac, 0x91, 0xf5, 0x11, 0x2a, 0xfa, 0xc1, 0x27, 0xb7, 0xfe, 0x3d, 0xc8, 0x27, 0xd1, - 0x05, 0x6e, 0x3c, 0x5a, 0xf6, 0xab, 0xbd, 0xe1, 0x7f, 0x7f, 0xda, 0xb2, 0xd1, 0x4f, 0x3e, 0xb9, - 0xbd, 0xea, 0x8c, 0x0e, 0x2f, 0x6a, 0xb1, 0x42, 0xa4, 0x77, 0x74, 0x09, 0x22, 0x2d, 0xf1, 0xc1, - 0x8b, 0xcf, 0xb3, 0xc0, 0x3f, 0x9f, 0x05, 0xfe, 0xf7, 0x59, 0xe0, 0xbf, 0x9f, 0x07, 0xde, 0xf9, - 0x3c, 0xf0, 0xbe, 0xce, 0x03, 0xef, 0xe5, 0x53, 0xa5, 0xdd, 0xf1, 0x74, 0x12, 0x71, 0x48, 0x9b, - 0xf7, 0xcd, 0x16, 0xbe, 0x0f, 0x7f, 0xaf, 0x5e, 0xbe, 0xc7, 0x4e, 0xff, 0xde, 0x3f, 0x57, 0x64, - 0x12, 0x27, 0x57, 0xab, 0x17, 0xff, 0xe8, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7a, 0x53, 0xb5, - 0xb8, 0xb0, 0x03, 0x00, 0x00, + // 568 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x3f, 0x6f, 0x13, 0x4f, + 0x10, 0xf5, 0xfd, 0xa2, 0x1f, 0x24, 0x9b, 0x80, 0xc4, 0xc9, 0x85, 0x73, 0x98, 0x33, 0x18, 0x01, + 0x29, 0xc2, 0xae, 0x6c, 0x0a, 0x44, 0x24, 0x0a, 0x3b, 0x34, 0x10, 0x59, 0x42, 0x47, 0x81, 0x44, + 0x81, 0x75, 0xb7, 0xbb, 0xac, 0x57, 0xf8, 0x76, 0x4f, 0xbb, 0x7b, 0x47, 0xee, 0x1b, 0x50, 0x42, + 0x85, 0xe8, 0xf2, 0x01, 0x90, 0xf8, 0x1a, 0x94, 0x29, 0xa9, 0x10, 0xb2, 0x1b, 0x6a, 0x4a, 0x2a, + 0xe4, 0xfb, 0x63, 0x5f, 0x84, 0xb1, 0x2c, 0xa0, 0xdb, 0x99, 0x79, 0xfb, 0xde, 0x1b, 0xcd, 0x68, + 0xc0, 0x3e, 0x17, 0x86, 0x2a, 0x3c, 0xf2, 0xb9, 0x18, 0x6a, 0x8a, 0x63, 0xc5, 0x4d, 0x8a, 0x30, + 0x4e, 0x50, 0xa4, 0x64, 0xc2, 0x09, 0x55, 0x28, 0xe9, 0x20, 0x73, 0x0c, 0x23, 0x25, 0x8d, 0xb4, + 0xaf, 0x2f, 0x41, 0x43, 0x8c, 0x13, 0x58, 0xa2, 0x61, 0xd2, 0x71, 0x9a, 0x4c, 0x4a, 0x36, 0xa6, + 0xc8, 0x8f, 0x38, 0xf2, 0x85, 0x90, 0xc6, 0x37, 0x5c, 0x0a, 0x9d, 0x53, 0x38, 0x75, 0x26, 0x99, + 0xcc, 0x9e, 0x68, 0xf6, 0x2a, 0xb2, 0xbb, 0x58, 0xea, 0x50, 0xea, 0x61, 0x5e, 0xc8, 0x83, 0xb2, + 0x54, 0xd0, 0x65, 0x51, 0x10, 0xbf, 0x40, 0xbe, 0x48, 0x8b, 0x12, 0xe2, 0x01, 0x46, 0x63, 0xce, + 0x46, 0x06, 0x8f, 0x39, 0x15, 0x46, 0x23, 0x43, 0x05, 0xa1, 0x2a, 0xe4, 0xc2, 0x64, 0xbe, 0xe7, + 0x51, 0xfe, 0xa1, 0xfd, 0xce, 0x02, 0xf5, 0x81, 0x66, 0x3d, 0xad, 0x39, 0x13, 0x87, 0x52, 0xe8, + 0x38, 0xa4, 0xea, 0x88, 0xa6, 0xf6, 0x2e, 0xd8, 0xcc, 0xbb, 0xe2, 0xa4, 0x61, 0x5d, 0xb5, 0xf6, + 0xb6, 0xbc, 0xf3, 0x59, 0xfc, 0x90, 0xd8, 0x77, 0xc1, 0x85, 0xb2, 0xbb, 0xa1, 0x4f, 0x88, 0x6a, + 0xfc, 0x37, 0xab, 0xf7, 0xed, 0xef, 0x5f, 0x5a, 0x17, 0x53, 0x3f, 0x1c, 0x1f, 0xb4, 0x67, 0x59, + 0xaa, 0x75, 0xdb, 0xdb, 0x29, 0x81, 0x3d, 0x42, 0x94, 0x7d, 0x0d, 0xec, 0xe0, 0x42, 0x62, 0xf8, + 0x92, 0xa6, 0x8d, 0x8d, 0x8c, 0x77, 0x1b, 0x2f, 0x64, 0x0f, 0x36, 0x5f, 0x9f, 0xb4, 0x6a, 0xdf, + 0x4e, 0x5a, 0xb5, 0xb6, 0x0b, 0x9a, 0xcb, 0x8c, 0x79, 0x54, 0x47, 0x52, 0x68, 0xda, 0x7e, 0x0e, + 0xdc, 0x81, 0x66, 0x1e, 0x65, 0x5c, 0x1b, 0xaa, 0x4a, 0x84, 0x47, 0x5f, 0xf9, 0x8a, 0x3c, 0xa0, + 0x42, 0x86, 0x76, 0x1d, 0xfc, 0x4f, 0x66, 0x8f, 0xc2, 0x7f, 0x1e, 0xd8, 0x4d, 0xb0, 0x45, 0x68, + 0x24, 0x35, 0x37, 0xb2, 0x70, 0xee, 0x2d, 0x12, 0x15, 0xfd, 0x3d, 0x70, 0x73, 0x35, 0xff, 0xdc, + 0xc9, 0x7b, 0x0b, 0x5c, 0x19, 0x68, 0xf6, 0x24, 0x0e, 0x42, 0x6e, 0x4a, 0xe0, 0x80, 0xeb, 0x80, + 0x8e, 0xfc, 0x84, 0xcb, 0x58, 0xcd, 0x34, 0x75, 0x56, 0x35, 0x54, 0x15, 0x6e, 0x16, 0x09, 0xfb, + 0x31, 0xd8, 0x09, 0x2b, 0xe8, 0xcc, 0xd4, 0x76, 0x77, 0x1f, 0xf2, 0x00, 0xc3, 0xea, 0x2c, 0x61, + 0x65, 0x7a, 0x49, 0x07, 0x56, 0x15, 0xbc, 0x33, 0x0c, 0x95, 0x2e, 0x6e, 0x81, 0x1b, 0x2b, 0xad, + 0x95, 0x4d, 0x74, 0x7f, 0x6c, 0x80, 0x8d, 0x81, 0x66, 0xf6, 0x5b, 0x0b, 0x5c, 0xfa, 0x75, 0x1b, + 0xee, 0xc1, 0x35, 0xf6, 0x1c, 0x2e, 0x9b, 0x97, 0xd3, 0xfb, 0xe3, 0xaf, 0xa5, 0x37, 0xfb, 0xa3, + 0x05, 0x2e, 0xaf, 0x1a, 0xf4, 0xe1, 0xba, 0x12, 0x2b, 0x48, 0x9c, 0xa3, 0x7f, 0x40, 0x32, 0x77, + 0xfc, 0xc1, 0x02, 0xce, 0x8a, 0x7d, 0xe8, 0xaf, 0xab, 0xf5, 0x7b, 0x0e, 0xe7, 0xd1, 0xdf, 0x73, + 0x94, 0x76, 0xfb, 0x4f, 0x3f, 0x4d, 0x5c, 0xeb, 0x74, 0xe2, 0x5a, 0x5f, 0x27, 0xae, 0xf5, 0x66, + 0xea, 0xd6, 0x4e, 0xa7, 0x6e, 0xed, 0xf3, 0xd4, 0xad, 0x3d, 0xbb, 0xcf, 0xb8, 0x19, 0xc5, 0x01, + 0xc4, 0x32, 0x2c, 0x8e, 0x10, 0x5a, 0xc8, 0xde, 0x9e, 0xdf, 0xc7, 0xa4, 0x8b, 0x8e, 0xcf, 0x1e, + 0x49, 0x93, 0x46, 0x54, 0x07, 0xe7, 0xb2, 0x2b, 0x73, 0xe7, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xe3, 0x4f, 0x57, 0x26, 0x55, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -249,6 +338,7 @@ const _ = grpc.SupportPackageIsVersion4 type MsgClient interface { AssignConsumerKey(ctx context.Context, in *MsgAssignConsumerKey, opts ...grpc.CallOption) (*MsgAssignConsumerKeyResponse, error) RegisterConsumerRewardDenom(ctx context.Context, in *MsgRegisterConsumerRewardDenom, opts ...grpc.CallOption) (*MsgRegisterConsumerRewardDenomResponse, error) + SubmitConsumerMisbehaviour(ctx context.Context, in *MsgSubmitConsumerMisbehaviour, opts ...grpc.CallOption) (*MsgSubmitConsumerMisbehaviourResponse, error) } type msgClient struct { @@ -277,10 +367,20 @@ func (c *msgClient) RegisterConsumerRewardDenom(ctx context.Context, in *MsgRegi return out, nil } +func (c *msgClient) SubmitConsumerMisbehaviour(ctx context.Context, in *MsgSubmitConsumerMisbehaviour, opts ...grpc.CallOption) (*MsgSubmitConsumerMisbehaviourResponse, error) { + out := new(MsgSubmitConsumerMisbehaviourResponse) + err := c.cc.Invoke(ctx, "/interchain_security.ccv.provider.v1.Msg/SubmitConsumerMisbehaviour", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { AssignConsumerKey(context.Context, *MsgAssignConsumerKey) (*MsgAssignConsumerKeyResponse, error) RegisterConsumerRewardDenom(context.Context, *MsgRegisterConsumerRewardDenom) (*MsgRegisterConsumerRewardDenomResponse, error) + SubmitConsumerMisbehaviour(context.Context, *MsgSubmitConsumerMisbehaviour) (*MsgSubmitConsumerMisbehaviourResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -293,6 +393,9 @@ func (*UnimplementedMsgServer) AssignConsumerKey(ctx context.Context, req *MsgAs func (*UnimplementedMsgServer) RegisterConsumerRewardDenom(ctx context.Context, req *MsgRegisterConsumerRewardDenom) (*MsgRegisterConsumerRewardDenomResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RegisterConsumerRewardDenom not implemented") } +func (*UnimplementedMsgServer) SubmitConsumerMisbehaviour(ctx context.Context, req *MsgSubmitConsumerMisbehaviour) (*MsgSubmitConsumerMisbehaviourResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitConsumerMisbehaviour not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -334,6 +437,24 @@ func _Msg_RegisterConsumerRewardDenom_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } +func _Msg_SubmitConsumerMisbehaviour_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSubmitConsumerMisbehaviour) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SubmitConsumerMisbehaviour(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/interchain_security.ccv.provider.v1.Msg/SubmitConsumerMisbehaviour", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SubmitConsumerMisbehaviour(ctx, req.(*MsgSubmitConsumerMisbehaviour)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "interchain_security.ccv.provider.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -346,6 +467,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "RegisterConsumerRewardDenom", Handler: _Msg_RegisterConsumerRewardDenom_Handler, }, + { + MethodName: "SubmitConsumerMisbehaviour", + Handler: _Msg_SubmitConsumerMisbehaviour_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "interchain_security/ccv/provider/v1/tx.proto", @@ -478,6 +603,71 @@ func (m *MsgRegisterConsumerRewardDenomResponse) MarshalToSizedBuffer(dAtA []byt return len(dAtA) - i, nil } +func (m *MsgSubmitConsumerMisbehaviour) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSubmitConsumerMisbehaviour) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSubmitConsumerMisbehaviour) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Misbehaviour != nil { + { + size, err := m.Misbehaviour.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Submitter) > 0 { + i -= len(m.Submitter) + copy(dAtA[i:], m.Submitter) + i = encodeVarintTx(dAtA, i, uint64(len(m.Submitter))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgSubmitConsumerMisbehaviourResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSubmitConsumerMisbehaviourResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSubmitConsumerMisbehaviourResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -545,6 +735,32 @@ func (m *MsgRegisterConsumerRewardDenomResponse) Size() (n int) { return n } +func (m *MsgSubmitConsumerMisbehaviour) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Submitter) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Misbehaviour != nil { + l = m.Misbehaviour.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgSubmitConsumerMisbehaviourResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -911,6 +1127,174 @@ func (m *MsgRegisterConsumerRewardDenomResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgSubmitConsumerMisbehaviour) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSubmitConsumerMisbehaviour: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSubmitConsumerMisbehaviour: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Submitter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Submitter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Misbehaviour", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Misbehaviour == nil { + m.Misbehaviour = &types.Misbehaviour{} + } + if err := m.Misbehaviour.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSubmitConsumerMisbehaviourResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSubmitConsumerMisbehaviourResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSubmitConsumerMisbehaviourResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/ccv/types/events.go b/x/ccv/types/events.go index ba71e063f3..df91333fa0 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -9,11 +9,11 @@ const ( EventTypeConsumerClientCreated = "consumer_client_created" EventTypeAssignConsumerKey = "assign_consumer_key" EventTypeRegisterConsumerRewardDenom = "register_consumer_reward_denom" - - EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash" - EventTypeFeeDistribution = "fee_distribution" - EventTypeConsumerSlashRequest = "consumer_slash_request" - EventTypeVSCMatured = "vsc_matured" + EventTypeSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" + EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash" + EventTypeFeeDistribution = "fee_distribution" + EventTypeConsumerSlashRequest = "consumer_slash_request" + EventTypeVSCMatured = "vsc_matured" AttributeKeyAckSuccess = "success" AttributeKeyAck = "acknowledgement" @@ -33,6 +33,11 @@ const ( AttributeUnbondingPeriod = "unbonding_period" AttributeProviderValidatorAddress = "provider_validator_address" AttributeConsumerConsensusPubKey = "consumer_consensus_pub_key" + AttributeSubmitterAddress = "submitter_address" + AttributeConsumerMisbehaviour = "consumer_misbehaviour" + AttributeMisbehaviourClientId = "misbehaviour_client_id" + AttributeMisbehaviourHeight1 = "misbehaviour_height_1" + AttributeMisbehaviourHeight2 = "misbehaviour_height_2" AttributeDistributionCurrentHeight = "current_distribution_height" AttributeDistributionNextHeight = "next_distribution_height" diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 7f18324c8d..b4dea9b670 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -86,6 +86,10 @@ type ClientKeeper interface { GetClientState(ctx sdk.Context, clientID string) (ibcexported.ClientState, bool) GetLatestClientConsensusState(ctx sdk.Context, clientID string) (ibcexported.ConsensusState, bool) GetSelfConsensusState(ctx sdk.Context, height ibcexported.Height) (ibcexported.ConsensusState, error) + ClientStore(ctx sdk.Context, clientID string) sdk.KVStore + SetClientState(ctx sdk.Context, clientID string, clientState ibcexported.ClientState) + GetClientConsensusState(ctx sdk.Context, clientID string, height ibcexported.Height) (ibcexported.ConsensusState, bool) + CheckMisbehaviourAndUpdateState(ctx sdk.Context, misbehaviour ibcexported.Misbehaviour) error } // DistributionKeeper defines the expected interface of the distribution keeper From 21e3d839ce3fc7141b6c808ab40b46899d5f9123 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 23 Aug 2023 16:38:35 +0200 Subject: [PATCH 02/40] feat: improve ICS misbehaviour E2E testing coverage (#1225) * update e2e tests * update the chain halt assertion --- tests/e2e/actions_consumer_misbehaviour.go | 24 ++++++++++++++++++++-- tests/e2e/main.go | 2 ++ tests/e2e/steps_consumer_misbehaviour.go | 14 +++++++------ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/tests/e2e/actions_consumer_misbehaviour.go b/tests/e2e/actions_consumer_misbehaviour.go index 2b01c2818e..84eb93152c 100644 --- a/tests/e2e/actions_consumer_misbehaviour.go +++ b/tests/e2e/actions_consumer_misbehaviour.go @@ -29,7 +29,7 @@ func (tr TestRun) forkConsumerChain(action forkConsumerChainAction, verbose bool ) if verbose { - fmt.Println("forkConsumerChain - reconfigure node cmd:", configureNodeCmd.String()) + log.Println("forkConsumerChain - reconfigure node cmd:", configureNodeCmd.String()) } cmdReader, err := configureNodeCmd.StdoutPipe() @@ -47,7 +47,7 @@ func (tr TestRun) forkConsumerChain(action forkConsumerChainAction, verbose bool for scanner.Scan() { out := scanner.Text() if verbose { - fmt.Println("fork consumer validator : " + out) + log.Println("fork consumer validator : " + out) } if out == done { break @@ -90,3 +90,23 @@ func (tr TestRun) updateLightClient( tr.waitBlocks(action.hostChain, 5, 30*time.Second) } + +type assertChainIsHaltedAction struct { + chain chainID +} + +// assertChainIsHalted verifies that the chain isn't producing blocks +// by checking that the block height is still the same after 20 seconds +func (tr TestRun) assertChainIsHalted( + action assertChainIsHaltedAction, + verbose bool, +) { + blockHeight := tr.getBlockHeight(action.chain) + time.Sleep(20 * time.Second) + if blockHeight != tr.getBlockHeight(action.chain) { + panic(fmt.Sprintf("chain %v isn't expected to produce blocks", action.chain)) + } + if verbose { + log.Printf("assertChainIsHalted - chain %v was successfully halted\n", action.chain) + } +} diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 4c7b6722cd..406a015e63 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -178,6 +178,8 @@ func (tr *TestRun) runStep(step Step, verbose bool) { tr.forkConsumerChain(action, verbose) case updateLightClientAction: tr.updateLightClient(action, verbose) + case assertChainIsHaltedAction: + tr.assertChainIsHalted(action, verbose) default: log.Fatalf("unknown action in testRun %s: %#v", tr.name, action) } diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index 7d7fda94b1..6401b5f638 100644 --- a/tests/e2e/steps_consumer_misbehaviour.go +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -241,13 +241,15 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { }, }, }, - chainID(consumerName): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 511, - validatorID("bob"): 20, - }, - }, }, }, + // we expect the consumer chain to be halted since the last VSC packet should + // have updated the alice validator power to 0. + { + action: assertChainIsHaltedAction{ + chain: chainID("consu"), + }, + state: State{}, + }, } } From 292ad7539b889ba3ac98165c5da6124da601ae30 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 23 Aug 2023 18:27:47 +0200 Subject: [PATCH 03/40] refactor: address comments of ICS Misbehaviour PRs #826 and #1148 (#1223) * remove interface * improve comment * update godoc * address last comments --- x/ccv/provider/keeper/misbehaviour.go | 12 +++--------- x/ccv/provider/types/codec.go | 5 ----- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 4d8f517e95..0438df36e3 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -1,8 +1,6 @@ package keeper import ( - "sort" - "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -109,7 +107,6 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. } } - sort.Sort(tmtypes.ValidatorsByVotingPower(validators)) return validators, nil } @@ -134,10 +131,6 @@ func headerToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { // CheckMisbehaviour checks that headers in the given misbehaviour forms // a valid light client attack and that the corresponding light client isn't expired func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { - if err := misbehaviour.ValidateBasic(); err != nil { - return err - } - clientState, found := k.clientKeeper.GetClientState(ctx, misbehaviour.GetClientID()) if !found { return sdkerrors.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot check misbehaviour for client with ID %s", misbehaviour.GetClientID()) @@ -147,14 +140,15 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe // Check that the headers are at the same height to ensure that // the misbehaviour is for a light client attack and not a time violation, - // see https://github.com/cosmos/ibc-go/blob/8f53c21361f9d65448a850c2eafcf3ab3c384a61/modules/light-clients/07-tendermint/types/misbehaviour_handle.go#L56 + // https://github.com/cosmos/ibc-go/blob/v4.2.0/modules/light-clients/07-tendermint/types/misbehaviour_handle.go#L53-L58 if !misbehaviour.Header1.GetHeight().EQ(misbehaviour.Header2.GetHeight()) { return sdkerrors.Wrap(ibcclienttypes.ErrInvalidMisbehaviour, "headers are not at same height") } // CheckMisbehaviourAndUpdateState verifies the misbehaviour against the trusted consensus states // but does NOT update the light client state. - // Note CheckMisbehaviourAndUpdateState returns an error if the trusted consensus states are expired + // Note that the CometBFT CheckMisbehaviourAndUpdateState method returns an error if the trusted consensus states are expired, + // see https://github.com/cosmos/ibc-go/blob/v4.2.0/modules/light-clients/07-tendermint/types/misbehaviour_handle.go#L120 _, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbehaviour) if err != nil { return err diff --git a/x/ccv/provider/types/codec.go b/x/ccv/provider/types/codec.go index 2f28b398f5..0ef3c2d296 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -6,7 +6,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - "github.com/cosmos/ibc-go/v4/modules/core/exported" ) // RegisterLegacyAminoCodec registers the necessary x/ibc transfer interfaces and concrete types @@ -41,10 +40,6 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { (*sdk.Msg)(nil), &MsgSubmitConsumerMisbehaviour{}, ) - registry.RegisterInterface( - "ibc.core.client.v1.Misbehaviour", - (*exported.Misbehaviour)(nil), - ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } From f168b9b4a5d15f3641d9cb09182b923f7688aead Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 28 Aug 2023 10:17:02 +0200 Subject: [PATCH 04/40] feat: add handler for consumer double voting (#1232) * create new endpoint for consumer double voting * add first draft handling logic * first iteration of double voting * draft first mem test * error handling * refactor * add unit test of double voting verification * remove evidence age checks * document * doc * protogen * reformat double voting handling * logger nit * nits * check evidence age duration * move verify double voting evidence to ut * fix nit * nits * fix e2e tests * improve double vote testing coverage * remove TODO * lint * add UT for JailAndTombstoneValidator * nits * nits * remove tombstoning and evidence age check * lint * typo * improve godoc --- .../ccv/provider/v1/tx.proto | 19 + tests/integration/double_vote.go | 123 +++++ tests/integration/misbehaviour.go | 3 +- testutil/crypto/evidence.go | 56 ++ testutil/integration/debug_test.go | 10 +- .../proto/tendermint/types/evidence.proto | 38 ++ x/ccv/provider/client/cli/tx.go | 46 ++ x/ccv/provider/handler.go | 3 + x/ccv/provider/keeper/double_vote.go | 109 ++++ x/ccv/provider/keeper/double_vote_test.go | 284 ++++++++++ x/ccv/provider/keeper/misbehaviour.go | 35 +- x/ccv/provider/keeper/msg_server.go | 25 + x/ccv/provider/keeper/punish_validator.go | 38 ++ .../provider/keeper/punish_validator_test.go | 132 +++++ x/ccv/provider/types/msg.go | 47 ++ x/ccv/provider/types/tx.pb.go | 512 ++++++++++++++++-- x/ccv/types/errors.go | 1 + x/ccv/types/events.go | 1 + 18 files changed, 1417 insertions(+), 65 deletions(-) create mode 100644 tests/integration/double_vote.go create mode 100644 testutil/crypto/evidence.go create mode 100644 third_party/proto/tendermint/types/evidence.proto create mode 100644 x/ccv/provider/keeper/double_vote.go create mode 100644 x/ccv/provider/keeper/double_vote_test.go create mode 100644 x/ccv/provider/keeper/punish_validator.go create mode 100644 x/ccv/provider/keeper/punish_validator_test.go diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 61be3064ea..64e4c88b70 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -8,12 +8,15 @@ import "gogoproto/gogo.proto"; import "cosmos_proto/cosmos.proto"; import "google/protobuf/any.proto"; import "ibc/lightclients/tendermint/v1/tendermint.proto"; +import "tendermint/types/evidence.proto"; + // Msg defines the Msg service. service Msg { rpc AssignConsumerKey(MsgAssignConsumerKey) returns (MsgAssignConsumerKeyResponse); rpc RegisterConsumerRewardDenom(MsgRegisterConsumerRewardDenom) returns (MsgRegisterConsumerRewardDenomResponse); rpc SubmitConsumerMisbehaviour(MsgSubmitConsumerMisbehaviour) returns (MsgSubmitConsumerMisbehaviourResponse); + rpc SubmitConsumerDoubleVoting(MsgSubmitConsumerDoubleVoting) returns (MsgSubmitConsumerDoubleVotingResponse); } message MsgAssignConsumerKey { @@ -59,3 +62,19 @@ message MsgSubmitConsumerMisbehaviour { } message MsgSubmitConsumerMisbehaviourResponse {} + + +// MsgSubmitConsumerDoubleVoting defines a message that reports an equivocation +// observed on a consumer chain +message MsgSubmitConsumerDoubleVoting { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + string submitter = 1; + // The equivocation of the consumer chain wrapping + // an evidence of a validator that signed two conflicting votes + tendermint.types.DuplicateVoteEvidence duplicate_vote_evidence = 2; + // The light client header of the infraction block + ibc.lightclients.tendermint.v1.Header infraction_block_header = 3; +} + +message MsgSubmitConsumerDoubleVotingResponse {} \ No newline at end of file diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go new file mode 100644 index 0000000000..04d976ed61 --- /dev/null +++ b/tests/integration/double_vote.go @@ -0,0 +1,123 @@ +package integration + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + testutil "github.com/cosmos/interchain-security/v2/testutil/crypto" + "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +// TestHandleConsumerDoubleVoting verifies that handling a double voting evidence +// of a consumer chain results in the expected jailing of the malicious validator +func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { + s.SetupCCVChannel(s.path) + // required to have the consumer client revision height greater than 0 + s.SendEmptyVSCPacket() + + // create signing info for all validators + for _, v := range s.providerChain.Vals.Validators { + s.setDefaultValSigningInfo(*v) + } + + valSet, err := tmtypes.ValidatorSetFromProto(s.consumerChain.LastHeader.ValidatorSet) + s.Require().NoError(err) + + val := valSet.Validators[0] + signer := s.consumerChain.Signers[val.Address.String()] + + blockID1 := testutil.MakeBlockID([]byte("blockhash"), 1000, []byte("partshash")) + blockID2 := testutil.MakeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) + + // Note that votes are signed along with the chain ID + // see VoteSignBytes in https://github.com/cometbft/cometbft/blob/main/types/vote.go#L139 + vote1 := testutil.MakeAndSignVote( + blockID1, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + valSet, + signer, + s.consumerChain.ChainID, + ) + + badVote := testutil.MakeAndSignVote( + blockID2, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + valSet, + signer, + s.consumerChain.ChainID, + ) + + testCases := []struct { + name string + ev *tmtypes.DuplicateVoteEvidence + chainID string + expPass bool + }{ + { + "invalid consumer chain id - shouldn't pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: vote1, + VoteB: badVote, + ValidatorPower: val.VotingPower, + TotalVotingPower: val.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + "chainID", + false, + }, + { + // create an invalid evidence containing two identical votes + "invalid double voting evidence - shouldn't pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: vote1, + VoteB: vote1, + ValidatorPower: val.VotingPower, + TotalVotingPower: val.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + false, + }, + { + // In order to create an evidence for a consumer chain, + // we create two votes that only differ by their Block IDs and + // signed them using the same validator private key and chain ID + // of the consumer chain + "valid double voting evidence - should pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: vote1, + VoteB: badVote, + ValidatorPower: val.VotingPower, + TotalVotingPower: val.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + true, + }, + } + + consuAddr := types.NewConsumerConsAddress(sdk.ConsAddress(val.Address.Bytes())) + provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) + + for _, tc := range testCases { + s.Run(tc.name, func() { + err = s.providerApp.GetProviderKeeper().HandleConsumerDoubleVoting( + s.providerCtx(), + tc.ev, + tc.chainID, + ) + if tc.expPass { + s.Require().NoError(err) + + // verifies that the jailing has occurred + s.Require().True(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(s.providerCtx(), provAddr.ToSdkConsAddr())) + } else { + s.Require().Error(err) + + // verifies that no jailing and has occurred + s.Require().False(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(s.providerCtx(), provAddr.ToSdkConsAddr())) + } + }) + } +} diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index c3b590d5c1..f7cebc84c0 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -11,7 +11,7 @@ import ( ) // TestHandleConsumerMisbehaviour tests that handling a valid misbehaviour, -// with conflicting headers forming an equivocation, results in the jailing and tombstoning of the validators +// with conflicting headers forming an equivocation, results in the jailing of the validators func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 @@ -63,7 +63,6 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { val, ok := s.providerApp.GetTestStakingKeeper().GetValidatorByConsAddr(s.providerCtx(), provAddr.Address) s.Require().True(ok) s.Require().True(val.Jailed) - s.Require().True(s.providerApp.GetTestSlashingKeeper().IsTombstoned(s.providerCtx(), provAddr.Address)) } } diff --git a/testutil/crypto/evidence.go b/testutil/crypto/evidence.go new file mode 100644 index 0000000000..050c17331a --- /dev/null +++ b/testutil/crypto/evidence.go @@ -0,0 +1,56 @@ +package crypto + +import ( + "time" + + "github.com/tendermint/tendermint/crypto/tmhash" + tmtypes "github.com/tendermint/tendermint/types" +) + +// utility function duplicated from CometBFT +// see https://github.com/cometbft/cometbft/blob/main/evidence/verify_test.go#L554 +func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) tmtypes.BlockID { + var ( + h = make([]byte, tmhash.Size) + psH = make([]byte, tmhash.Size) + ) + copy(h, hash) + copy(psH, partSetHash) + return tmtypes.BlockID{ + Hash: h, + PartSetHeader: tmtypes.PartSetHeader{ + Total: partSetSize, + Hash: psH, + }, + } +} + +func MakeAndSignVote( + blockID tmtypes.BlockID, + blockHeight int64, + blockTime time.Time, + valSet *tmtypes.ValidatorSet, + signer tmtypes.PrivValidator, + chainID string, +) *tmtypes.Vote { + vote, err := tmtypes.MakeVote( + blockHeight, + blockID, + valSet, + signer, + chainID, + blockTime, + ) + if err != nil { + panic(err) + } + + v := vote.ToProto() + err = signer.SignVote(chainID, v) + if err != nil { + panic(err) + } + + vote.Signature = v.Signature + return vote +} diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index 2d9421300b..fcb19d5255 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -258,7 +258,7 @@ func TestRecycleTransferChannel(t *testing.T) { } // -// Misbehaviour test +// Misbehaviour tests // func TestHandleConsumerMisbehaviour(t *testing.T) { @@ -272,3 +272,11 @@ func TestGetByzantineValidators(t *testing.T) { func TestCheckMisbehaviour(t *testing.T) { runCCVTestByName(t, "TestCheckMisbehaviour") } + +// +// Equivocation test +// + +func TestHandleConsumerDoubleVoting(t *testing.T) { + runCCVTestByName(t, "TestHandleConsumerDoubleVoting") +} diff --git a/third_party/proto/tendermint/types/evidence.proto b/third_party/proto/tendermint/types/evidence.proto new file mode 100644 index 0000000000..451b8dca3c --- /dev/null +++ b/third_party/proto/tendermint/types/evidence.proto @@ -0,0 +1,38 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "tendermint/types/types.proto"; +import "tendermint/types/validator.proto"; + +message Evidence { + oneof sum { + DuplicateVoteEvidence duplicate_vote_evidence = 1; + LightClientAttackEvidence light_client_attack_evidence = 2; + } +} + +// DuplicateVoteEvidence contains evidence of a validator signed two conflicting votes. +message DuplicateVoteEvidence { + tendermint.types.Vote vote_a = 1; + tendermint.types.Vote vote_b = 2; + int64 total_voting_power = 3; + int64 validator_power = 4; + google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} + +// LightClientAttackEvidence contains evidence of a set of validators attempting to mislead a light client. +message LightClientAttackEvidence { + tendermint.types.LightBlock conflicting_block = 1; + int64 common_height = 2; + repeated tendermint.types.Validator byzantine_validators = 3; + int64 total_voting_power = 4; + google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} + +message EvidenceList { + repeated Evidence evidence = 1 [(gogoproto.nullable) = false]; +} diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 1cad8b38ce..1d3ccc0011 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/version" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" @@ -29,6 +30,7 @@ func GetTxCmd() *cobra.Command { cmd.AddCommand(NewAssignConsumerKeyCmd()) cmd.AddCommand(NewRegisterConsumerRewardDenomCmd()) cmd.AddCommand(NewSubmitConsumerMisbehaviourCmd()) + cmd.AddCommand(NewSubmitConsumerDoubleVotingCmd()) return cmd } @@ -148,3 +150,47 @@ Examples: return cmd } + +func NewSubmitConsumerDoubleVotingCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "submit consumer-double-voting [evidence] [infraction_header]", + Short: "submit a double voting evidence for a consumer chain", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()). + WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + submitter := clientCtx.GetFromAddress() + var ev *tmproto.DuplicateVoteEvidence + if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(args[1]), &ev); err != nil { + return err + } + + var header ibctmtypes.Header + if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(args[2]), &header); err != nil { + return err + } + + msg, err := types.NewMsgSubmitConsumerDoubleVoting(submitter, ev, nil) + if err != nil { + return err + } + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + _ = cmd.MarkFlagRequired(flags.FlagFrom) + + return cmd +} diff --git a/x/ccv/provider/handler.go b/x/ccv/provider/handler.go index 6fa38b5ddf..8dca550732 100644 --- a/x/ccv/provider/handler.go +++ b/x/ccv/provider/handler.go @@ -24,6 +24,9 @@ func NewHandler(k *keeper.Keeper) sdk.Handler { case *types.MsgSubmitConsumerMisbehaviour: res, err := msgServer.SubmitConsumerMisbehaviour(sdk.WrapSDKContext(ctx), msg) return sdk.WrapServiceResult(ctx, res, err) + case *types.MsgSubmitConsumerDoubleVoting: + res, err := msgServer.SubmitConsumerDoubleVoting(sdk.WrapSDKContext(ctx), msg) + return sdk.WrapServiceResult(ctx, res, err) default: return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) } diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go new file mode 100644 index 0000000000..ee47e233ee --- /dev/null +++ b/x/ccv/provider/keeper/double_vote.go @@ -0,0 +1,109 @@ +package keeper + +import ( + "bytes" + "fmt" + + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v2/x/ccv/types" + tmprotocrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" + tmtypes "github.com/tendermint/tendermint/types" +) + +// HandleConsumerDoubleVoting verifies a double voting evidence for a given a consumer chain and, +// if successful, executes the jailing of the malicious validator. +func (k Keeper) HandleConsumerDoubleVoting(ctx sdk.Context, evidence *tmtypes.DuplicateVoteEvidence, chainID string) error { + // get the validator's consensus address on the provider + providerAddr := k.GetProviderAddrFromConsumerAddr( + ctx, + chainID, + types.NewConsumerConsAddress(sdk.ConsAddress(evidence.VoteA.ValidatorAddress.Bytes())), + ) + + // get the consumer chain public key assigned to the validator + consuPubkey, ok := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) + if !ok { + return fmt.Errorf("cannot find public key for validator %s and consumer chain %s", providerAddr.String(), chainID) + } + + // verifies the double voting evidence using the consumer chain public key + if err := k.VerifyDoubleVotingEvidence(ctx, *evidence, chainID, consuPubkey); err != nil { + return err + } + + // execute the jailing + k.JailValidator(ctx, providerAddr) + + k.Logger(ctx).Info( + "confirmed equivocation", + "byzantine validator address", providerAddr, + ) + + return nil +} + +// VerifyDoubleVotingEvidence verifies a double voting evidence +// for a given chain id and a validator public key +func (k Keeper) VerifyDoubleVotingEvidence( + ctx sdk.Context, + evidence tmtypes.DuplicateVoteEvidence, + chainID string, + pubkey tmprotocrypto.PublicKey, +) error { + // Note that since we're only jailing validators for double voting on a consumer chain, + // the age of the evidence is irrelevant and therefore isn't checked. + + // TODO: check the age of the evidence once we slash + // validators for double voting on a consumer chain + + // H/R/S must be the same + if evidence.VoteA.Height != evidence.VoteB.Height || + evidence.VoteA.Round != evidence.VoteB.Round || + evidence.VoteA.Type != evidence.VoteB.Type { + return sdkerrors.Wrapf( + ccvtypes.ErrInvalidEvidence, + "h/r/s does not match: %d/%d/%v vs %d/%d/%v", + evidence.VoteA.Height, evidence.VoteA.Round, evidence.VoteA.Type, + evidence.VoteB.Height, evidence.VoteB.Round, evidence.VoteB.Type) + } + + // Address must be the same + if !bytes.Equal(evidence.VoteA.ValidatorAddress, evidence.VoteB.ValidatorAddress) { + return sdkerrors.Wrapf( + ccvtypes.ErrInvalidEvidence, + "validator addresses do not match: %X vs %X", + evidence.VoteA.ValidatorAddress, + evidence.VoteB.ValidatorAddress, + ) + } + + // BlockIDs must be different + if evidence.VoteA.BlockID.Equals(evidence.VoteB.BlockID) { + return sdkerrors.Wrapf( + ccvtypes.ErrInvalidEvidence, + "block IDs are the same (%v) - not a real duplicate vote", + evidence.VoteA.BlockID, + ) + } + + pk, err := cryptocodec.FromTmProtoPublicKey(pubkey) + if err != nil { + return err + } + + va := evidence.VoteA.ToProto() + vb := evidence.VoteB.ToProto() + + // signatures must be valid + if !pk.VerifySignature(tmtypes.VoteSignBytes(chainID, va), evidence.VoteA.Signature) { + return fmt.Errorf("verifying VoteA: %w", tmtypes.ErrVoteInvalidSignature) + } + if !pk.VerifySignature(tmtypes.VoteSignBytes(chainID, vb), evidence.VoteB.Signature) { + return fmt.Errorf("verifying VoteB: %w", tmtypes.ErrVoteInvalidSignature) + } + + return nil +} diff --git a/x/ccv/provider/keeper/double_vote_test.go b/x/ccv/provider/keeper/double_vote_test.go new file mode 100644 index 0000000000..9bb789c381 --- /dev/null +++ b/x/ccv/provider/keeper/double_vote_test.go @@ -0,0 +1,284 @@ +package keeper_test + +import ( + "testing" + "time" + + testutil "github.com/cosmos/interchain-security/v2/testutil/crypto" + testkeeper "github.com/cosmos/interchain-security/v2/testutil/keeper" + "github.com/stretchr/testify/require" + tmencoding "github.com/tendermint/tendermint/crypto/encoding" + tmprotocrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" + tmtypes "github.com/tendermint/tendermint/types" +) + +func TestVerifyDoubleVotingEvidence(t *testing.T) { + keeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + chainID := "consumer" + + signer1 := tmtypes.NewMockPV() + signer2 := tmtypes.NewMockPV() + + val1 := tmtypes.NewValidator(signer1.PrivKey.PubKey(), 1) + val2 := tmtypes.NewValidator(signer2.PrivKey.PubKey(), 1) + + valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{val1, val2}) + + blockID1 := testutil.MakeBlockID([]byte("blockhash"), 1000, []byte("partshash")) + blockID2 := testutil.MakeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) + + ctx = ctx.WithBlockTime(time.Now()) + + valPubkey1, err := tmencoding.PubKeyToProto(val1.PubKey) + require.NoError(t, err) + + valPubkey2, err := tmencoding.PubKeyToProto(val2.PubKey) + require.NoError(t, err) + + testCases := []struct { + name string + votes []*tmtypes.Vote + chainID string + pubkey tmprotocrypto.PublicKey + expPass bool + }{ + { + "evidence has votes with different block height - shouldn't pass", + []*tmtypes.Vote{ + testutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight()+1, + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + testutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "evidence has votes with different validator address - shouldn't pass", + []*tmtypes.Vote{ + testutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + testutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer2, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "evidence has votes with same block IDs - shouldn't pass", + []*tmtypes.Vote{ + testutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + testutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "given chain ID isn't the same as the one used to sign the votes - shouldn't pass", + []*tmtypes.Vote{ + testutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + testutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + "WrongChainID", + valPubkey1, + false, + }, + { + "voteA is signed using the wrong chain ID - shouldn't pass", + []*tmtypes.Vote{ + testutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + "WrongChainID", + ), + testutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "voteB is signed using the wrong chain ID - shouldn't pass", + []*tmtypes.Vote{ + testutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + testutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + "WrongChainID", + ), + }, + chainID, + valPubkey1, + false, + }, + { + "invalid public key - shouldn't pass", + []*tmtypes.Vote{ + testutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + testutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + tmprotocrypto.PublicKey{}, + false, + }, + { + "wrong public key - shouldn't pass", + []*tmtypes.Vote{ + testutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + testutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey2, + false, + }, + { + "valid double voting evidence should pass", + []*tmtypes.Vote{ + testutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + testutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey1, + true, + }, + } + + for _, tc := range testCases { + err = keeper.VerifyDoubleVotingEvidence( + ctx, + tmtypes.DuplicateVoteEvidence{ + VoteA: tc.votes[0], + VoteB: tc.votes[1], + ValidatorPower: val1.VotingPower, + TotalVotingPower: val1.VotingPower, + Timestamp: tc.votes[0].Timestamp, + }, + tc.chainID, + tc.pubkey, + ) + if tc.expPass { + require.NoError(t, err) + } else { + require.Error(t, err) + } + } +} diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 0438df36e3..35d9219324 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -5,7 +5,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" ibcclienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" tmtypes "github.com/tendermint/tendermint/types" @@ -33,36 +32,22 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty return err } + provAddrs := make([]types.ProviderConsAddress, len(byzantineValidators)) + // jail and tombstone the Byzantine validators for _, v := range byzantineValidators { - // convert consumer consensus address - consumerAddr := types.NewConsumerConsAddress(sdk.ConsAddress(v.Address.Bytes())) - providerAddr := k.GetProviderAddrFromConsumerAddr(ctx, misbehaviour.Header1.Header.ChainID, consumerAddr) - val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) - - if !ok || val.IsUnbonded() { - logger.Error("validator not found or is unbonded", providerAddr.String()) - continue - } - - // jail validator if not already - if !val.IsJailed() { - k.stakingKeeper.Jail(ctx, providerAddr.ToSdkConsAddr()) - } - - // tombstone validator if not already - if !k.slashingKeeper.IsTombstoned(ctx, providerAddr.ToSdkConsAddr()) { - k.slashingKeeper.Tombstone(ctx, providerAddr.ToSdkConsAddr()) - k.Logger(ctx).Info("validator tombstoned", "provider cons addr", providerAddr.String()) - } - - // update jail time to end after double sign jail duration - k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime) + providerAddr := k.GetProviderAddrFromConsumerAddr( + ctx, + misbehaviour.Header1.Header.ChainID, + types.NewConsumerConsAddress(sdk.ConsAddress(v.Address.Bytes())), + ) + k.JailValidator(ctx, providerAddr) + provAddrs = append(provAddrs, providerAddr) } logger.Info( "confirmed equivocation light client attack", - "byzantine validators", byzantineValidators, + "byzantine validators", provAddrs, ) return nil diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index 80f325cc1b..b6b96bb010 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" ccvtypes "github.com/cosmos/interchain-security/v2/x/ccv/types" tmprotocrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" + tmtypes "github.com/tendermint/tendermint/types" ) type msgServer struct { @@ -146,3 +147,27 @@ func (k msgServer) SubmitConsumerMisbehaviour(goCtx context.Context, msg *types. return &types.MsgSubmitConsumerMisbehaviourResponse{}, nil } + +func (k msgServer) SubmitConsumerDoubleVoting(goCtx context.Context, msg *types.MsgSubmitConsumerDoubleVoting) (*types.MsgSubmitConsumerDoubleVotingResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + evidence, err := tmtypes.DuplicateVoteEvidenceFromProto(msg.DuplicateVoteEvidence) + if err != nil { + return nil, err + } + + if err := k.Keeper.HandleConsumerDoubleVoting(ctx, evidence, msg.InfractionBlockHeader.Header.ChainID); err != nil { + return &types.MsgSubmitConsumerDoubleVotingResponse{}, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + ccvtypes.EventTypeSubmitConsumerMisbehaviour, + sdk.NewAttribute(ccvtypes.AttributeConsumerDoubleVoting, msg.DuplicateVoteEvidence.String()), + sdk.NewAttribute(ccvtypes.AttributeChainID, msg.InfractionBlockHeader.Header.ChainID), + sdk.NewAttribute(ccvtypes.AttributeSubmitterAddress, msg.Submitter), + ), + }) + + return &types.MsgSubmitConsumerDoubleVotingResponse{}, nil +} diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go new file mode 100644 index 0000000000..f4648cc641 --- /dev/null +++ b/x/ccv/provider/keeper/punish_validator.go @@ -0,0 +1,38 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" +) + +// JailValidator jails the validator with the given provider consensus address +// Note that the tombstoning is temporarily removed until we slash validator +// for double signing on a consumer chain, see comment +// https://github.com/cosmos/interchain-security/pull/1232#issuecomment-1693127641. +func (k Keeper) JailValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress) { + logger := k.Logger(ctx) + + // get validator + val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) + if !ok || val.IsUnbonded() { + logger.Error("validator not found or is unbonded", providerAddr.String()) + return + } + + // check that the validator isn't tombstoned + if k.slashingKeeper.IsTombstoned(ctx, providerAddr.ToSdkConsAddr()) { + logger.Info("validator is already tombstoned", "provider cons addr", providerAddr.String()) + return + } + + // jail validator if not already + if !val.IsJailed() { + k.stakingKeeper.Jail(ctx, providerAddr.ToSdkConsAddr()) + } + + // update jail time to end after double sign jail duration + k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime) + + // TODO: add tombstoning back once we integrate the slashing +} diff --git a/x/ccv/provider/keeper/punish_validator_test.go b/x/ccv/provider/keeper/punish_validator_test.go new file mode 100644 index 0000000000..50da9ae4bb --- /dev/null +++ b/x/ccv/provider/keeper/punish_validator_test.go @@ -0,0 +1,132 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + cryptotestutil "github.com/cosmos/interchain-security/v2/testutil/crypto" + testkeeper "github.com/cosmos/interchain-security/v2/testutil/keeper" + "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" + "github.com/golang/mock/gomock" +) + +// TestJailValidator tests that the jailing of a validator is only executed +// under the conditions that the validator is neither unbonded, already jailed, nor tombstoned. +func TestJailValidator(t *testing.T) { + providerConsAddr := cryptotestutil.NewCryptoIdentityFromIntSeed(7842334).ProviderConsAddress() + testCases := []struct { + name string + provAddr types.ProviderConsAddress + expectedCalls func(sdk.Context, testkeeper.MockedKeepers, types.ProviderConsAddress) []*gomock.Call + }{ + { + "unfound validator", + providerConsAddr, + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, + provAddr types.ProviderConsAddress, + ) []*gomock.Call { + return []*gomock.Call{ + // We only expect a single call to GetValidatorByConsAddr. + // Method will return once validator is not found. + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + stakingtypes.Validator{}, false, // false = Not found. + ).Times(1), + } + }, + }, + { + "unbonded validator", + providerConsAddr, + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, + provAddr types.ProviderConsAddress, + ) []*gomock.Call { + return []*gomock.Call{ + // We only expect a single call to GetValidatorByConsAddr. + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + stakingtypes.Validator{Status: stakingtypes.Unbonded}, true, + ).Times(1), + } + }, + }, + { + "tombstoned validator", + providerConsAddr, + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, + provAddr types.ProviderConsAddress, + ) []*gomock.Call { + return []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + stakingtypes.Validator{}, true, + ).Times(1), + mocks.MockSlashingKeeper.EXPECT().IsTombstoned( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + true, + ).Times(1), + } + }, + }, + { + "jailed validator", + providerConsAddr, + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, + provAddr types.ProviderConsAddress, + ) []*gomock.Call { + return []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + stakingtypes.Validator{Jailed: true}, true, + ).Times(1), + mocks.MockSlashingKeeper.EXPECT().IsTombstoned( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + false, + ).Times(1), + mocks.MockSlashingKeeper.EXPECT().JailUntil( + ctx, providerConsAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime). + Times(1), + } + }, + }, + { + "bonded validator", + providerConsAddr, + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, + provAddr types.ProviderConsAddress, + ) []*gomock.Call { + return []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + stakingtypes.Validator{Status: stakingtypes.Bonded}, true, + ).Times(1), + mocks.MockSlashingKeeper.EXPECT().IsTombstoned( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + false, + ).Times(1), + mocks.MockStakingKeeper.EXPECT().Jail( + ctx, providerConsAddr.ToSdkConsAddr()). + Times(1), + mocks.MockSlashingKeeper.EXPECT().JailUntil( + ctx, providerConsAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime). + Times(1), + } + }, + }, + } + + for _, tc := range testCases { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx( + t, testkeeper.NewInMemKeeperParams(t)) + + // Setup expected mock calls + gomock.InOrder(tc.expectedCalls(ctx, mocks, tc.provAddr)...) + + // Execute method and assert expected mock calls + providerKeeper.JailValidator(ctx, tc.provAddr) + + ctrl.Finish() + } +} diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 5c217f53b8..e5dbb16697 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -2,11 +2,13 @@ package types import ( "encoding/json" + "fmt" "strings" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" + tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" ) // provider message types @@ -14,6 +16,7 @@ const ( TypeMsgAssignConsumerKey = "assign_consumer_key" TypeMsgRegisterConsumerRewardDenom = "register_consumer_reward_denom" TypeMsgSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" + TypeMsgSubmitConsumerDoubleVoting = "submit_consumer_double_vote" ) var ( @@ -185,3 +188,47 @@ func (msg MsgSubmitConsumerMisbehaviour) GetSigners() []sdk.AccAddress { } return []sdk.AccAddress{addr} } + +func NewMsgSubmitConsumerDoubleVoting(submitter sdk.AccAddress, ev *tmtypes.DuplicateVoteEvidence, header *ibctmtypes.Header) (*MsgSubmitConsumerDoubleVoting, error) { + return &MsgSubmitConsumerDoubleVoting{Submitter: submitter.String(), DuplicateVoteEvidence: ev, InfractionBlockHeader: header}, nil +} + +// Route implements the sdk.Msg interface. +func (msg MsgSubmitConsumerDoubleVoting) Route() string { return RouterKey } + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerDoubleVoting) Type() string { + return TypeMsgSubmitConsumerDoubleVoting +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerDoubleVoting) ValidateBasic() error { + if msg.Submitter == "" { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Submitter) + } + if msg.DuplicateVoteEvidence == nil { + return fmt.Errorf("double voting evidence cannot be nil") + } + + if msg.InfractionBlockHeader.Header == nil { + return fmt.Errorf("infraction header cannot be nil") + } + + return nil +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerDoubleVoting) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(&msg) + return sdk.MustSortJSON(bz) +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerDoubleVoting) GetSigners() []sdk.AccAddress { + addr, err := sdk.AccAddressFromBech32(msg.Submitter) + if err != nil { + // same behavior as in cosmos-sdk + panic(err) + } + return []sdk.AccAddress{addr} +} diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index f27ab52533..9182d546b5 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -12,6 +12,7 @@ import ( grpc1 "github.com/gogo/protobuf/grpc" proto "github.com/gogo/protobuf/proto" _ "github.com/regen-network/cosmos-proto" + types1 "github.com/tendermint/tendermint/proto/tendermint/types" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" @@ -271,6 +272,86 @@ func (m *MsgSubmitConsumerMisbehaviourResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSubmitConsumerMisbehaviourResponse proto.InternalMessageInfo +// MsgSubmitConsumerDoubleVoting defines a message that reports an equivocation +// observed on a consumer chain +type MsgSubmitConsumerDoubleVoting struct { + Submitter string `protobuf:"bytes,1,opt,name=submitter,proto3" json:"submitter,omitempty"` + // The equivocation of the consumer chain wrapping + // an evidence of a validator that signed two conflicting votes + DuplicateVoteEvidence *types1.DuplicateVoteEvidence `protobuf:"bytes,2,opt,name=duplicate_vote_evidence,json=duplicateVoteEvidence,proto3" json:"duplicate_vote_evidence,omitempty"` + // The light client header of the infraction block + InfractionBlockHeader *types.Header `protobuf:"bytes,3,opt,name=infraction_block_header,json=infractionBlockHeader,proto3" json:"infraction_block_header,omitempty"` +} + +func (m *MsgSubmitConsumerDoubleVoting) Reset() { *m = MsgSubmitConsumerDoubleVoting{} } +func (m *MsgSubmitConsumerDoubleVoting) String() string { return proto.CompactTextString(m) } +func (*MsgSubmitConsumerDoubleVoting) ProtoMessage() {} +func (*MsgSubmitConsumerDoubleVoting) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{6} +} +func (m *MsgSubmitConsumerDoubleVoting) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSubmitConsumerDoubleVoting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSubmitConsumerDoubleVoting.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSubmitConsumerDoubleVoting) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSubmitConsumerDoubleVoting.Merge(m, src) +} +func (m *MsgSubmitConsumerDoubleVoting) XXX_Size() int { + return m.Size() +} +func (m *MsgSubmitConsumerDoubleVoting) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSubmitConsumerDoubleVoting.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSubmitConsumerDoubleVoting proto.InternalMessageInfo + +type MsgSubmitConsumerDoubleVotingResponse struct { +} + +func (m *MsgSubmitConsumerDoubleVotingResponse) Reset() { *m = MsgSubmitConsumerDoubleVotingResponse{} } +func (m *MsgSubmitConsumerDoubleVotingResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSubmitConsumerDoubleVotingResponse) ProtoMessage() {} +func (*MsgSubmitConsumerDoubleVotingResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{7} +} +func (m *MsgSubmitConsumerDoubleVotingResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSubmitConsumerDoubleVotingResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSubmitConsumerDoubleVotingResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSubmitConsumerDoubleVotingResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSubmitConsumerDoubleVotingResponse.Merge(m, src) +} +func (m *MsgSubmitConsumerDoubleVotingResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSubmitConsumerDoubleVotingResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSubmitConsumerDoubleVotingResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSubmitConsumerDoubleVotingResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgAssignConsumerKey)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKey") proto.RegisterType((*MsgAssignConsumerKeyResponse)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKeyResponse") @@ -278,6 +359,8 @@ func init() { proto.RegisterType((*MsgRegisterConsumerRewardDenomResponse)(nil), "interchain_security.ccv.provider.v1.MsgRegisterConsumerRewardDenomResponse") proto.RegisterType((*MsgSubmitConsumerMisbehaviour)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviour") proto.RegisterType((*MsgSubmitConsumerMisbehaviourResponse)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviourResponse") + proto.RegisterType((*MsgSubmitConsumerDoubleVoting)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerDoubleVoting") + proto.RegisterType((*MsgSubmitConsumerDoubleVotingResponse)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerDoubleVotingResponse") } func init() { @@ -285,43 +368,51 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 568 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x3f, 0x6f, 0x13, 0x4f, - 0x10, 0xf5, 0xfd, 0xa2, 0x1f, 0x24, 0x9b, 0x80, 0xc4, 0xc9, 0x85, 0x73, 0x98, 0x33, 0x18, 0x01, - 0x29, 0xc2, 0xae, 0x6c, 0x0a, 0x44, 0x24, 0x0a, 0x3b, 0x34, 0x10, 0x59, 0x42, 0x47, 0x81, 0x44, - 0x81, 0x75, 0xb7, 0xbb, 0xac, 0x57, 0xf8, 0x76, 0x4f, 0xbb, 0x7b, 0x47, 0xee, 0x1b, 0x50, 0x42, - 0x85, 0xe8, 0xf2, 0x01, 0x90, 0xf8, 0x1a, 0x94, 0x29, 0xa9, 0x10, 0xb2, 0x1b, 0x6a, 0x4a, 0x2a, - 0xe4, 0xfb, 0x63, 0x5f, 0x84, 0xb1, 0x2c, 0xa0, 0xdb, 0x99, 0x79, 0xfb, 0xde, 0x1b, 0xcd, 0x68, - 0xc0, 0x3e, 0x17, 0x86, 0x2a, 0x3c, 0xf2, 0xb9, 0x18, 0x6a, 0x8a, 0x63, 0xc5, 0x4d, 0x8a, 0x30, - 0x4e, 0x50, 0xa4, 0x64, 0xc2, 0x09, 0x55, 0x28, 0xe9, 0x20, 0x73, 0x0c, 0x23, 0x25, 0x8d, 0xb4, - 0xaf, 0x2f, 0x41, 0x43, 0x8c, 0x13, 0x58, 0xa2, 0x61, 0xd2, 0x71, 0x9a, 0x4c, 0x4a, 0x36, 0xa6, - 0xc8, 0x8f, 0x38, 0xf2, 0x85, 0x90, 0xc6, 0x37, 0x5c, 0x0a, 0x9d, 0x53, 0x38, 0x75, 0x26, 0x99, - 0xcc, 0x9e, 0x68, 0xf6, 0x2a, 0xb2, 0xbb, 0x58, 0xea, 0x50, 0xea, 0x61, 0x5e, 0xc8, 0x83, 0xb2, - 0x54, 0xd0, 0x65, 0x51, 0x10, 0xbf, 0x40, 0xbe, 0x48, 0x8b, 0x12, 0xe2, 0x01, 0x46, 0x63, 0xce, - 0x46, 0x06, 0x8f, 0x39, 0x15, 0x46, 0x23, 0x43, 0x05, 0xa1, 0x2a, 0xe4, 0xc2, 0x64, 0xbe, 0xe7, - 0x51, 0xfe, 0xa1, 0xfd, 0xce, 0x02, 0xf5, 0x81, 0x66, 0x3d, 0xad, 0x39, 0x13, 0x87, 0x52, 0xe8, - 0x38, 0xa4, 0xea, 0x88, 0xa6, 0xf6, 0x2e, 0xd8, 0xcc, 0xbb, 0xe2, 0xa4, 0x61, 0x5d, 0xb5, 0xf6, - 0xb6, 0xbc, 0xf3, 0x59, 0xfc, 0x90, 0xd8, 0x77, 0xc1, 0x85, 0xb2, 0xbb, 0xa1, 0x4f, 0x88, 0x6a, - 0xfc, 0x37, 0xab, 0xf7, 0xed, 0xef, 0x5f, 0x5a, 0x17, 0x53, 0x3f, 0x1c, 0x1f, 0xb4, 0x67, 0x59, - 0xaa, 0x75, 0xdb, 0xdb, 0x29, 0x81, 0x3d, 0x42, 0x94, 0x7d, 0x0d, 0xec, 0xe0, 0x42, 0x62, 0xf8, - 0x92, 0xa6, 0x8d, 0x8d, 0x8c, 0x77, 0x1b, 0x2f, 0x64, 0x0f, 0x36, 0x5f, 0x9f, 0xb4, 0x6a, 0xdf, - 0x4e, 0x5a, 0xb5, 0xb6, 0x0b, 0x9a, 0xcb, 0x8c, 0x79, 0x54, 0x47, 0x52, 0x68, 0xda, 0x7e, 0x0e, - 0xdc, 0x81, 0x66, 0x1e, 0x65, 0x5c, 0x1b, 0xaa, 0x4a, 0x84, 0x47, 0x5f, 0xf9, 0x8a, 0x3c, 0xa0, - 0x42, 0x86, 0x76, 0x1d, 0xfc, 0x4f, 0x66, 0x8f, 0xc2, 0x7f, 0x1e, 0xd8, 0x4d, 0xb0, 0x45, 0x68, - 0x24, 0x35, 0x37, 0xb2, 0x70, 0xee, 0x2d, 0x12, 0x15, 0xfd, 0x3d, 0x70, 0x73, 0x35, 0xff, 0xdc, - 0xc9, 0x7b, 0x0b, 0x5c, 0x19, 0x68, 0xf6, 0x24, 0x0e, 0x42, 0x6e, 0x4a, 0xe0, 0x80, 0xeb, 0x80, - 0x8e, 0xfc, 0x84, 0xcb, 0x58, 0xcd, 0x34, 0x75, 0x56, 0x35, 0x54, 0x15, 0x6e, 0x16, 0x09, 0xfb, - 0x31, 0xd8, 0x09, 0x2b, 0xe8, 0xcc, 0xd4, 0x76, 0x77, 0x1f, 0xf2, 0x00, 0xc3, 0xea, 0x2c, 0x61, - 0x65, 0x7a, 0x49, 0x07, 0x56, 0x15, 0xbc, 0x33, 0x0c, 0x95, 0x2e, 0x6e, 0x81, 0x1b, 0x2b, 0xad, - 0x95, 0x4d, 0x74, 0x7f, 0x6c, 0x80, 0x8d, 0x81, 0x66, 0xf6, 0x5b, 0x0b, 0x5c, 0xfa, 0x75, 0x1b, - 0xee, 0xc1, 0x35, 0xf6, 0x1c, 0x2e, 0x9b, 0x97, 0xd3, 0xfb, 0xe3, 0xaf, 0xa5, 0x37, 0xfb, 0xa3, - 0x05, 0x2e, 0xaf, 0x1a, 0xf4, 0xe1, 0xba, 0x12, 0x2b, 0x48, 0x9c, 0xa3, 0x7f, 0x40, 0x32, 0x77, - 0xfc, 0xc1, 0x02, 0xce, 0x8a, 0x7d, 0xe8, 0xaf, 0xab, 0xf5, 0x7b, 0x0e, 0xe7, 0xd1, 0xdf, 0x73, - 0x94, 0x76, 0xfb, 0x4f, 0x3f, 0x4d, 0x5c, 0xeb, 0x74, 0xe2, 0x5a, 0x5f, 0x27, 0xae, 0xf5, 0x66, - 0xea, 0xd6, 0x4e, 0xa7, 0x6e, 0xed, 0xf3, 0xd4, 0xad, 0x3d, 0xbb, 0xcf, 0xb8, 0x19, 0xc5, 0x01, - 0xc4, 0x32, 0x2c, 0x8e, 0x10, 0x5a, 0xc8, 0xde, 0x9e, 0xdf, 0xc7, 0xa4, 0x8b, 0x8e, 0xcf, 0x1e, - 0x49, 0x93, 0x46, 0x54, 0x07, 0xe7, 0xb2, 0x2b, 0x73, 0xe7, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xe3, 0x4f, 0x57, 0x26, 0x55, 0x05, 0x00, 0x00, + // 694 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcf, 0x4e, 0x14, 0x4f, + 0x10, 0xde, 0x81, 0xf0, 0xfb, 0x41, 0x83, 0x26, 0x4e, 0x20, 0xc0, 0x8a, 0xb3, 0xba, 0x46, 0xe0, + 0x80, 0xd3, 0x61, 0x3d, 0x18, 0x49, 0x3c, 0xb0, 0x60, 0xa2, 0x92, 0x4d, 0xcc, 0x98, 0x60, 0xe2, + 0x81, 0xc9, 0x4c, 0x77, 0x31, 0xdb, 0x61, 0xa7, 0x7b, 0xd3, 0xdd, 0x33, 0xb2, 0x6f, 0xc0, 0x51, + 0x4f, 0xc6, 0x1b, 0x57, 0x13, 0x13, 0x5f, 0xc3, 0x23, 0x47, 0x4f, 0xc6, 0xc0, 0xc5, 0xb3, 0x4f, + 0x60, 0xe6, 0xdf, 0xee, 0x10, 0xd7, 0x85, 0xa0, 0xb7, 0xae, 0xaa, 0xaf, 0xbf, 0xfa, 0xaa, 0xa7, + 0x6a, 0x0a, 0xad, 0x31, 0xae, 0x41, 0x92, 0xb6, 0xc7, 0xb8, 0xab, 0x80, 0x44, 0x92, 0xe9, 0x1e, + 0x26, 0x24, 0xc6, 0x5d, 0x29, 0x62, 0x46, 0x41, 0xe2, 0x78, 0x1d, 0xeb, 0x43, 0xbb, 0x2b, 0x85, + 0x16, 0xe6, 0xdd, 0x21, 0x68, 0x9b, 0x90, 0xd8, 0x2e, 0xd0, 0x76, 0xbc, 0x5e, 0x5d, 0x0a, 0x84, + 0x08, 0x3a, 0x80, 0xbd, 0x2e, 0xc3, 0x1e, 0xe7, 0x42, 0x7b, 0x9a, 0x09, 0xae, 0x32, 0x8a, 0xea, + 0x6c, 0x20, 0x02, 0x91, 0x1e, 0x71, 0x72, 0xca, 0xbd, 0x8b, 0x44, 0xa8, 0x50, 0x28, 0x37, 0x0b, + 0x64, 0x46, 0x11, 0xca, 0xe9, 0x52, 0xcb, 0x8f, 0xf6, 0xb1, 0xc7, 0x7b, 0x79, 0x08, 0x33, 0x9f, + 0xe0, 0x0e, 0x0b, 0xda, 0x9a, 0x74, 0x18, 0x70, 0xad, 0xb0, 0x06, 0x4e, 0x41, 0x86, 0x8c, 0xeb, + 0x54, 0x77, 0xdf, 0xca, 0x2f, 0xd4, 0x4a, 0x71, 0xdd, 0xeb, 0x82, 0xc2, 0x90, 0xc8, 0xe6, 0x04, + 0x32, 0x40, 0xfd, 0xbd, 0x81, 0x66, 0x5b, 0x2a, 0xd8, 0x54, 0x8a, 0x05, 0x7c, 0x4b, 0x70, 0x15, + 0x85, 0x20, 0x77, 0xa0, 0x67, 0x2e, 0xa2, 0xc9, 0xac, 0x6c, 0x46, 0x17, 0x8c, 0xdb, 0xc6, 0xea, + 0x94, 0xf3, 0x7f, 0x6a, 0x3f, 0xa3, 0xe6, 0x43, 0x74, 0xad, 0x28, 0xdf, 0xf5, 0x28, 0x95, 0x0b, + 0x63, 0x49, 0xbc, 0x69, 0xfe, 0xfc, 0x56, 0xbb, 0xde, 0xf3, 0xc2, 0xce, 0x46, 0x3d, 0xf1, 0x82, + 0x52, 0x75, 0x67, 0xa6, 0x00, 0x6e, 0x52, 0x2a, 0xcd, 0x3b, 0x68, 0x86, 0xe4, 0x29, 0xdc, 0x03, + 0xe8, 0x2d, 0x8c, 0xa7, 0xbc, 0xd3, 0x64, 0x90, 0x76, 0x63, 0xf2, 0xe8, 0xb8, 0x56, 0xf9, 0x71, + 0x5c, 0xab, 0xd4, 0x2d, 0xb4, 0x34, 0x4c, 0x98, 0x03, 0xaa, 0x2b, 0xb8, 0x82, 0xfa, 0x1e, 0xb2, + 0x5a, 0x2a, 0x70, 0x20, 0x60, 0x4a, 0x83, 0x2c, 0x10, 0x0e, 0xbc, 0xf1, 0x24, 0xdd, 0x06, 0x2e, + 0x42, 0x73, 0x16, 0x4d, 0xd0, 0xe4, 0x90, 0xeb, 0xcf, 0x0c, 0x73, 0x09, 0x4d, 0x51, 0xe8, 0x0a, + 0xc5, 0xb4, 0xc8, 0x95, 0x3b, 0x03, 0x47, 0x29, 0xff, 0x2a, 0x5a, 0x1e, 0xcd, 0xdf, 0x57, 0xf2, + 0xc1, 0x40, 0xb7, 0x5a, 0x2a, 0x78, 0x19, 0xf9, 0x21, 0xd3, 0x05, 0xb0, 0xc5, 0x94, 0x0f, 0x6d, + 0x2f, 0x66, 0x22, 0x92, 0x49, 0x4e, 0x95, 0x46, 0x35, 0xc8, 0x5c, 0xcd, 0xc0, 0x61, 0xbe, 0x40, + 0x33, 0x61, 0x09, 0x9d, 0x8a, 0x9a, 0x6e, 0xac, 0xd9, 0xcc, 0x27, 0x76, 0xf9, 0x63, 0xdb, 0xa5, + 0xcf, 0x1b, 0xaf, 0xdb, 0xe5, 0x0c, 0xce, 0x39, 0x86, 0x52, 0x15, 0x2b, 0xe8, 0xde, 0x48, 0x69, + 0xfd, 0x22, 0x8e, 0xc6, 0x86, 0x14, 0xb1, 0x2d, 0x22, 0xbf, 0x03, 0xbb, 0x42, 0x33, 0x1e, 0x5c, + 0x50, 0x84, 0x8b, 0xe6, 0x69, 0xd4, 0xed, 0x30, 0xe2, 0x69, 0x70, 0x63, 0xa1, 0xc1, 0x2d, 0x3a, + 0x2d, 0xaf, 0x67, 0xa5, 0x2c, 0x3f, 0xed, 0x45, 0x7b, 0xbb, 0xb8, 0xb0, 0x2b, 0x34, 0x3c, 0xc9, + 0xe1, 0xce, 0x1c, 0x1d, 0xe6, 0x36, 0xf7, 0xd0, 0x3c, 0xe3, 0xfb, 0xd2, 0x23, 0xc9, 0x70, 0xb9, + 0x7e, 0x47, 0x90, 0x03, 0xb7, 0x0d, 0x1e, 0x05, 0x99, 0xf6, 0xd1, 0x74, 0x63, 0xf9, 0xa2, 0x07, + 0x7b, 0x9a, 0xa2, 0x9d, 0xb9, 0x01, 0x4d, 0x33, 0x61, 0xc9, 0xdc, 0x17, 0xbc, 0x59, 0xf9, 0x25, + 0x8a, 0x37, 0x6b, 0x7c, 0x9c, 0x40, 0xe3, 0x2d, 0x15, 0x98, 0xef, 0x0c, 0x74, 0xe3, 0xf7, 0x09, + 0x7a, 0x64, 0x5f, 0xe2, 0xe7, 0x61, 0x0f, 0xeb, 0xf1, 0xea, 0xe6, 0x95, 0xaf, 0x16, 0xda, 0xcc, + 0xcf, 0x06, 0xba, 0x39, 0x6a, 0x38, 0xb6, 0x2e, 0x9b, 0x62, 0x04, 0x49, 0x75, 0xe7, 0x1f, 0x90, + 0xf4, 0x15, 0x7f, 0x32, 0x50, 0x75, 0xc4, 0x0c, 0x35, 0x2f, 0x9b, 0xeb, 0xcf, 0x1c, 0xd5, 0xe7, + 0x7f, 0xcf, 0x31, 0x42, 0xee, 0xb9, 0x69, 0xb9, 0xa2, 0xdc, 0x32, 0xc7, 0x55, 0xe5, 0x0e, 0xeb, + 0xd5, 0xe6, 0xab, 0x2f, 0xa7, 0x96, 0x71, 0x72, 0x6a, 0x19, 0xdf, 0x4f, 0x2d, 0xe3, 0xed, 0x99, + 0x55, 0x39, 0x39, 0xb3, 0x2a, 0x5f, 0xcf, 0xac, 0xca, 0xeb, 0xc7, 0x01, 0xd3, 0xed, 0xc8, 0xb7, + 0x89, 0x08, 0xf3, 0x45, 0x84, 0x07, 0x69, 0xef, 0xf7, 0x77, 0x64, 0xdc, 0xc0, 0x87, 0xe7, 0x17, + 0x65, 0x3a, 0xc4, 0xfe, 0x7f, 0xe9, 0x22, 0x79, 0xf0, 0x2b, 0x00, 0x00, 0xff, 0xff, 0xe9, 0x25, + 0x43, 0x02, 0x59, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -339,6 +430,7 @@ type MsgClient interface { AssignConsumerKey(ctx context.Context, in *MsgAssignConsumerKey, opts ...grpc.CallOption) (*MsgAssignConsumerKeyResponse, error) RegisterConsumerRewardDenom(ctx context.Context, in *MsgRegisterConsumerRewardDenom, opts ...grpc.CallOption) (*MsgRegisterConsumerRewardDenomResponse, error) SubmitConsumerMisbehaviour(ctx context.Context, in *MsgSubmitConsumerMisbehaviour, opts ...grpc.CallOption) (*MsgSubmitConsumerMisbehaviourResponse, error) + SubmitConsumerDoubleVoting(ctx context.Context, in *MsgSubmitConsumerDoubleVoting, opts ...grpc.CallOption) (*MsgSubmitConsumerDoubleVotingResponse, error) } type msgClient struct { @@ -376,11 +468,21 @@ func (c *msgClient) SubmitConsumerMisbehaviour(ctx context.Context, in *MsgSubmi return out, nil } +func (c *msgClient) SubmitConsumerDoubleVoting(ctx context.Context, in *MsgSubmitConsumerDoubleVoting, opts ...grpc.CallOption) (*MsgSubmitConsumerDoubleVotingResponse, error) { + out := new(MsgSubmitConsumerDoubleVotingResponse) + err := c.cc.Invoke(ctx, "/interchain_security.ccv.provider.v1.Msg/SubmitConsumerDoubleVoting", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { AssignConsumerKey(context.Context, *MsgAssignConsumerKey) (*MsgAssignConsumerKeyResponse, error) RegisterConsumerRewardDenom(context.Context, *MsgRegisterConsumerRewardDenom) (*MsgRegisterConsumerRewardDenomResponse, error) SubmitConsumerMisbehaviour(context.Context, *MsgSubmitConsumerMisbehaviour) (*MsgSubmitConsumerMisbehaviourResponse, error) + SubmitConsumerDoubleVoting(context.Context, *MsgSubmitConsumerDoubleVoting) (*MsgSubmitConsumerDoubleVotingResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -396,6 +498,9 @@ func (*UnimplementedMsgServer) RegisterConsumerRewardDenom(ctx context.Context, func (*UnimplementedMsgServer) SubmitConsumerMisbehaviour(ctx context.Context, req *MsgSubmitConsumerMisbehaviour) (*MsgSubmitConsumerMisbehaviourResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SubmitConsumerMisbehaviour not implemented") } +func (*UnimplementedMsgServer) SubmitConsumerDoubleVoting(ctx context.Context, req *MsgSubmitConsumerDoubleVoting) (*MsgSubmitConsumerDoubleVotingResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitConsumerDoubleVoting not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -455,6 +560,24 @@ func _Msg_SubmitConsumerMisbehaviour_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _Msg_SubmitConsumerDoubleVoting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSubmitConsumerDoubleVoting) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SubmitConsumerDoubleVoting(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/interchain_security.ccv.provider.v1.Msg/SubmitConsumerDoubleVoting", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SubmitConsumerDoubleVoting(ctx, req.(*MsgSubmitConsumerDoubleVoting)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "interchain_security.ccv.provider.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -471,6 +594,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "SubmitConsumerMisbehaviour", Handler: _Msg_SubmitConsumerMisbehaviour_Handler, }, + { + MethodName: "SubmitConsumerDoubleVoting", + Handler: _Msg_SubmitConsumerDoubleVoting_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "interchain_security/ccv/provider/v1/tx.proto", @@ -668,6 +795,83 @@ func (m *MsgSubmitConsumerMisbehaviourResponse) MarshalToSizedBuffer(dAtA []byte return len(dAtA) - i, nil } +func (m *MsgSubmitConsumerDoubleVoting) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSubmitConsumerDoubleVoting) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSubmitConsumerDoubleVoting) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.InfractionBlockHeader != nil { + { + size, err := m.InfractionBlockHeader.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.DuplicateVoteEvidence != nil { + { + size, err := m.DuplicateVoteEvidence.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Submitter) > 0 { + i -= len(m.Submitter) + copy(dAtA[i:], m.Submitter) + i = encodeVarintTx(dAtA, i, uint64(len(m.Submitter))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgSubmitConsumerDoubleVotingResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSubmitConsumerDoubleVotingResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSubmitConsumerDoubleVotingResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -761,6 +965,36 @@ func (m *MsgSubmitConsumerMisbehaviourResponse) Size() (n int) { return n } +func (m *MsgSubmitConsumerDoubleVoting) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Submitter) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.DuplicateVoteEvidence != nil { + l = m.DuplicateVoteEvidence.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.InfractionBlockHeader != nil { + l = m.InfractionBlockHeader.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgSubmitConsumerDoubleVotingResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1295,6 +1529,210 @@ func (m *MsgSubmitConsumerMisbehaviourResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgSubmitConsumerDoubleVoting) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSubmitConsumerDoubleVoting: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSubmitConsumerDoubleVoting: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Submitter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Submitter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DuplicateVoteEvidence", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DuplicateVoteEvidence == nil { + m.DuplicateVoteEvidence = &types1.DuplicateVoteEvidence{} + } + if err := m.DuplicateVoteEvidence.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InfractionBlockHeader", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.InfractionBlockHeader == nil { + m.InfractionBlockHeader = &types.Header{} + } + if err := m.InfractionBlockHeader.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSubmitConsumerDoubleVotingResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSubmitConsumerDoubleVotingResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSubmitConsumerDoubleVotingResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/ccv/types/errors.go b/x/ccv/types/errors.go index 79c0e1e31c..e0cb663219 100644 --- a/x/ccv/types/errors.go +++ b/x/ccv/types/errors.go @@ -25,4 +25,5 @@ var ( ErrClientNotFound = errorsmod.Register(ModuleName, 18, "client not found") ErrDuplicateConsumerChain = errorsmod.Register(ModuleName, 19, "consumer chain already exists") ErrConsumerChainNotFound = errorsmod.Register(ModuleName, 20, "consumer chain not found") + ErrInvalidEvidence = errorsmod.Register(ModuleName, 21, "invalid consumer double voting evidence") ) diff --git a/x/ccv/types/events.go b/x/ccv/types/events.go index df91333fa0..28796144fe 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -38,6 +38,7 @@ const ( AttributeMisbehaviourClientId = "misbehaviour_client_id" AttributeMisbehaviourHeight1 = "misbehaviour_height_1" AttributeMisbehaviourHeight2 = "misbehaviour_height_2" + AttributeConsumerDoubleVoting = "consumer_double_voting" AttributeDistributionCurrentHeight = "current_distribution_height" AttributeDistributionNextHeight = "next_distribution_height" From f12a5c016f40bd75beb6821b986344802436159b Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 31 Aug 2023 17:45:56 +0200 Subject: [PATCH 05/40] fix: tiny bug in `NewSubmitConsumerDoubleVotingCmd` (#1247) * fix double voting cli * fix bug double signing handler * godoc * nits * revert wrong push of lasts commits --- x/ccv/provider/client/cli/tx.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 1d3ccc0011..a4b6e233e0 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -153,7 +153,7 @@ Examples: func NewSubmitConsumerDoubleVotingCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "submit consumer-double-voting [evidence] [infraction_header]", + Use: "submit-consumer-double-voting [evidence] [infraction_header]", Short: "submit a double voting evidence for a consumer chain", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { @@ -176,7 +176,7 @@ func NewSubmitConsumerDoubleVotingCmd() *cobra.Command { return err } - msg, err := types.NewMsgSubmitConsumerDoubleVoting(submitter, ev, nil) + msg, err := types.NewMsgSubmitConsumerDoubleVoting(submitter, ev, &header) if err != nil { return err } From 2501e83e9678f16bda19ce2187dda7abaa806eed Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 4 Sep 2023 16:57:43 +0200 Subject: [PATCH 06/40] fix: make `HandleConsumerDoubleVoting` works with provider pubkeys (#1254) * fix double voting cli * fix bug double signing handler * godoc * nits * lint * nit --- tests/integration/double_vote.go | 104 ++++++++++++++++------ x/ccv/provider/keeper/double_vote.go | 59 ++++++++---- x/ccv/provider/keeper/double_vote_test.go | 12 +-- x/ccv/provider/types/msg.go | 12 ++- 4 files changed, 137 insertions(+), 50 deletions(-) diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index 04d976ed61..184f7604bb 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -19,32 +19,58 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { s.setDefaultValSigningInfo(*v) } - valSet, err := tmtypes.ValidatorSetFromProto(s.consumerChain.LastHeader.ValidatorSet) + consuValSet, err := tmtypes.ValidatorSetFromProto(s.consumerChain.LastHeader.ValidatorSet) s.Require().NoError(err) + consuVal := consuValSet.Validators[0] + s.Require().NoError(err) + consuSigner := s.consumerChain.Signers[consuVal.Address.String()] - val := valSet.Validators[0] - signer := s.consumerChain.Signers[val.Address.String()] + provValSet, err := tmtypes.ValidatorSetFromProto(s.providerChain.LastHeader.ValidatorSet) + s.Require().NoError(err) + provVal := provValSet.Validators[0] + provSigner := s.providerChain.Signers[provVal.Address.String()] blockID1 := testutil.MakeBlockID([]byte("blockhash"), 1000, []byte("partshash")) blockID2 := testutil.MakeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) // Note that votes are signed along with the chain ID // see VoteSignBytes in https://github.com/cometbft/cometbft/blob/main/types/vote.go#L139 - vote1 := testutil.MakeAndSignVote( + + // create two votes using the consumer validator key + consuVote := testutil.MakeAndSignVote( + blockID1, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + consuValSet, + consuSigner, + s.consumerChain.ChainID, + ) + + consuBadVote := testutil.MakeAndSignVote( + blockID2, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + consuValSet, + consuSigner, + s.consumerChain.ChainID, + ) + + // create two votes using the provider validator key + provVote := testutil.MakeAndSignVote( blockID1, s.consumerCtx().BlockHeight(), s.consumerCtx().BlockTime(), - valSet, - signer, + provValSet, + provSigner, s.consumerChain.ChainID, ) - badVote := testutil.MakeAndSignVote( + provBadVote := testutil.MakeAndSignVote( blockID2, s.consumerCtx().BlockHeight(), s.consumerCtx().BlockTime(), - valSet, - signer, + provValSet, + provSigner, s.consumerChain.ChainID, ) @@ -57,10 +83,10 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { { "invalid consumer chain id - shouldn't pass", &tmtypes.DuplicateVoteEvidence{ - VoteA: vote1, - VoteB: badVote, - ValidatorPower: val.VotingPower, - TotalVotingPower: val.VotingPower, + VoteA: consuVote, + VoteB: consuBadVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, Timestamp: s.consumerCtx().BlockTime(), }, "chainID", @@ -68,12 +94,12 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { }, { // create an invalid evidence containing two identical votes - "invalid double voting evidence - shouldn't pass", + "invalid double voting evidence with identical votes - shouldn't pass", &tmtypes.DuplicateVoteEvidence{ - VoteA: vote1, - VoteB: vote1, - ValidatorPower: val.VotingPower, - TotalVotingPower: val.VotingPower, + VoteA: consuVote, + VoteB: consuVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, Timestamp: s.consumerCtx().BlockTime(), }, s.consumerChain.ChainID, @@ -84,12 +110,25 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { // we create two votes that only differ by their Block IDs and // signed them using the same validator private key and chain ID // of the consumer chain - "valid double voting evidence - should pass", + "valid double voting evidence 1 - should pass", &tmtypes.DuplicateVoteEvidence{ - VoteA: vote1, - VoteB: badVote, - ValidatorPower: val.VotingPower, - TotalVotingPower: val.VotingPower, + VoteA: consuVote, + VoteB: consuBadVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + true, + }, + { + // create a double voting evidence using the provider validator key + "valid double voting evidence 2 - should pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: provVote, + VoteB: provBadVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, Timestamp: s.consumerCtx().BlockTime(), }, s.consumerChain.ChainID, @@ -97,26 +136,37 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { }, } - consuAddr := types.NewConsumerConsAddress(sdk.ConsAddress(val.Address.Bytes())) + consuAddr := types.NewConsumerConsAddress(sdk.ConsAddress(consuVal.Address.Bytes())) provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) for _, tc := range testCases { s.Run(tc.name, func() { + // reset context for each run + provCtx := s.providerCtx() + + // if the evidence was built using the validator provider address and key, + // we remove the consumer key assigned to the validator otherwise + // HandleConsumerDoubleVoting uses the consumer key to verify the signature + if tc.ev.VoteA.ValidatorAddress.String() != consuVal.Address.String() { + s.providerApp.GetProviderKeeper().DeleteKeyAssignments(provCtx, s.consumerChain.ChainID) + } + err = s.providerApp.GetProviderKeeper().HandleConsumerDoubleVoting( - s.providerCtx(), + provCtx, tc.ev, tc.chainID, ) + if tc.expPass { s.Require().NoError(err) // verifies that the jailing has occurred - s.Require().True(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(s.providerCtx(), provAddr.ToSdkConsAddr())) + s.Require().True(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(provCtx, provAddr.ToSdkConsAddr())) } else { s.Require().Error(err) // verifies that no jailing and has occurred - s.Require().False(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(s.providerCtx(), provAddr.ToSdkConsAddr())) + s.Require().False(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(provCtx, provAddr.ToSdkConsAddr())) } }) } diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index ee47e233ee..171be6b250 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -5,11 +5,12 @@ import ( "fmt" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" ccvtypes "github.com/cosmos/interchain-security/v2/x/ccv/types" - tmprotocrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" tmtypes "github.com/tendermint/tendermint/types" ) @@ -23,14 +24,14 @@ func (k Keeper) HandleConsumerDoubleVoting(ctx sdk.Context, evidence *tmtypes.Du types.NewConsumerConsAddress(sdk.ConsAddress(evidence.VoteA.ValidatorAddress.Bytes())), ) - // get the consumer chain public key assigned to the validator - consuPubkey, ok := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) - if !ok { - return fmt.Errorf("cannot find public key for validator %s and consumer chain %s", providerAddr.String(), chainID) + // get validator pubkey used on the consumer chain + pubkey, err := k.getValidatorPubkeyOnConsumer(ctx, chainID, providerAddr) + if err != nil { + return err } // verifies the double voting evidence using the consumer chain public key - if err := k.VerifyDoubleVotingEvidence(ctx, *evidence, chainID, consuPubkey); err != nil { + if err := k.VerifyDoubleVotingEvidence(ctx, *evidence, chainID, pubkey); err != nil { return err } @@ -39,7 +40,7 @@ func (k Keeper) HandleConsumerDoubleVoting(ctx sdk.Context, evidence *tmtypes.Du k.Logger(ctx).Info( "confirmed equivocation", - "byzantine validator address", providerAddr, + "byzantine validator address", providerAddr.String(), ) return nil @@ -51,8 +52,12 @@ func (k Keeper) VerifyDoubleVotingEvidence( ctx sdk.Context, evidence tmtypes.DuplicateVoteEvidence, chainID string, - pubkey tmprotocrypto.PublicKey, + pubkey cryptotypes.PubKey, ) error { + if pubkey == nil { + return fmt.Errorf("validator public key cannot be empty") + } + // Note that since we're only jailing validators for double voting on a consumer chain, // the age of the evidence is irrelevant and therefore isn't checked. @@ -89,21 +94,45 @@ func (k Keeper) VerifyDoubleVotingEvidence( ) } - pk, err := cryptocodec.FromTmProtoPublicKey(pubkey) - if err != nil { - return err - } - va := evidence.VoteA.ToProto() vb := evidence.VoteB.ToProto() // signatures must be valid - if !pk.VerifySignature(tmtypes.VoteSignBytes(chainID, va), evidence.VoteA.Signature) { + if !pubkey.VerifySignature(tmtypes.VoteSignBytes(chainID, va), evidence.VoteA.Signature) { return fmt.Errorf("verifying VoteA: %w", tmtypes.ErrVoteInvalidSignature) } - if !pk.VerifySignature(tmtypes.VoteSignBytes(chainID, vb), evidence.VoteB.Signature) { + if !pubkey.VerifySignature(tmtypes.VoteSignBytes(chainID, vb), evidence.VoteB.Signature) { return fmt.Errorf("verifying VoteB: %w", tmtypes.ErrVoteInvalidSignature) } return nil } + +// getValidatorPubkeyOnConsumer returns the public key a validator used on a given consumer chain. +// Note that it can either be an assigned public key or the same public key the validator uses +// on the provider chain. +func (k Keeper) getValidatorPubkeyOnConsumer( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) (pubkey cryptotypes.PubKey, err error) { + tmPK, ok := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) + if ok { + pubkey, err = cryptocodec.FromTmProtoPublicKey(tmPK) + if err != nil { + return + } + } else { + val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) + if !ok { + err = fmt.Errorf("cannot find validator %s", providerAddr.String()) + return + } + pubkey, err = val.ConsPubKey() + if err != nil { + return + } + } + + return +} diff --git a/x/ccv/provider/keeper/double_vote_test.go b/x/ccv/provider/keeper/double_vote_test.go index 9bb789c381..cc8280b14d 100644 --- a/x/ccv/provider/keeper/double_vote_test.go +++ b/x/ccv/provider/keeper/double_vote_test.go @@ -4,11 +4,11 @@ import ( "testing" "time" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" testutil "github.com/cosmos/interchain-security/v2/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v2/testutil/keeper" "github.com/stretchr/testify/require" - tmencoding "github.com/tendermint/tendermint/crypto/encoding" - tmprotocrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" tmtypes "github.com/tendermint/tendermint/types" ) @@ -31,17 +31,17 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { ctx = ctx.WithBlockTime(time.Now()) - valPubkey1, err := tmencoding.PubKeyToProto(val1.PubKey) + valPubkey1, err := cryptocodec.FromTmPubKeyInterface(val1.PubKey) require.NoError(t, err) - valPubkey2, err := tmencoding.PubKeyToProto(val2.PubKey) + valPubkey2, err := cryptocodec.FromTmPubKeyInterface(val2.PubKey) require.NoError(t, err) testCases := []struct { name string votes []*tmtypes.Vote chainID string - pubkey tmprotocrypto.PublicKey + pubkey cryptotypes.PubKey expPass bool }{ { @@ -209,7 +209,7 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { ), }, chainID, - tmprotocrypto.PublicKey{}, + nil, false, }, { diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index e5dbb16697..b6741a0a43 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -210,8 +210,16 @@ func (msg MsgSubmitConsumerDoubleVoting) ValidateBasic() error { return fmt.Errorf("double voting evidence cannot be nil") } - if msg.InfractionBlockHeader.Header == nil { - return fmt.Errorf("infraction header cannot be nil") + if msg.InfractionBlockHeader == nil { + return fmt.Errorf("infraction block header cannot be nil") + } + + if msg.InfractionBlockHeader.SignedHeader == nil { + return fmt.Errorf("signed header in infraction block header cannot be nil") + } + + if msg.InfractionBlockHeader.SignedHeader.Header == nil { + return fmt.Errorf("invalid signed header in infraction block header, 'SignedHeader.Header' is nil") } return nil From eb6a0790ca9b220c1881e6f64c8e5d9dabcd1667 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 6 Sep 2023 16:50:24 +0200 Subject: [PATCH 07/40] fix: verify equivocation using validator pubkey in `SubmitConsumerDoubleVoting` msg (#1264) * verify dv evidence using malicious validator pubkey in infraction block header * nits * nits --- tests/integration/double_vote.go | 25 ++++++++++++ x/ccv/provider/keeper/double_vote.go | 57 +++++++--------------------- x/ccv/provider/keeper/msg_server.go | 31 ++++++++++++++- 3 files changed, 67 insertions(+), 46 deletions(-) diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index 184f7604bb..c79b92115e 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -1,9 +1,11 @@ package integration import ( + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" testutil "github.com/cosmos/interchain-security/v2/testutil/crypto" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" + "github.com/tendermint/tendermint/crypto" tmtypes "github.com/tendermint/tendermint/types" ) @@ -78,6 +80,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { name string ev *tmtypes.DuplicateVoteEvidence chainID string + pubkey crypto.PubKey expPass bool }{ { @@ -90,6 +93,20 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { Timestamp: s.consumerCtx().BlockTime(), }, "chainID", + consuVal.PubKey, + false, + }, + { + "wrong public key - shouldn't pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: consuVote, + VoteB: consuVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + provVal.PubKey, false, }, { @@ -103,6 +120,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { Timestamp: s.consumerCtx().BlockTime(), }, s.consumerChain.ChainID, + consuVal.PubKey, false, }, { @@ -119,6 +137,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { Timestamp: s.consumerCtx().BlockTime(), }, s.consumerChain.ChainID, + consuVal.PubKey, true, }, { @@ -132,6 +151,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { Timestamp: s.consumerCtx().BlockTime(), }, s.consumerChain.ChainID, + provVal.PubKey, true, }, } @@ -151,10 +171,15 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { s.providerApp.GetProviderKeeper().DeleteKeyAssignments(provCtx, s.consumerChain.ChainID) } + // convert validator public key + pk, err := cryptocodec.FromTmPubKeyInterface(tc.pubkey) + s.Require().NoError(err) + err = s.providerApp.GetProviderKeeper().HandleConsumerDoubleVoting( provCtx, tc.ev, tc.chainID, + pk, ) if tc.expPass { diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 171be6b250..ee5f716ef8 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -14,9 +13,19 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) -// HandleConsumerDoubleVoting verifies a double voting evidence for a given a consumer chain and, -// if successful, executes the jailing of the malicious validator. -func (k Keeper) HandleConsumerDoubleVoting(ctx sdk.Context, evidence *tmtypes.DuplicateVoteEvidence, chainID string) error { +// HandleConsumerDoubleVoting verifies a double voting evidence for a given a consumer chain ID +// and a public key and, if successful, executes the jailing of the malicious validator. +func (k Keeper) HandleConsumerDoubleVoting( + ctx sdk.Context, + evidence *tmtypes.DuplicateVoteEvidence, + chainID string, + pubkey cryptotypes.PubKey, +) error { + // verifies the double voting evidence using the consumer chain public key + if err := k.VerifyDoubleVotingEvidence(ctx, *evidence, chainID, pubkey); err != nil { + return err + } + // get the validator's consensus address on the provider providerAddr := k.GetProviderAddrFromConsumerAddr( ctx, @@ -24,17 +33,6 @@ func (k Keeper) HandleConsumerDoubleVoting(ctx sdk.Context, evidence *tmtypes.Du types.NewConsumerConsAddress(sdk.ConsAddress(evidence.VoteA.ValidatorAddress.Bytes())), ) - // get validator pubkey used on the consumer chain - pubkey, err := k.getValidatorPubkeyOnConsumer(ctx, chainID, providerAddr) - if err != nil { - return err - } - - // verifies the double voting evidence using the consumer chain public key - if err := k.VerifyDoubleVotingEvidence(ctx, *evidence, chainID, pubkey); err != nil { - return err - } - // execute the jailing k.JailValidator(ctx, providerAddr) @@ -107,32 +105,3 @@ func (k Keeper) VerifyDoubleVotingEvidence( return nil } - -// getValidatorPubkeyOnConsumer returns the public key a validator used on a given consumer chain. -// Note that it can either be an assigned public key or the same public key the validator uses -// on the provider chain. -func (k Keeper) getValidatorPubkeyOnConsumer( - ctx sdk.Context, - chainID string, - providerAddr types.ProviderConsAddress, -) (pubkey cryptotypes.PubKey, err error) { - tmPK, ok := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) - if ok { - pubkey, err = cryptocodec.FromTmProtoPublicKey(tmPK) - if err != nil { - return - } - } else { - val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) - if !ok { - err = fmt.Errorf("cannot find validator %s", providerAddr.String()) - return - } - pubkey, err = val.ConsPubKey() - if err != nil { - return - } - } - - return -} diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index b6b96bb010..9293859b02 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -4,6 +4,8 @@ import ( "context" "encoding/base64" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -156,8 +158,33 @@ func (k msgServer) SubmitConsumerDoubleVoting(goCtx context.Context, msg *types. return nil, err } - if err := k.Keeper.HandleConsumerDoubleVoting(ctx, evidence, msg.InfractionBlockHeader.Header.ChainID); err != nil { - return &types.MsgSubmitConsumerDoubleVotingResponse{}, err + // parse the validator set of the infraction block header in order + // to find the public key of the validator who double voted + + // get validator set + valset, err := tmtypes.ValidatorSetFromProto(msg.InfractionBlockHeader.ValidatorSet) + if err != nil { + return nil, err + } + + // look for the malicious validator in the validator set + _, validator := valset.GetByAddress(evidence.VoteA.ValidatorAddress) + if validator == nil { + return nil, errorsmod.Wrapf( + ccvtypes.ErrInvalidEvidence, + "misbehaving validator %s cannot be found in the infraction block header validator set", + evidence.VoteA.ValidatorAddress) + } + + pubkey, err := cryptocodec.FromTmPubKeyInterface(validator.PubKey) + if err != nil { + return nil, err + } + + // handle the double voting evidence using the chain ID of the infraction block header + // and the malicious validator's public key + if err := k.Keeper.HandleConsumerDoubleVoting(ctx, evidence, msg.InfractionBlockHeader.Header.ChainID, pubkey); err != nil { + return nil, err } ctx.EventManager().EmitEvents(sdk.Events{ From 98af9c0144bb3ceb84acc195dcfbf16c2afb9a23 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 8 Sep 2023 16:13:13 +0200 Subject: [PATCH 08/40] refactor: update the E2E tests to work with Hermes relayer v1.6.0 (#1278) * save changes * fix hermes config * fist successful run * nit * nits * nits * doc and nits * lint --- tests/e2e/actions.go | 24 ++++++++++- tests/e2e/actions_consumer_misbehaviour.go | 7 ++- tests/e2e/main.go | 2 + tests/e2e/state.go | 48 +++++++++++++++++++++ tests/e2e/steps_consumer_misbehaviour.go | 50 ++++++++++++++++------ tests/e2e/testnet-scripts/fork-consumer.sh | 6 ++- 6 files changed, 119 insertions(+), 18 deletions(-) diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 9c7d41af17..0c47b452e2 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -718,7 +718,7 @@ rpc_addr = "%s" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "14days" -websocket_addr = "%s" +event_source = { mode = "push", url = "%s", batch_delay = "50ms" } ccv_consumer_chain = %v [chains.gas_price] @@ -1844,3 +1844,25 @@ func (tr TestRun) GetPathNameForGorelayer(chainA, chainB chainID) string { return pathName } + +// Run an instance of the Hermes relayer using the "evidence" command, +// which detects evidences committed to the blocks of a consumer chain. +// Each infraction detected is reported to the provider chain using +// either a SubmitConsumerDoubleVoting or a SubmitConsumerMisbehaviour message. +type detectConsumerEvidenceAction struct { + chain chainID +} + +func (tr TestRun) detectConsumerEvidence( + action detectConsumerEvidenceAction, + verbose bool, +) { + chainConfig := tr.chainConfigs[action.chain] + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + bz, err := exec.Command("docker", "exec", "-d", tr.containerConfig.instanceName, + "hermes", "evidence", "--chain", string(chainConfig.chainId)).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + tr.waitBlocks("provi", 10, 2*time.Minute) +} diff --git a/tests/e2e/actions_consumer_misbehaviour.go b/tests/e2e/actions_consumer_misbehaviour.go index 84eb93152c..0da2f9e56f 100644 --- a/tests/e2e/actions_consumer_misbehaviour.go +++ b/tests/e2e/actions_consumer_misbehaviour.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os/exec" + "strconv" "time" ) @@ -61,6 +62,7 @@ func (tr TestRun) forkConsumerChain(action forkConsumerChainAction, verbose bool } type updateLightClientAction struct { + chain chainID hostChain chainID relayerConfig string clientID string @@ -70,7 +72,9 @@ func (tr TestRun) updateLightClient( action updateLightClientAction, verbose bool, ) { - // hermes clear packets ibc0 transfer channel-13 + // retrieve a trusted height of the consumer light client + trustedHeight := tr.getTrustedHeight(action.hostChain, action.clientID, 2) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", "--config", action.relayerConfig, @@ -78,6 +82,7 @@ func (tr TestRun) updateLightClient( "client", "--client", action.clientID, "--host-chain", string(action.hostChain), + "--trusted-height", strconv.Itoa(int(trustedHeight.RevisionHeight)), ) if verbose { log.Println("updateLightClientAction cmd:", cmd.String()) diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 406a015e63..84a06eeab9 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -180,6 +180,8 @@ func (tr *TestRun) runStep(step Step, verbose bool) { tr.updateLightClient(action, verbose) case assertChainIsHaltedAction: tr.assertChainIsHalted(action, verbose) + case detectConsumerEvidenceAction: + tr.detectConsumerEvidence(action, verbose) default: log.Fatalf("unknown action in testRun %s: %#v", tr.name, action) } diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 8d9ba9a81e..d0d908a494 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "fmt" "log" "os/exec" @@ -776,3 +777,50 @@ func (tr TestRun) getClientFrozenHeight(chain chainID, clientID string) clientty return clienttypes.Height{RevisionHeight: uint64(revHeight), RevisionNumber: uint64(revNumber)} } + +func (tr TestRun) getTrustedHeight( + chain chainID, + clientID string, + index int, +) clienttypes.Height { + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + configureNodeCmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", + "--json", "query", "client", "consensus", "--chain", string(chain), + `--client`, clientID, + ) + + cmdReader, err := configureNodeCmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + + configureNodeCmd.Stderr = configureNodeCmd.Stdout + + if err := configureNodeCmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + var trustedHeight gjson.Result + // iterate on the relayer's response + // and parse the the command "result" + for scanner.Scan() { + out := scanner.Text() + if len(gjson.Get(out, "result").Array()) > 0 { + trustedHeight = gjson.Get(out, "result").Array()[index] + break + } + } + + revHeight, err := strconv.Atoi(trustedHeight.Get("revision_height").String()) + if err != nil { + log.Fatal(err) + } + + revNumber, err := strconv.Atoi(trustedHeight.Get("revision_number").String()) + if err != nil { + log.Fatal(err) + } + return clienttypes.Height{RevisionHeight: uint64(revHeight), RevisionNumber: uint64(revNumber)} +} diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index 6401b5f638..53cfb78fae 100644 --- a/tests/e2e/steps_consumer_misbehaviour.go +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -213,43 +213,65 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { }, state: State{}, }, + // start relayer to detect IBC misbehaviour { - // start relayer to detect ICS misbehaviour action: startRelayerAction{}, state: State{}, }, + // detect the ICS misbehaviour + // and jail alice on the provider + { + action: detectConsumerEvidenceAction{ + chain: chainID(consumerName), + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 20, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 20, + }, + }, + }, + }, { // update the fork consumer client to create a light client attack // which should trigger a ICS misbehaviour message action: updateLightClientAction{ + chain: chainID(consumerName), + clientID: consumerClientID, hostChain: chainID("provi"), relayerConfig: forkRelayerConfig, // this relayer config uses the "forked" consumer - clientID: consumerClientID, }, state: State{ chainID("provi"): ChainState{ - // validator should be jailed on the provider + // alice should be jailed on the provider ValPowers: &map[validatorID]uint{ validatorID("alice"): 0, validatorID("bob"): 20, }, - // The consumer light client should not be frozen + // The consumer light client should be frozen on the provider ClientsFrozenHeights: &map[string]clienttypes.Height{ - "07-tendermint-0": { + consumerClientID: { RevisionNumber: 0, - RevisionHeight: 0, + RevisionHeight: 1, }, }, }, + chainID(consumerName): ChainState{ + // consumer should not have learned the jailing of alice + // since its light client is frozen on the provider + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 20, + }, + }, }, }, - // we expect the consumer chain to be halted since the last VSC packet should - // have updated the alice validator power to 0. - { - action: assertChainIsHaltedAction{ - chain: chainID("consu"), - }, - state: State{}, - }, } } diff --git a/tests/e2e/testnet-scripts/fork-consumer.sh b/tests/e2e/testnet-scripts/fork-consumer.sh index 0bf96fcb79..7c12438b71 100644 --- a/tests/e2e/testnet-scripts/fork-consumer.sh +++ b/tests/e2e/testnet-scripts/fork-consumer.sh @@ -63,7 +63,7 @@ rpc_addr = "http://$CONS_CHAIN_PREFIX.252:26658" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -websocket_addr = "ws://$CONS_CHAIN_PREFIX.252:26658/websocket" +event_source = { mode = 'push', url = 'ws://$CONS_CHAIN_PREFIX.252:26658/websocket' , batch_delay = '50ms' } [chains.gas_price] denom = "stake" @@ -85,7 +85,9 @@ rpc_addr = "http://$PROV_CHAIN_PREFIX.4:26658" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -websocket_addr = "ws://$PROV_CHAIN_PREFIX.4:26658/websocket" +event_source = { mode = 'push', url = 'ws://$PROV_CHAIN_PREFIX.4:26658/websocket' , batch_delay = '50ms' } + + [chains.gas_price] denom = "stake" From c881a1aad37f2f8041c913468602edaf69fef9bf Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 11 Sep 2023 18:21:15 +0200 Subject: [PATCH 09/40] test: add E2E tests for double voting evidence handling (#1256) * fix double voting cli * add double-signing e2e test * refortmat e2e double voting test * godoc, revert unwanted changes * nit * verify dv evidence using malicious validator pubkey in infraction block header * save changes * fix hermes config * fist successful run * nit * nits * nits * doc and nits * lint * refactor * typo * change hermes docker image * nits * Update tests/e2e/steps.go Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * address PR comments * nits --------- Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> --- tests/e2e/main.go | 2 + tests/e2e/steps.go | 10 ++++- tests/e2e/steps_double_sign.go | 76 ++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 84a06eeab9..ac9becdd5e 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -63,7 +63,9 @@ func main() { {DemocracyTestRun(false), rewardDenomConsumerSteps}, {SlashThrottleTestRun(), slashThrottleSteps}, {ConsumerMisbehaviourTestRun(), consumerMisbehaviourSteps}, + {DefaultTestRun(), consumerDoubleSignSteps}, } + if includeMultiConsumer != nil && *includeMultiConsumer { testRuns = append(testRuns, testRunWithSteps{MultiConsumerTestRun(), multipleConsumers}) } diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 78a56654e6..fb506eff5f 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -91,6 +91,14 @@ var changeoverSteps = concatSteps( var consumerMisbehaviourSteps = concatSteps( // start provider and consumer chain stepsStartChainsWithSoftOptOut("consu"), - // make consumer validator to misbehave and get jail + // make a consumer validator to misbehave and get jailed stepsCauseConsumerMisbehaviour("consu"), ) + +var consumerDoubleSignSteps = concatSteps( + // start provider and consumer chain + stepsStartChains([]string{"consu"}, false), + + // make a consumer validator double sign and get jailed + stepsCauseDoubleSignOnConsumer("consu", "provi"), +) diff --git a/tests/e2e/steps_double_sign.go b/tests/e2e/steps_double_sign.go index c007fa5c1c..dcce236b46 100644 --- a/tests/e2e/steps_double_sign.go +++ b/tests/e2e/steps_double_sign.go @@ -128,3 +128,79 @@ func stepsDoubleSignOnProviderAndConsumer(consumerName string) []Step { }, } } + +// Steps that make bob double sign on the consumer +func stepsCauseDoubleSignOnConsumer(consumerName, providerName string) []Step { + return []Step{ + { + action: doublesignSlashAction{ + chain: chainID(consumerName), + validator: validatorID("bob"), + }, + state: State{ + chainID(providerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + // detect the double voting infraction + // and jail bob on the provider + { + action: detectConsumerEvidenceAction{ + chain: chainID(consumerName), + }, + state: State{ + chainID(providerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 0, + validatorID("carol"): 500, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + // consumer learns about the jailing + { + action: relayPacketsAction{ + chainA: chainID(providerName), + chainB: chainID(consumerName), + port: "provider", + channel: 0, + }, + state: State{ + chainID(providerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 0, + validatorID("carol"): 500, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 0, + validatorID("carol"): 500, + }, + }, + }, + }, + } +} From 494c4d22a2dc5265e0572f34d7218349271dd052 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 14:43:15 -0700 Subject: [PATCH 10/40] feat!: provider proposal for changing reward denoms (backport #1280) (#1291) * feat!: provider proposal for changing reward denoms (#1280) * new provider prop type * add methods and tests for new prop, update docs * remove old tx, fix tests * e2e handling * fix command type * boilerplate * fix e2e tests * Update CHANGELOG.md * lint * validate denoms * Update proposal.go * rm msg string * fix tests * rm chain in change denom action * lint * test for invalid denom * events for both add and remove * Update proposal_test.go (cherry picked from commit 48a21869e9a56cafff53b5c51077a96d1e3c4926) # Conflicts: # CHANGELOG.md # app/provider/app.go # proto/interchain_security/ccv/provider/v1/provider.proto # proto/interchain_security/ccv/provider/v1/tx.proto # tests/e2e/actions.go # tests/integration/distribution.go # x/ccv/provider/client/cli/tx.go # x/ccv/provider/client/proposal_handler.go # x/ccv/provider/keeper/distribution.go # x/ccv/provider/keeper/distribution_test.go # x/ccv/provider/proposal_handler_test.go # x/ccv/provider/types/codec.go # x/ccv/provider/types/proposal.go # x/ccv/provider/types/provider.pb.go # x/ccv/provider/types/tx.pb.go * fix conflicts * fix rest handler * Update CHANGELOG.md * rm uneeded tx proto --------- Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> --- CHANGELOG.md | 4 + app/provider/app.go | 1 + docs/docs/features/proposals.md | 17 + .../ccv/provider/v1/provider.proto | 13 + .../ccv/provider/v1/tx.proto | 15 - tests/e2e/actions.go | 59 +- tests/e2e/main.go | 4 +- tests/e2e/steps_democracy.go | 26 +- tests/e2e/steps_reward_denom.go | 26 +- tests/integration/distribution.go | 34 +- x/ccv/provider/client/cli/tx.go | 38 -- x/ccv/provider/client/proposal_handler.go | 128 +++- x/ccv/provider/handler.go | 3 - x/ccv/provider/keeper/distribution.go | 25 +- x/ccv/provider/keeper/distribution_test.go | 48 -- x/ccv/provider/keeper/msg_server.go | 21 - x/ccv/provider/keeper/proposal.go | 28 + x/ccv/provider/proposal_handler.go | 2 + x/ccv/provider/proposal_handler_test.go | 24 +- x/ccv/provider/types/codec.go | 6 +- x/ccv/provider/types/msg.go | 48 +- x/ccv/provider/types/proposal.go | 65 +- x/ccv/provider/types/proposal_test.go | 49 ++ x/ccv/provider/types/provider.pb.go | 575 ++++++++++++++---- x/ccv/provider/types/tx.pb.go | 423 +------------ x/ccv/types/events.go | 18 +- 26 files changed, 903 insertions(+), 797 deletions(-) delete mode 100644 x/ccv/provider/keeper/distribution_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d1f7a4e1f..dfe47bfd21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Add an entry to the unreleased section whenever merging a PR to main that is not targeted at a specific release. These entries will eventually be included in a release. +## v2.1.0-lsm-provider + +* (feature!) [#1280](https://github.com/cosmos/interchain-security/pull/1280) provider proposal for changing reward denoms + ## v2.0.0-lsm Date: August 18th, 2023 diff --git a/app/provider/app.go b/app/provider/app.go index 94719cf2c6..0555cf4aa5 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -140,6 +140,7 @@ var ( ibcproviderclient.ConsumerAdditionProposalHandler, ibcproviderclient.ConsumerRemovalProposalHandler, ibcproviderclient.EquivocationProposalHandler, + ibcproviderclient.ChangeRewardDenomsProposalHandler, ), params.AppModuleBasic{}, crisis.AppModuleBasic{}, diff --git a/docs/docs/features/proposals.md b/docs/docs/features/proposals.md index b68fd343d6..66b9d3a751 100644 --- a/docs/docs/features/proposals.md +++ b/docs/docs/features/proposals.md @@ -105,6 +105,23 @@ Minimal example: } ``` +## ChangeRewardDenomProposal +:::tip +`ChangeRewardDenomProposal` will only be accepted on the provider chain if at least one of the denomsToAdd or denomsToRemove fields is populated with at least one denom. Also, a denom cannot be repeated in both sets. +::: + +Proposal type used to mutate the set of denoms accepted by the provider as rewards. + +Minimal example: +```js +{ + "title": "Add untrn as a reward denom", + "description": "Here is more information about the proposal", + "denomsToAdd": ["untrn"], + "denomsToRemove": [] +} +``` + ### Notes When submitting equivocation evidence through an `EquivocationProposal` please take note that you need to use the consensus address (`valcons`) of the offending validator on the **provider chain**. Besides that, the `height` and the `time` fields should be mapped to the **provider chain** to avoid your evidence being rejected. diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index d190b3a6ca..c024d7eaef 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -95,6 +95,19 @@ message EquivocationProposal { repeated cosmos.evidence.v1beta1.Equivocation equivocations = 3; } +// ChangeRewardDenomsProposal is a governance proposal on the provider chain to +// mutate the set of denoms accepted by the provider as rewards. +message ChangeRewardDenomsProposal { + // the title of the proposal + string title = 1; + // the description of the proposal + string description = 2; + // the list of consumer reward denoms to add + repeated string denoms_to_add = 3; + // the list of consumer reward denoms to remove + repeated string denoms_to_remove = 4; +} + // A persisted queue entry indicating that a slash packet data instance needs to be handled. // This type belongs in the "global" queue, to coordinate slash packet handling times between consumers. message GlobalSlashEntry { diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 705e155317..528065180a 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -11,7 +11,6 @@ import "google/protobuf/any.proto"; // Msg defines the Msg service. service Msg { rpc AssignConsumerKey(MsgAssignConsumerKey) returns (MsgAssignConsumerKeyResponse); - rpc RegisterConsumerRewardDenom(MsgRegisterConsumerRewardDenom) returns (MsgRegisterConsumerRewardDenomResponse); } message MsgAssignConsumerKey { @@ -29,17 +28,3 @@ message MsgAssignConsumerKey { } message MsgAssignConsumerKeyResponse {} - -// MsgRegisterConsumerRewardDenom allows an account to register -// a consumer reward denom, i.e., add it to the list of denoms -// accepted by the provider as rewards. -message MsgRegisterConsumerRewardDenom { - option (gogoproto.equal) = false; - option (gogoproto.goproto_getters) = false; - - string denom = 1; - string depositor = 2; -} - -// MsgRegisterConsumerRewardDenomResponse defines the Msg/RegisterConsumerRewardDenom response type. -message MsgRegisterConsumerRewardDenomResponse {} \ No newline at end of file diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 702a82d5cd..77b56b6cbe 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -1675,34 +1675,63 @@ func (tr TestRun) registerRepresentative( wg.Wait() } -type registerConsumerRewardDenomAction struct { - chain chainID - from validatorID - denom string +type submitChangeRewardDenomsProposalAction struct { + denom string + deposit uint + from validatorID } -func (tr TestRun) registerConsumerRewardDenom(action registerConsumerRewardDenomAction, verbose bool) { +func (tr TestRun) submitChangeRewardDenomsProposal(action submitChangeRewardDenomsProposalAction, verbose bool) { + providerChain := tr.chainConfigs[chainID("provi")] + + prop := client.ChangeRewardDenomsProposalJSON{ + Summary: "Change reward denoms", + ChangeRewardDenomsProposal: types.ChangeRewardDenomsProposal{ + Title: "Change reward denoms", + Description: "Change reward denoms", + DenomsToAdd: []string{action.denom}, + DenomsToRemove: []string{"stake"}, + }, + 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, tr.chainConfigs[action.chain].binaryName, - "tx", "provider", "register-consumer-reward-denom", action.denom, + bz, err = exec.Command("docker", "exec", tr.containerConfig.instanceName, + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/change-reward-denoms-proposal.json")).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + // CHANGE REWARDS DENOM PROPOSAL + bz, err = exec.Command("docker", "exec", tr.containerConfig.instanceName, providerChain.binaryName, + "tx", "gov", "submit-legacy-proposal", "change-reward-denoms", "/change-reward-denoms-proposal.json", `--from`, `validator`+fmt.Sprint(action.from), - `--chain-id`, string(action.chain), - `--home`, tr.getValidatorHome(action.chain, action.from), - `--node`, tr.getValidatorNode(action.chain, 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 verbose { - fmt.Println("redelegate cmd:", string(bz)) - } - if err != nil { log.Fatal(err, "\n", string(bz)) } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(chainID("provi"), 2, 30*time.Second) } // Creates an additional node on selected chain diff --git a/tests/e2e/main.go b/tests/e2e/main.go index bf89f73853..6e6c3b6cac 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -173,8 +173,8 @@ func (tr *TestRun) runStep(step Step, verbose bool) { tr.waitForSlashThrottleDequeue(action, verbose) case startRelayerAction: tr.startRelayer(action, verbose) - case registerConsumerRewardDenomAction: - tr.registerConsumerRewardDenom(action, verbose) + case submitChangeRewardDenomsProposalAction: + tr.submitChangeRewardDenomsProposal(action, verbose) default: log.Fatalf("unknown action in testRun %s: %#v", tr.name, action) } diff --git a/tests/e2e/steps_democracy.go b/tests/e2e/steps_democracy.go index 686fbf1c49..5b48afdef4 100644 --- a/tests/e2e/steps_democracy.go +++ b/tests/e2e/steps_democracy.go @@ -132,19 +132,29 @@ func stepsDemocracy(consumerName string) []Step { }, }, { - action: registerConsumerRewardDenomAction{ - chain: chainID("provi"), - from: validatorID("bob"), - denom: consumerRewardDenom, + action: submitChangeRewardDenomsProposalAction{ + denom: consumerRewardDenom, + deposit: 10000001, + from: validatorID("bob"), + }, + state: State{ + chainID("provi"): ChainState{ + // Denom not yet registered, gov prop needs to pass first + RegisteredConsumerRewardDenoms: &[]string{}, + }, + }, + }, + { + 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{ // Check that the denom is registered on provider chain RegisteredConsumerRewardDenoms: &[]string{consumerRewardDenom}, - ValBalances: &map[validatorID]uint{ - // make sure that bob's account was debited - validatorID("bob"): 9490000000, - }, }, }, }, diff --git a/tests/e2e/steps_reward_denom.go b/tests/e2e/steps_reward_denom.go index dc10ccb7c6..a3a16f10e2 100644 --- a/tests/e2e/steps_reward_denom.go +++ b/tests/e2e/steps_reward_denom.go @@ -130,19 +130,29 @@ func stepsRewardDenomConsumer(consumerName string) []Step { }, }, { - action: registerConsumerRewardDenomAction{ - chain: chainID("provi"), - from: validatorID("bob"), - denom: "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9", + action: submitChangeRewardDenomsProposalAction{ + denom: "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9", + deposit: 10000001, + from: validatorID("bob"), + }, + state: State{ + chainID("provi"): ChainState{ + // Denom not yet registered, gov prop needs to pass first + RegisteredConsumerRewardDenoms: &[]string{}, + }, + }, + }, + { + 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{ // Check that the denom is registered on provider chain RegisteredConsumerRewardDenoms: &[]string{"ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9"}, - ValBalances: &map[validatorID]uint{ - // make sure that bob's account was debited - validatorID("bob"): 9490000000, - }, }, }, }, diff --git a/tests/integration/distribution.go b/tests/integration/distribution.go index b9e3b9975e..2423b432f1 100644 --- a/tests/integration/distribution.go +++ b/tests/integration/distribution.go @@ -5,8 +5,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" + consumertypes "github.com/cosmos/interchain-security/v2/x/ccv/consumer/types" providertypes "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" ccv "github.com/cosmos/interchain-security/v2/x/ccv/types" @@ -92,41 +92,13 @@ func (s *CCVTestSuite) TestRewardsDistribution() { // Check that the coins got into the ConsumerRewardsPool s.Require().True(rewardCoins[ibcCoinIndex].Amount.Equal(providerExpectedRewards[0].Amount)) - // Attempt to register the consumer reward denom, but fail because the account has no coins - - // Get the balance of delAddr to send it out - senderCoins := providerBankKeeper.GetAllBalances(s.providerCtx(), delAddr) - - // Send the coins to the governance module just to have a place to send them - err = providerBankKeeper.SendCoinsFromAccountToModule(s.providerCtx(), delAddr, govtypes.ModuleName, senderCoins) - s.Require().NoError(err) - - // Attempt to register the consumer reward denom, but fail because the account has no coins - err = s.providerApp.GetProviderKeeper().RegisterConsumerRewardDenom(s.providerCtx(), rewardCoins[ibcCoinIndex].Denom, delAddr) - s.Require().Error(err) - // Advance a block and check that the coins are still in the ConsumerRewardsPool s.providerChain.NextBlock() rewardCoins = providerBankKeeper.GetAllBalances(s.providerCtx(), rewardPool) s.Require().True(rewardCoins[ibcCoinIndex].Amount.Equal(providerExpectedRewards[0].Amount)) - // Successfully register the consumer reward denom this time - - // Send the coins back to the delAddr - err = providerBankKeeper.SendCoinsFromModuleToAccount(s.providerCtx(), govtypes.ModuleName, delAddr, senderCoins) - s.Require().NoError(err) - - // log the sender's coins - senderCoins1 := providerBankKeeper.GetAllBalances(s.providerCtx(), delAddr) - - // Register the consumer reward denom - err = s.providerApp.GetProviderKeeper().RegisterConsumerRewardDenom(s.providerCtx(), rewardCoins[ibcCoinIndex].Denom, delAddr) - s.Require().NoError(err) - - // Check that the delAddr has the right amount less money in it after paying the fee - senderCoins2 := providerBankKeeper.GetAllBalances(s.providerCtx(), delAddr) - consumerRewardDenomRegistrationFee := s.providerApp.GetProviderKeeper().GetConsumerRewardDenomRegistrationFee(s.providerCtx()) - s.Require().Equal(senderCoins1.Sub(senderCoins2), sdk.NewCoins(consumerRewardDenomRegistrationFee)) + // Set the consumer reward denom. This would be done by a governance proposal in prod + s.providerApp.GetProviderKeeper().SetConsumerRewardDenom(s.providerCtx(), rewardCoins[ibcCoinIndex].Denom) s.providerChain.NextBlock() diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 73b1df34c3..724e388e84 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -2,15 +2,12 @@ package cli import ( "fmt" - "strings" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/version" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" ) @@ -26,7 +23,6 @@ func GetTxCmd() *cobra.Command { } cmd.AddCommand(NewAssignConsumerKeyCmd()) - cmd.AddCommand(NewRegisterConsumerRewardDenomCmd()) return cmd } @@ -65,37 +61,3 @@ func NewAssignConsumerKeyCmd() *cobra.Command { return cmd } - -func NewRegisterConsumerRewardDenomCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "register-consumer-reward-denom [denom]", - Args: cobra.ExactArgs(1), - Short: "Registers a denom that can be sent from consumer chains to all validators and delegators as a reward", - Long: strings.TrimSpace( - fmt.Sprintf(`Registers a denom that can be sent from consumer chains to all validators and delegators as a reward. - -Costs a fee, which is specified in genesis.json under the "consumer_reward_denom_fee" key. Will fail if the sending account has an insufficient balance. - -Example: -$ %s tx provider register-consumer-reward-denom untrn --from mykey -`, - version.AppName, - ), - ), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - depositorAddr := clientCtx.GetFromAddress() - - msg := types.NewMsgRegisterConsumerRewardDenom(args[0], depositorAddr) - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - - return cmd -} diff --git a/x/ccv/provider/client/proposal_handler.go b/x/ccv/provider/client/proposal_handler.go index 76491b86d2..dd33b83f15 100644 --- a/x/ccv/provider/client/proposal_handler.go +++ b/x/ccv/provider/client/proposal_handler.go @@ -23,9 +23,10 @@ import ( ) var ( - ConsumerAdditionProposalHandler = govclient.NewProposalHandler(SubmitConsumerAdditionPropTxCmd, ConsumerAdditionProposalRESTHandler) - ConsumerRemovalProposalHandler = govclient.NewProposalHandler(SubmitConsumerRemovalProposalTxCmd, ConsumerRemovalProposalRESTHandler) - EquivocationProposalHandler = govclient.NewProposalHandler(SubmitEquivocationProposalTxCmd, EquivocationProposalRESTHandler) + ConsumerAdditionProposalHandler = govclient.NewProposalHandler(SubmitConsumerAdditionPropTxCmd, ConsumerAdditionProposalRESTHandler) + ConsumerRemovalProposalHandler = govclient.NewProposalHandler(SubmitConsumerRemovalProposalTxCmd, ConsumerRemovalProposalRESTHandler) + EquivocationProposalHandler = govclient.NewProposalHandler(SubmitEquivocationProposalTxCmd, EquivocationProposalRESTHandler) + ChangeRewardDenomsProposalHandler = govclient.NewProposalHandler(SubmitChangeRewardDenomsProposalTxCmd, ChangeRewardDenomsProposalRESTHandler) ) // SubmitConsumerAdditionPropTxCmd returns a CLI command handler for submitting @@ -215,6 +216,58 @@ Where proposal.json contains: } } +// SubmitChangeRewardDenomsProposalTxCmd returns a CLI command handler for submitting +// a change reward denoms proposal via a transaction. +func SubmitChangeRewardDenomsProposalTxCmd() *cobra.Command { + return &cobra.Command{ + Use: "change-reward-denoms [proposal-file]", + Args: cobra.ExactArgs(1), + Short: "Submit a change reward denoms proposal", + Long: `Submit an change reward denoms proposal with an initial deposit. + The proposal details must be supplied via a JSON file. + + Example: + $ tx gov submit-legacy-proposal change-reward-denoms --from= + + Where proposal.json contains: + { + "title": "Change reward denoms", + "summary": "Change reward denoms", + "denoms_to_add": ["untrn"], + "denoms_to_remove": ["stake"], + "deposit": "10000stake" + } + `, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + proposal, err := ParseChangeRewardDenomsProposalJSON(args[0]) + if err != nil { + return err + } + + content := types.NewChangeRewardDenomsProposal(proposal.Title, proposal.Summary, proposal.DenomsToAdd, proposal.DenomsToRemove) + + from := clientCtx.GetFromAddress() + + deposit, err := sdk.ParseCoinsNormalized(proposal.Deposit) + if err != nil { + return err + } + + msg, err := govtypes.NewMsgSubmitProposal(content, deposit, from) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } +} + type ConsumerAdditionProposalJSON struct { Title string `json:"title"` Description string `json:"description"` @@ -293,6 +346,21 @@ type ConsumerRemovalProposalReq struct { Deposit sdk.Coins `json:"deposit"` } +func ParseConsumerRemovalProposalJSON(proposalFile string) (ConsumerRemovalProposalJSON, error) { + proposal := ConsumerRemovalProposalJSON{} + + contents, err := os.ReadFile(filepath.Clean(proposalFile)) + if err != nil { + return proposal, err + } + + if err := json.Unmarshal(contents, &proposal); err != nil { + return proposal, err + } + + return proposal, nil +} + type EquivocationProposalJSON struct { // evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" types.EquivocationProposal @@ -333,21 +401,39 @@ func EquivocationProposalRESTHandler(clientCtx client.Context) govrest.ProposalR } } -func ParseConsumerRemovalProposalJSON(proposalFile string) (ConsumerRemovalProposalJSON, error) { - proposal := ConsumerRemovalProposalJSON{} +type ChangeRewardDenomsProposalJSON struct { + Summary string `json:"summary"` + types.ChangeRewardDenomsProposal + Deposit string `json:"deposit"` +} + +type ChangeRewardDenomsProposalReq struct { + BaseReq rest.BaseReq `json:"base_req"` + Proposer sdk.AccAddress `json:"proposer"` + types.ChangeRewardDenomsProposal + Deposit sdk.Coins `json:"deposit"` +} + +func ParseChangeRewardDenomsProposalJSON(proposalFile string) (ChangeRewardDenomsProposalJSON, error) { + proposal := ChangeRewardDenomsProposalJSON{} contents, err := os.ReadFile(filepath.Clean(proposalFile)) if err != nil { return proposal, err } - if err := json.Unmarshal(contents, &proposal); err != nil { return proposal, err } - return proposal, nil } +func ChangeRewardDenomsProposalRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler { + return govrest.ProposalRESTHandler{ + SubRoute: "change_reward_denoms", + Handler: postChangeRewardDenomsProposalHandlerFn(clientCtx), + } +} + // ConsumerAdditionProposalRESTHandler returns a ProposalRESTHandler that exposes the consumer addition rest handler. func ConsumerAdditionProposalRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler { return govrest.ProposalRESTHandler{ @@ -473,3 +559,31 @@ provider unbonding: %s`, providerUnbondingTime) } } + +func postChangeRewardDenomsProposalHandlerFn(clientCtx client.Context) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req ChangeRewardDenomsProposalReq + if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) { + return + } + + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + content := types.NewChangeRewardDenomsProposal( + req.Title, req.Description, req.DenomsToAdd, req.DenomsToRemove) + + msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer) + if rest.CheckBadRequestError(w, err) { + return + } + + if rest.CheckBadRequestError(w, msg.ValidateBasic()) { + return + } + + tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg) + } +} diff --git a/x/ccv/provider/handler.go b/x/ccv/provider/handler.go index dc0c8cbc4f..512cbf9afd 100644 --- a/x/ccv/provider/handler.go +++ b/x/ccv/provider/handler.go @@ -18,9 +18,6 @@ func NewHandler(k *keeper.Keeper) sdk.Handler { case *types.MsgAssignConsumerKey: res, err := msgServer.AssignConsumerKey(sdk.WrapSDKContext(ctx), msg) return sdk.WrapServiceResult(ctx, res, err) - case *types.MsgRegisterConsumerRewardDenom: - res, err := msgServer.RegisterConsumerRewardDenom(sdk.WrapSDKContext(ctx), msg) - return sdk.WrapServiceResult(ctx, res, err) default: return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) } diff --git a/x/ccv/provider/keeper/distribution.go b/x/ccv/provider/keeper/distribution.go index 759de65147..0a1335c11a 100644 --- a/x/ccv/provider/keeper/distribution.go +++ b/x/ccv/provider/keeper/distribution.go @@ -2,7 +2,6 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" - consumertypes "github.com/cosmos/interchain-security/v2/x/ccv/consumer/types" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" ) @@ -14,22 +13,6 @@ func (k Keeper) EndBlockRD(ctx sdk.Context) { k.TransferRewardsToFeeCollector(ctx) } -func (k Keeper) RegisterConsumerRewardDenom(ctx sdk.Context, denom string, sender sdk.AccAddress) error { - // Check if the denom is already registered - if k.ConsumerRewardDenomExists(ctx, denom) { - return consumertypes.ErrConsumerRewardDenomAlreadyRegistered - } - - // Send the consumer reward denom registration fee to the community pool - err := k.distributionKeeper.FundCommunityPool(ctx, sdk.NewCoins(k.GetConsumerRewardDenomRegistrationFee(ctx)), sender) - if err != nil { - return err - } - k.SetConsumerRewardDenom(ctx, denom) - k.Logger(ctx).Info("new consumer reward denom registered:", "denom", denom, "sender", sender.String()) - return nil -} - func (k Keeper) GetConsumerRewardsPoolAddressStr(ctx sdk.Context) string { return k.accountKeeper.GetModuleAccount( ctx, types.ConsumerRewardsPool).GetAddress().String() @@ -52,6 +35,14 @@ func (k Keeper) ConsumerRewardDenomExists( return bz != nil } +func (k Keeper) DeleteConsumerRewardDenom( + ctx sdk.Context, + denom string, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.ConsumerRewardDenomsKey(denom)) +} + func (k Keeper) GetAllConsumerRewardDenoms(ctx sdk.Context) (consumerRewardDenoms []string) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, []byte{types.ConsumerRewardDenomsBytePrefix}) diff --git a/x/ccv/provider/keeper/distribution_test.go b/x/ccv/provider/keeper/distribution_test.go deleted file mode 100644 index a9aa6d88fb..0000000000 --- a/x/ccv/provider/keeper/distribution_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package keeper_test - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - testutil "github.com/cosmos/interchain-security/v2/testutil/keeper" - consumertypes "github.com/cosmos/interchain-security/v2/x/ccv/consumer/types" - "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" -) - -// TestRegisterConsumerRewardDenom tests the RegisterConsumerRewardDenom method. -func TestRegisterConsumerRewardDenom(t *testing.T) { - // Setup - providerKeeper, ctx, ctrl, mocks := testutil.GetProviderKeeperAndCtx(t, testutil.NewInMemKeeperParams(t)) - defer ctrl.Finish() - defaultParams := types.DefaultParams() - providerKeeper.SetParams(ctx, defaultParams) - accAddr := sdk.AccAddress([]byte("addr1")) - gomock.InOrder( - mocks.MockDistributionKeeper.EXPECT().FundCommunityPool(ctx, - sdk.NewCoins(defaultParams.ConsumerRewardDenomRegistrationFee), accAddr).Return(nil).Times(2), - ) - - // Register a consumer reward denom, confirm it's persisted as expected - err := providerKeeper.RegisterConsumerRewardDenom(ctx, "denom1", accAddr) - require.NoError(t, err) - require.True(t, providerKeeper.ConsumerRewardDenomExists(ctx, "denom1")) - allDenoms := providerKeeper.GetAllConsumerRewardDenoms(ctx) - require.Len(t, allDenoms, 1) - require.Equal(t, "denom1", allDenoms[0]) - - // Register another consumer reward denom, confirm both denoms are persisted as expected - err = providerKeeper.RegisterConsumerRewardDenom(ctx, "denom2", accAddr) - require.NoError(t, err) - require.True(t, providerKeeper.ConsumerRewardDenomExists(ctx, "denom2")) - allDenoms = providerKeeper.GetAllConsumerRewardDenoms(ctx) - require.Len(t, allDenoms, 2) - require.Equal(t, "denom1", allDenoms[0]) - require.Equal(t, "denom2", allDenoms[1]) - - // Try to register first consumer reward denom again, confirm it fails - err = providerKeeper.RegisterConsumerRewardDenom(ctx, "denom1", accAddr) - require.Error(t, err) - require.Equal(t, consumertypes.ErrConsumerRewardDenomAlreadyRegistered, err) -} diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index ea5ff66220..58c621d74b 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -105,24 +105,3 @@ func (k msgServer) AssignConsumerKey(goCtx context.Context, msg *types.MsgAssign return &types.MsgAssignConsumerKeyResponse{}, nil } - -func (k msgServer) RegisterConsumerRewardDenom(goCtx context.Context, msg *types.MsgRegisterConsumerRewardDenom) (*types.MsgRegisterConsumerRewardDenomResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - depositer, err := sdk.AccAddressFromBech32(msg.Depositor) - if err != nil { - return nil, err - } - - if err := k.Keeper.RegisterConsumerRewardDenom(ctx, msg.Denom, depositer); err != nil { - return nil, err - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - ccvtypes.EventTypeRegisterConsumerRewardDenom, - sdk.NewAttribute(ccvtypes.AttributeConsumerRewardDenom, msg.Denom), - sdk.NewAttribute(ccvtypes.AttributeConsumerRewardDepositor, msg.Depositor), - )) - - return &types.MsgRegisterConsumerRewardDenomResponse{}, nil -} diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index e50619c066..10c9339735 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -615,3 +615,31 @@ func (k Keeper) HandleEquivocationProposal(ctx sdk.Context, p *types.Equivocatio } return nil } + +func (k Keeper) HandleConsumerRewardDenomProposal(ctx sdk.Context, p *types.ChangeRewardDenomsProposal) error { + for _, denomToAdd := range p.DenomsToAdd { + // Log error and move on if one of the denoms is already registered + if k.ConsumerRewardDenomExists(ctx, denomToAdd) { + ctx.Logger().Error("denom %s already registered", denomToAdd) + continue + } + k.SetConsumerRewardDenom(ctx, denomToAdd) + ctx.EventManager().EmitEvent(sdk.NewEvent( + ccv.EventTypeAddConsumerRewardDenom, + sdk.NewAttribute(ccv.AttributeConsumerRewardDenom, denomToAdd), + )) + } + for _, denomToRemove := range p.DenomsToRemove { + // Log error and move on if one of the denoms is not registered + if !k.ConsumerRewardDenomExists(ctx, denomToRemove) { + ctx.Logger().Error("denom %s not registered", denomToRemove) + continue + } + k.DeleteConsumerRewardDenom(ctx, denomToRemove) + ctx.EventManager().EmitEvent(sdk.NewEvent( + ccv.EventTypeRemoveConsumerRewardDenom, + sdk.NewAttribute(ccv.AttributeConsumerRewardDenom, denomToRemove), + )) + } + return nil +} diff --git a/x/ccv/provider/proposal_handler.go b/x/ccv/provider/proposal_handler.go index 8854b8e57c..83ff370f43 100644 --- a/x/ccv/provider/proposal_handler.go +++ b/x/ccv/provider/proposal_handler.go @@ -21,6 +21,8 @@ func NewProviderProposalHandler(k keeper.Keeper) govtypes.Handler { return k.HandleConsumerRemovalProposal(ctx, c) case *types.EquivocationProposal: return k.HandleEquivocationProposal(ctx, c) + case *types.ChangeRewardDenomsProposal: + return k.HandleConsumerRewardDenomProposal(ctx, c) default: return errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized ccv proposal content type: %T", c) } diff --git a/x/ccv/provider/proposal_handler_test.go b/x/ccv/provider/proposal_handler_test.go index 1e8e9b34b5..b171b2ce31 100644 --- a/x/ccv/provider/proposal_handler_test.go +++ b/x/ccv/provider/proposal_handler_test.go @@ -27,12 +27,13 @@ func TestProviderProposalHandler(t *testing.T) { equivocation := &evidencetypes.Equivocation{Height: 42} testCases := []struct { - name string - content govtypes.Content - blockTime time.Time - expValidConsumerAddition bool - expValidConsumerRemoval bool - expValidEquivocation bool + name string + content govtypes.Content + blockTime time.Time + expValidConsumerAddition bool + expValidConsumerRemoval bool + expValidEquivocation bool + expValidChangeRewardDenom bool }{ { name: "valid consumer addition proposal", @@ -72,6 +73,13 @@ func TestProviderProposalHandler(t *testing.T) { blockTime: hourFromNow, expValidEquivocation: true, }, + { + name: "valid change reward denoms proposal", + content: providertypes.NewChangeRewardDenomsProposal( + "title", "description", []string{"denom1"}, []string{"denom2"}), + blockTime: hourFromNow, + expValidChangeRewardDenom: true, + }, { name: "nil proposal", content: nil, @@ -106,6 +114,8 @@ func TestProviderProposalHandler(t *testing.T) { case tc.expValidEquivocation: providerKeeper.SetSlashLog(ctx, providertypes.NewProviderConsAddress(equivocation.GetConsensusAddress())) mocks.MockEvidenceKeeper.EXPECT().HandleEquivocationEvidence(ctx, equivocation) + case tc.expValidChangeRewardDenom: + // Nothing to mock } // Execution @@ -113,7 +123,7 @@ func TestProviderProposalHandler(t *testing.T) { err := proposalHandler(ctx, tc.content) if tc.expValidConsumerAddition || tc.expValidConsumerRemoval || - tc.expValidEquivocation { + tc.expValidEquivocation || tc.expValidChangeRewardDenom { require.NoError(t, err) } else { require.Error(t, err) diff --git a/x/ccv/provider/types/codec.go b/x/ccv/provider/types/codec.go index 3e4b34dd42..aacc63731b 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -28,12 +28,12 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { &MsgAssignConsumerKey{}, ) registry.RegisterImplementations( - (*sdk.Msg)(nil), - &MsgRegisterConsumerRewardDenom{}, + (*govtypes.Content)(nil), + &EquivocationProposal{}, ) registry.RegisterImplementations( (*govtypes.Content)(nil), - &EquivocationProposal{}, + &ChangeRewardDenomsProposal{}, ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 67ad99d10c..901aa03600 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -9,8 +9,7 @@ import ( // provider message types const ( - TypeMsgAssignConsumerKey = "assign_consumer_key" - TypeMsgRegisterConsumerRewardDenom = "register_consumer_reward_denom" + TypeMsgAssignConsumerKey = "assign_consumer_key" ) var _ sdk.Msg = &MsgAssignConsumerKey{} @@ -94,48 +93,3 @@ func ParseConsumerKeyFromJson(jsonStr string) (pkType, key string, err error) { } return pubKey.Type, pubKey.Key, nil } - -// NewMsgRegisterConsumerRewardDenom returns a new MsgRegisterConsumerRewardDenom with a sender and -// a funding amount. -func NewMsgRegisterConsumerRewardDenom(denom string, depositor sdk.AccAddress) *MsgRegisterConsumerRewardDenom { - return &MsgRegisterConsumerRewardDenom{ - Denom: denom, - Depositor: depositor.String(), - } -} - -// Route returns the MsgRegisterConsumerRewardDenom message route. -func (msg MsgRegisterConsumerRewardDenom) Route() string { return ModuleName } - -// Type returns the MsgRegisterConsumerRewardDenom message type. -func (msg MsgRegisterConsumerRewardDenom) Type() string { return TypeMsgRegisterConsumerRewardDenom } - -// GetSigners returns the signer addresses that are expected to sign the result -// of GetSignBytes. -func (msg MsgRegisterConsumerRewardDenom) GetSigners() []sdk.AccAddress { - depoAddr, err := sdk.AccAddressFromBech32(msg.Depositor) - if err != nil { - panic(err) - } - return []sdk.AccAddress{depoAddr} -} - -// GetSignBytes returns the raw bytes for a MsgRegisterConsumerRewardDenom message that -// the expected signer needs to sign. -func (msg MsgRegisterConsumerRewardDenom) GetSignBytes() []byte { - bz := ModuleCdc.MustMarshalJSON(&msg) - return sdk.MustSortJSON(bz) -} - -// ValidateBasic performs basic MsgRegisterConsumerRewardDenom message validation. -func (msg MsgRegisterConsumerRewardDenom) ValidateBasic() error { - if !sdk.NewCoin(msg.Denom, sdk.NewInt(0)).IsValid() { - return ErrInvalidConsumerRewardDenom - } - _, err := sdk.AccAddressFromBech32(msg.Depositor) - if err != nil { - return ErrInvalidDepositorAddress - } - - return nil -} diff --git a/x/ccv/provider/types/proposal.go b/x/ccv/provider/types/proposal.go index 913152e68c..6c1ff5057c 100644 --- a/x/ccv/provider/types/proposal.go +++ b/x/ccv/provider/types/proposal.go @@ -7,6 +7,8 @@ import ( time "time" errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" @@ -14,21 +16,24 @@ import ( ) const ( - ProposalTypeConsumerAddition = "ConsumerAddition" - ProposalTypeConsumerRemoval = "ConsumerRemoval" - ProposalTypeEquivocation = "Equivocation" + ProposalTypeConsumerAddition = "ConsumerAddition" + ProposalTypeConsumerRemoval = "ConsumerRemoval" + ProposalTypeEquivocation = "Equivocation" + ProposalTypeChangeRewardDenoms = "ChangeRewardDenoms" ) var ( _ govtypes.Content = &ConsumerAdditionProposal{} _ govtypes.Content = &ConsumerRemovalProposal{} _ govtypes.Content = &EquivocationProposal{} + _ govtypes.Content = &ChangeRewardDenomsProposal{} ) func init() { govtypes.RegisterProposalType(ProposalTypeConsumerAddition) govtypes.RegisterProposalType(ProposalTypeConsumerRemoval) govtypes.RegisterProposalType(ProposalTypeEquivocation) + govtypes.RegisterProposalType(ProposalTypeChangeRewardDenoms) } // NewConsumerAdditionProposal creates a new consumer addition proposal. @@ -228,3 +233,57 @@ func (sp *EquivocationProposal) ValidateBasic() error { } return nil } + +func NewChangeRewardDenomsProposal(title, description string, + denomsToAdd, denomsToRemove []string, +) govtypes.Content { + return &ChangeRewardDenomsProposal{ + Title: title, + Description: description, + DenomsToAdd: denomsToAdd, + DenomsToRemove: denomsToRemove, + } +} + +// ProposalRoute returns the routing key of a change reward denoms proposal. +func (crdp *ChangeRewardDenomsProposal) ProposalRoute() string { return RouterKey } + +// ProposalType returns the type of a change reward denoms proposal. +func (crdp *ChangeRewardDenomsProposal) ProposalType() string { + return ProposalTypeChangeRewardDenoms +} + +// ValidateBasic runs basic stateless validity checks on a ChangeRewardDenomsProposal. +func (crdp *ChangeRewardDenomsProposal) ValidateBasic() error { + emptyDenomsToAdd := len(crdp.DenomsToAdd) == 0 + emptyDenomsToRemove := len(crdp.DenomsToRemove) == 0 + // Return error if both sets are empty or nil + if emptyDenomsToAdd && emptyDenomsToRemove { + return fmt.Errorf( + "invalid change reward denoms proposal: both denoms to add and denoms to remove are empty") + } + + // Return error if a denom is in both sets + for _, denomToAdd := range crdp.DenomsToAdd { + for _, denomToRemove := range crdp.DenomsToRemove { + if denomToAdd == denomToRemove { + return fmt.Errorf( + "invalid change reward denoms proposal: %s cannot be both added and removed", denomToAdd) + } + } + } + + // Return error if any denom is "invalid" + for _, denom := range crdp.DenomsToAdd { + if !sdk.NewCoin(denom, sdk.NewInt(1)).IsValid() { + return fmt.Errorf("invalid change reward denoms proposal: %s is not a valid denom", denom) + } + } + for _, denom := range crdp.DenomsToRemove { + if !sdk.NewCoin(denom, sdk.NewInt(1)).IsValid() { + return fmt.Errorf("invalid change reward denoms proposal: %s is not a valid denom", denom) + } + } + + return nil +} diff --git a/x/ccv/provider/types/proposal_test.go b/x/ccv/provider/types/proposal_test.go index d1440eb9a0..165450f8b2 100644 --- a/x/ccv/provider/types/proposal_test.go +++ b/x/ccv/provider/types/proposal_test.go @@ -362,3 +362,52 @@ func TestEquivocationProposalValidateBasic(t *testing.T) { }) } } + +func TestChangeRewardDenomsProposalValidateBasic(t *testing.T) { + tcs := []struct { + name string + proposal govtypes.Content + expectError bool + expectPanic bool + }{ + { + name: "invalid change reward denoms proposal, none to add or remove", + proposal: types.NewChangeRewardDenomsProposal( + "title", "description", []string{}, []string{}), + expectError: true, + }, + { + name: "invalid change reward denoms proposal, same denom in both sets", + proposal: types.NewChangeRewardDenomsProposal( + "title", "description", []string{"denom1"}, []string{"denom1"}), + expectError: true, + }, + { + name: "valid change reward denoms proposal", + proposal: types.NewChangeRewardDenomsProposal( + "title", "description", []string{"denom1"}, []string{"denom2"}), + expectError: false, + }, + { + name: "invalid prop, invalid denom, will panic", + proposal: types.NewChangeRewardDenomsProposal( + "title", "description", []string{"!@blah"}, []string{"denom2"}), + expectPanic: true, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + if tc.expectPanic { + require.Panics(t, func() { tc.proposal.ValidateBasic() }) + return + } + err := tc.proposal.ValidateBasic() + if tc.expectError { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index acc95ca9af..2146e0a188 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -253,6 +253,80 @@ func (m *EquivocationProposal) GetEquivocations() []*types1.Equivocation { return nil } +// ChangeRewardDenomsProposal is a governance proposal on the provider chain to +// mutate the set of denoms accepted by the provider as rewards. +type ChangeRewardDenomsProposal struct { + // the title of the proposal + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` + // the description of the proposal + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // the list of consumer reward denoms to add + DenomsToAdd []string `protobuf:"bytes,3,rep,name=denoms_to_add,json=denomsToAdd,proto3" json:"denoms_to_add,omitempty"` + // the list of consumer reward denoms to remove + DenomsToRemove []string `protobuf:"bytes,4,rep,name=denoms_to_remove,json=denomsToRemove,proto3" json:"denoms_to_remove,omitempty"` +} + +func (m *ChangeRewardDenomsProposal) Reset() { *m = ChangeRewardDenomsProposal{} } +func (m *ChangeRewardDenomsProposal) String() string { return proto.CompactTextString(m) } +func (*ChangeRewardDenomsProposal) ProtoMessage() {} +func (*ChangeRewardDenomsProposal) Descriptor() ([]byte, []int) { + return fileDescriptor_f22ec409a72b7b72, []int{3} +} +func (m *ChangeRewardDenomsProposal) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ChangeRewardDenomsProposal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ChangeRewardDenomsProposal.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ChangeRewardDenomsProposal) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChangeRewardDenomsProposal.Merge(m, src) +} +func (m *ChangeRewardDenomsProposal) XXX_Size() int { + return m.Size() +} +func (m *ChangeRewardDenomsProposal) XXX_DiscardUnknown() { + xxx_messageInfo_ChangeRewardDenomsProposal.DiscardUnknown(m) +} + +var xxx_messageInfo_ChangeRewardDenomsProposal proto.InternalMessageInfo + +func (m *ChangeRewardDenomsProposal) GetTitle() string { + if m != nil { + return m.Title + } + return "" +} + +func (m *ChangeRewardDenomsProposal) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func (m *ChangeRewardDenomsProposal) GetDenomsToAdd() []string { + if m != nil { + return m.DenomsToAdd + } + return nil +} + +func (m *ChangeRewardDenomsProposal) GetDenomsToRemove() []string { + if m != nil { + return m.DenomsToRemove + } + return nil +} + // A persisted queue entry indicating that a slash packet data instance needs to be handled. // This type belongs in the "global" queue, to coordinate slash packet handling times between consumers. type GlobalSlashEntry struct { @@ -275,7 +349,7 @@ func (m *GlobalSlashEntry) Reset() { *m = GlobalSlashEntry{} } func (m *GlobalSlashEntry) String() string { return proto.CompactTextString(m) } func (*GlobalSlashEntry) ProtoMessage() {} func (*GlobalSlashEntry) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{3} + return fileDescriptor_f22ec409a72b7b72, []int{4} } func (m *GlobalSlashEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -362,7 +436,7 @@ func (m *Params) Reset() { *m = Params{} } func (m *Params) String() string { return proto.CompactTextString(m) } func (*Params) ProtoMessage() {} func (*Params) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{4} + return fileDescriptor_f22ec409a72b7b72, []int{5} } func (m *Params) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -463,7 +537,7 @@ func (m *HandshakeMetadata) Reset() { *m = HandshakeMetadata{} } func (m *HandshakeMetadata) String() string { return proto.CompactTextString(m) } func (*HandshakeMetadata) ProtoMessage() {} func (*HandshakeMetadata) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{5} + return fileDescriptor_f22ec409a72b7b72, []int{6} } func (m *HandshakeMetadata) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -516,7 +590,7 @@ func (m *SlashAcks) Reset() { *m = SlashAcks{} } func (m *SlashAcks) String() string { return proto.CompactTextString(m) } func (*SlashAcks) ProtoMessage() {} func (*SlashAcks) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{6} + return fileDescriptor_f22ec409a72b7b72, []int{7} } func (m *SlashAcks) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -562,7 +636,7 @@ func (m *ConsumerAdditionProposals) Reset() { *m = ConsumerAdditionPropo func (m *ConsumerAdditionProposals) String() string { return proto.CompactTextString(m) } func (*ConsumerAdditionProposals) ProtoMessage() {} func (*ConsumerAdditionProposals) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{7} + return fileDescriptor_f22ec409a72b7b72, []int{8} } func (m *ConsumerAdditionProposals) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -608,7 +682,7 @@ func (m *ConsumerRemovalProposals) Reset() { *m = ConsumerRemovalProposa func (m *ConsumerRemovalProposals) String() string { return proto.CompactTextString(m) } func (*ConsumerRemovalProposals) ProtoMessage() {} func (*ConsumerRemovalProposals) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{8} + return fileDescriptor_f22ec409a72b7b72, []int{9} } func (m *ConsumerRemovalProposals) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -653,7 +727,7 @@ func (m *AddressList) Reset() { *m = AddressList{} } func (m *AddressList) String() string { return proto.CompactTextString(m) } func (*AddressList) ProtoMessage() {} func (*AddressList) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{9} + return fileDescriptor_f22ec409a72b7b72, []int{10} } func (m *AddressList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -698,7 +772,7 @@ func (m *ChannelToChain) Reset() { *m = ChannelToChain{} } func (m *ChannelToChain) String() string { return proto.CompactTextString(m) } func (*ChannelToChain) ProtoMessage() {} func (*ChannelToChain) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{10} + return fileDescriptor_f22ec409a72b7b72, []int{11} } func (m *ChannelToChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -752,7 +826,7 @@ func (m *VscUnbondingOps) Reset() { *m = VscUnbondingOps{} } func (m *VscUnbondingOps) String() string { return proto.CompactTextString(m) } func (*VscUnbondingOps) ProtoMessage() {} func (*VscUnbondingOps) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{11} + return fileDescriptor_f22ec409a72b7b72, []int{12} } func (m *VscUnbondingOps) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -807,7 +881,7 @@ func (m *UnbondingOp) Reset() { *m = UnbondingOp{} } func (m *UnbondingOp) String() string { return proto.CompactTextString(m) } func (*UnbondingOp) ProtoMessage() {} func (*UnbondingOp) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{12} + return fileDescriptor_f22ec409a72b7b72, []int{13} } func (m *UnbondingOp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -859,7 +933,7 @@ func (m *InitTimeoutTimestamp) Reset() { *m = InitTimeoutTimestamp{} } func (m *InitTimeoutTimestamp) String() string { return proto.CompactTextString(m) } func (*InitTimeoutTimestamp) ProtoMessage() {} func (*InitTimeoutTimestamp) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{13} + return fileDescriptor_f22ec409a72b7b72, []int{14} } func (m *InitTimeoutTimestamp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -911,7 +985,7 @@ func (m *VscSendTimestamp) Reset() { *m = VscSendTimestamp{} } func (m *VscSendTimestamp) String() string { return proto.CompactTextString(m) } func (*VscSendTimestamp) ProtoMessage() {} func (*VscSendTimestamp) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{14} + return fileDescriptor_f22ec409a72b7b72, []int{15} } func (m *VscSendTimestamp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -964,7 +1038,7 @@ func (m *KeyAssignmentReplacement) Reset() { *m = KeyAssignmentReplaceme func (m *KeyAssignmentReplacement) String() string { return proto.CompactTextString(m) } func (*KeyAssignmentReplacement) ProtoMessage() {} func (*KeyAssignmentReplacement) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{15} + return fileDescriptor_f22ec409a72b7b72, []int{16} } func (m *KeyAssignmentReplacement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1026,7 +1100,7 @@ func (m *ValidatorConsumerPubKey) Reset() { *m = ValidatorConsumerPubKey func (m *ValidatorConsumerPubKey) String() string { return proto.CompactTextString(m) } func (*ValidatorConsumerPubKey) ProtoMessage() {} func (*ValidatorConsumerPubKey) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{16} + return fileDescriptor_f22ec409a72b7b72, []int{17} } func (m *ValidatorConsumerPubKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1088,7 +1162,7 @@ func (m *ValidatorByConsumerAddr) Reset() { *m = ValidatorByConsumerAddr func (m *ValidatorByConsumerAddr) String() string { return proto.CompactTextString(m) } func (*ValidatorByConsumerAddr) ProtoMessage() {} func (*ValidatorByConsumerAddr) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{17} + return fileDescriptor_f22ec409a72b7b72, []int{18} } func (m *ValidatorByConsumerAddr) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1150,7 +1224,7 @@ func (m *ConsumerAddrsToPrune) Reset() { *m = ConsumerAddrsToPrune{} } func (m *ConsumerAddrsToPrune) String() string { return proto.CompactTextString(m) } func (*ConsumerAddrsToPrune) ProtoMessage() {} func (*ConsumerAddrsToPrune) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{18} + return fileDescriptor_f22ec409a72b7b72, []int{19} } func (m *ConsumerAddrsToPrune) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1204,6 +1278,7 @@ func init() { proto.RegisterType((*ConsumerAdditionProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerAdditionProposal") proto.RegisterType((*ConsumerRemovalProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerRemovalProposal") proto.RegisterType((*EquivocationProposal)(nil), "interchain_security.ccv.provider.v1.EquivocationProposal") + proto.RegisterType((*ChangeRewardDenomsProposal)(nil), "interchain_security.ccv.provider.v1.ChangeRewardDenomsProposal") proto.RegisterType((*GlobalSlashEntry)(nil), "interchain_security.ccv.provider.v1.GlobalSlashEntry") proto.RegisterType((*Params)(nil), "interchain_security.ccv.provider.v1.Params") proto.RegisterType((*HandshakeMetadata)(nil), "interchain_security.ccv.provider.v1.HandshakeMetadata") @@ -1227,107 +1302,111 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1598 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0x4b, 0x73, 0xdc, 0xc6, - 0x11, 0x26, 0xb8, 0x7c, 0xed, 0x2c, 0x1f, 0x12, 0x44, 0x59, 0x4b, 0x85, 0x59, 0xae, 0xe0, 0xc4, - 0xc5, 0x94, 0xcb, 0xd8, 0x90, 0xbe, 0xa4, 0x54, 0x71, 0xb9, 0xc8, 0x95, 0x65, 0xd1, 0x8c, 0xad, - 0x35, 0xc8, 0x50, 0x95, 0xe4, 0x80, 0x1a, 0x0c, 0x5a, 0xbb, 0x53, 0x04, 0x30, 0xd0, 0xcc, 0x00, - 0xd2, 0x5e, 0x72, 0xce, 0xd1, 0xb9, 0xb9, 0x92, 0x8b, 0xf3, 0x0b, 0xf2, 0x37, 0x7c, 0xf4, 0x31, - 0x27, 0x3b, 0x45, 0x1d, 0x72, 0xc8, 0x9f, 0x48, 0xcd, 0xe0, 0xcd, 0x87, 0xb2, 0xaa, 0xc4, 0x37, - 0xcc, 0x4c, 0xf7, 0xd7, 0xdd, 0xd3, 0xdd, 0x5f, 0x0f, 0xd0, 0x3e, 0x8d, 0x24, 0x70, 0x32, 0xc1, - 0x34, 0x72, 0x05, 0x90, 0x84, 0x53, 0x39, 0x1d, 0x10, 0x92, 0x0e, 0x62, 0xce, 0x52, 0xea, 0x03, - 0x1f, 0xa4, 0x7b, 0xe5, 0xb7, 0x1d, 0x73, 0x26, 0x99, 0xf9, 0xee, 0x35, 0x3a, 0x36, 0x21, 0xa9, - 0x5d, 0xca, 0xa5, 0x7b, 0xf7, 0x37, 0xc7, 0x6c, 0xcc, 0xb4, 0xfc, 0x40, 0x7d, 0x65, 0xaa, 0xf7, - 0x77, 0xc6, 0x8c, 0x8d, 0x03, 0x18, 0xe8, 0x95, 0x97, 0x3c, 0x1f, 0x48, 0x1a, 0x82, 0x90, 0x38, - 0x8c, 0x73, 0x81, 0xde, 0x65, 0x01, 0x3f, 0xe1, 0x58, 0x52, 0x16, 0x15, 0x00, 0xd4, 0x23, 0x03, - 0xc2, 0x38, 0x0c, 0x48, 0x40, 0x21, 0x92, 0xca, 0xbd, 0xec, 0x2b, 0x17, 0x18, 0x28, 0x81, 0x80, - 0x8e, 0x27, 0x32, 0xdb, 0x16, 0x03, 0x09, 0x91, 0x0f, 0x3c, 0xa4, 0x99, 0x70, 0xb5, 0xca, 0x15, - 0xb6, 0x6b, 0xe7, 0x84, 0x4f, 0x63, 0xc9, 0x06, 0xe7, 0x30, 0x15, 0xf9, 0xe9, 0x7b, 0x84, 0x89, - 0x90, 0x89, 0x01, 0xa8, 0xc0, 0x22, 0x02, 0x83, 0x74, 0xcf, 0x03, 0x89, 0xf7, 0xca, 0x8d, 0xc2, - 0xef, 0x5c, 0xce, 0xc3, 0xa2, 0x92, 0x21, 0x8c, 0xe6, 0x7e, 0x5b, 0x3f, 0x2c, 0xa1, 0xee, 0x90, - 0x45, 0x22, 0x09, 0x81, 0x1f, 0xf8, 0x3e, 0x55, 0x21, 0x8d, 0x38, 0x8b, 0x99, 0xc0, 0x81, 0xb9, - 0x89, 0x16, 0x25, 0x95, 0x01, 0x74, 0x8d, 0xbe, 0xb1, 0xdb, 0x76, 0xb2, 0x85, 0xd9, 0x47, 0x1d, - 0x1f, 0x04, 0xe1, 0x34, 0x56, 0xc2, 0xdd, 0x79, 0x7d, 0x56, 0xdf, 0x32, 0xb7, 0xd0, 0x4a, 0x96, - 0x05, 0xea, 0x77, 0x5b, 0xfa, 0x78, 0x59, 0xaf, 0x8f, 0x7c, 0xf3, 0x53, 0xb4, 0x4e, 0x23, 0x2a, - 0x29, 0x0e, 0xdc, 0x09, 0xa8, 0xdb, 0xe8, 0x2e, 0xf4, 0x8d, 0xdd, 0xce, 0xfe, 0x7d, 0x9b, 0x7a, - 0xc4, 0x56, 0x17, 0x68, 0xe7, 0xd7, 0x96, 0xee, 0xd9, 0x4f, 0xb4, 0xc4, 0xe1, 0xc2, 0xb7, 0xdf, - 0xef, 0xcc, 0x39, 0x6b, 0xb9, 0x5e, 0xb6, 0x69, 0x3e, 0x40, 0xab, 0x63, 0x88, 0x40, 0x50, 0xe1, - 0x4e, 0xb0, 0x98, 0x74, 0x17, 0xfb, 0xc6, 0xee, 0xaa, 0xd3, 0xc9, 0xf7, 0x9e, 0x60, 0x31, 0x31, - 0x77, 0x50, 0xc7, 0xa3, 0x11, 0xe6, 0xd3, 0x4c, 0x62, 0x49, 0x4b, 0xa0, 0x6c, 0x4b, 0x0b, 0x0c, - 0x11, 0x12, 0x31, 0x7e, 0x19, 0xb9, 0x2a, 0xdb, 0xdd, 0xe5, 0xdc, 0x91, 0x2c, 0xd3, 0x76, 0x91, - 0x69, 0xfb, 0xb4, 0x28, 0x85, 0xc3, 0x15, 0xe5, 0xc8, 0x57, 0x3f, 0xec, 0x18, 0x4e, 0x5b, 0xeb, - 0xa9, 0x13, 0xf3, 0x0b, 0x74, 0x2b, 0x89, 0x3c, 0x16, 0xf9, 0x34, 0x1a, 0xbb, 0x31, 0x70, 0xca, - 0xfc, 0xee, 0x8a, 0x86, 0xda, 0xba, 0x02, 0xf5, 0x28, 0x2f, 0x9a, 0x0c, 0xe9, 0x6b, 0x85, 0xb4, - 0x51, 0x2a, 0x8f, 0xb4, 0xae, 0xf9, 0x25, 0x32, 0x09, 0x49, 0xb5, 0x4b, 0x2c, 0x91, 0x05, 0x62, - 0x7b, 0x76, 0xc4, 0x5b, 0x84, 0xa4, 0xa7, 0x99, 0x76, 0x0e, 0xf9, 0x07, 0x74, 0x4f, 0x72, 0x1c, - 0x89, 0xe7, 0xc0, 0x2f, 0xe3, 0xa2, 0xd9, 0x71, 0xef, 0x16, 0x18, 0x4d, 0xf0, 0x27, 0xa8, 0x4f, - 0xf2, 0x02, 0x72, 0x39, 0xf8, 0x54, 0x48, 0x4e, 0xbd, 0x44, 0xe9, 0xba, 0xcf, 0x39, 0x26, 0xba, - 0x46, 0x3a, 0xba, 0x08, 0x7a, 0x85, 0x9c, 0xd3, 0x10, 0x7b, 0x9c, 0x4b, 0x99, 0x4f, 0xd1, 0xcf, - 0xbc, 0x80, 0x91, 0x73, 0xa1, 0x9c, 0x73, 0x1b, 0x48, 0xda, 0x74, 0x48, 0x85, 0x50, 0x68, 0xab, - 0x7d, 0x63, 0xb7, 0xe5, 0x3c, 0xc8, 0x64, 0x47, 0xc0, 0x1f, 0xd5, 0x24, 0x4f, 0x6b, 0x82, 0xe6, - 0x07, 0xc8, 0x9c, 0x50, 0x21, 0x19, 0xa7, 0x04, 0x07, 0x2e, 0x44, 0x92, 0x53, 0x10, 0xdd, 0x35, - 0xad, 0x7e, 0xbb, 0x3a, 0xf9, 0x24, 0x3b, 0x30, 0x3f, 0x43, 0x0f, 0x6e, 0x34, 0xea, 0x92, 0x09, - 0x8e, 0x22, 0x08, 0xba, 0xeb, 0x3a, 0x94, 0x1d, 0xff, 0x06, 0x9b, 0xc3, 0x4c, 0xec, 0xe1, 0xca, - 0x9f, 0xbe, 0xd9, 0x99, 0xfb, 0xfa, 0x9b, 0x9d, 0x39, 0xeb, 0xef, 0x06, 0xba, 0x37, 0x2c, 0x03, - 0x0f, 0x59, 0x8a, 0x83, 0x1f, 0xb3, 0xc1, 0x0e, 0x50, 0x5b, 0x48, 0x16, 0x67, 0x25, 0xbd, 0xf0, - 0x16, 0x25, 0xbd, 0xa2, 0xd4, 0xd4, 0x81, 0xf5, 0x57, 0x03, 0x6d, 0x7e, 0xf2, 0x22, 0xa1, 0x29, - 0x23, 0xf8, 0xff, 0xc2, 0x07, 0xc7, 0x68, 0x0d, 0x6a, 0x78, 0xa2, 0xdb, 0xea, 0xb7, 0x76, 0x3b, - 0xfb, 0x3f, 0xb7, 0x33, 0x72, 0xb2, 0x4b, 0xce, 0xca, 0x09, 0xca, 0xae, 0x5b, 0x77, 0x9a, 0xba, - 0xd6, 0xbf, 0x0d, 0x74, 0xeb, 0xd3, 0x80, 0x79, 0x38, 0x38, 0x09, 0xb0, 0x98, 0xa8, 0xe4, 0x4d, - 0x55, 0xd4, 0x1c, 0xf2, 0xae, 0xd1, 0xde, 0xcd, 0x1c, 0xb5, 0x52, 0xd3, 0x7d, 0xfc, 0x31, 0xba, - 0x5d, 0xd6, 0x71, 0x79, 0xb9, 0x3a, 0x98, 0xc3, 0x3b, 0x17, 0xdf, 0xef, 0x6c, 0x14, 0x39, 0x1c, - 0xea, 0x8b, 0x7e, 0xe4, 0x6c, 0x90, 0xc6, 0x86, 0x6f, 0xf6, 0x50, 0x87, 0x7a, 0xc4, 0x15, 0xf0, - 0xc2, 0x8d, 0x92, 0x50, 0xe7, 0x65, 0xc1, 0x69, 0x53, 0x8f, 0x9c, 0xc0, 0x8b, 0x2f, 0x92, 0xd0, - 0xfc, 0x10, 0xbd, 0x53, 0x0c, 0x22, 0x37, 0xc5, 0x81, 0xab, 0xf4, 0x5d, 0xec, 0xfb, 0x5c, 0xa7, - 0x69, 0xd5, 0xb9, 0x53, 0x9c, 0x9e, 0xe1, 0x40, 0x19, 0x3b, 0xf0, 0x7d, 0x6e, 0xfd, 0x6b, 0x11, - 0x2d, 0x8d, 0x30, 0xc7, 0xa1, 0x30, 0x4f, 0xd1, 0x86, 0x84, 0x30, 0x0e, 0xb0, 0x04, 0x37, 0xe3, - 0xc8, 0x3c, 0xd2, 0xf7, 0x35, 0x77, 0xd6, 0x67, 0x8b, 0x5d, 0x9b, 0x26, 0xe9, 0x9e, 0x3d, 0xd4, - 0xbb, 0x27, 0x12, 0x4b, 0x70, 0xd6, 0x0b, 0x8c, 0x6c, 0xd3, 0xfc, 0x15, 0xea, 0x4a, 0x9e, 0x08, - 0x59, 0xb1, 0x57, 0xd5, 0xb6, 0x59, 0x2a, 0xdf, 0x29, 0xce, 0xb3, 0x86, 0x2f, 0xdb, 0xf5, 0x7a, - 0xa2, 0x6a, 0xfd, 0x2f, 0x44, 0x75, 0x82, 0xee, 0x28, 0x96, 0xbf, 0x8c, 0xb9, 0x30, 0x3b, 0xe6, - 0x6d, 0xa5, 0xdf, 0x04, 0xfd, 0x12, 0x99, 0xa9, 0x20, 0x97, 0x31, 0x17, 0xdf, 0xc2, 0xcf, 0x54, - 0x90, 0x26, 0xa4, 0x8f, 0xb6, 0x85, 0x2a, 0x3e, 0x37, 0x04, 0xa9, 0x69, 0x2f, 0x0e, 0x20, 0xa2, - 0x62, 0x52, 0x80, 0x2f, 0xcd, 0x0e, 0xbe, 0xa5, 0x81, 0x3e, 0x57, 0x38, 0x4e, 0x01, 0x93, 0x5b, - 0x19, 0xa2, 0xde, 0xf5, 0x56, 0xca, 0x04, 0x2d, 0xeb, 0x04, 0xfd, 0xe4, 0x1a, 0x88, 0x32, 0x4b, - 0xfb, 0xe8, 0x6e, 0x88, 0x5f, 0xb9, 0x72, 0xc2, 0x99, 0x94, 0x01, 0xf8, 0x6e, 0x8c, 0xc9, 0x39, - 0x48, 0xa1, 0x67, 0x54, 0xcb, 0xb9, 0x13, 0xe2, 0x57, 0xa7, 0xc5, 0xd9, 0x28, 0x3b, 0x32, 0x05, - 0x7a, 0xaf, 0x46, 0xe9, 0x2f, 0x31, 0xf7, 0x5d, 0x1f, 0x22, 0x16, 0xba, 0x1c, 0xc6, 0x8a, 0xf7, - 0x70, 0xc6, 0xee, 0x00, 0xe5, 0x58, 0xca, 0x1b, 0x59, 0xbd, 0x32, 0xca, 0x26, 0x1e, 0x32, 0x1a, - 0xe5, 0xb3, 0xdb, 0xaa, 0x98, 0x5f, 0xa1, 0x3d, 0x52, 0x60, 0x4e, 0x0d, 0xeb, 0x31, 0x80, 0xe5, - 0xa1, 0xdb, 0x4f, 0x70, 0xe4, 0x8b, 0x09, 0x3e, 0x87, 0xcf, 0x41, 0x62, 0x1f, 0x4b, 0xdc, 0xe8, - 0x99, 0xe7, 0x00, 0x6e, 0xcc, 0x58, 0x90, 0xf5, 0x4c, 0x46, 0x41, 0x65, 0xcf, 0x3c, 0x06, 0x18, - 0x31, 0x16, 0xa8, 0x9e, 0x31, 0xbb, 0x68, 0x39, 0x05, 0x2e, 0xaa, 0x0a, 0x2e, 0x96, 0xd6, 0x2f, - 0x50, 0x5b, 0x93, 0xc6, 0x01, 0x39, 0x17, 0xe6, 0x36, 0x6a, 0x2b, 0x24, 0x10, 0x02, 0x44, 0xd7, - 0xe8, 0xb7, 0x76, 0xdb, 0x4e, 0xb5, 0x61, 0x49, 0xb4, 0x75, 0xd3, 0xbb, 0x48, 0x98, 0xcf, 0xd0, - 0x72, 0x0c, 0x7a, 0x68, 0x6b, 0xc5, 0xce, 0xfe, 0x47, 0xf6, 0x0c, 0x6f, 0x4f, 0xfb, 0x26, 0x40, - 0xa7, 0x40, 0xb3, 0x78, 0xf5, 0x1a, 0xbb, 0x34, 0x2b, 0x84, 0x79, 0x76, 0xd9, 0xe8, 0xaf, 0xdf, - 0xca, 0xe8, 0x25, 0xbc, 0xca, 0xe6, 0xfb, 0xa8, 0x73, 0x90, 0x85, 0xfd, 0x1b, 0x2a, 0xe4, 0xd5, - 0x6b, 0x59, 0xad, 0x5f, 0xcb, 0x67, 0x68, 0x3d, 0x1f, 0x71, 0xa7, 0x4c, 0x13, 0x9f, 0xf9, 0x53, - 0x84, 0xf2, 0xd9, 0xa8, 0x08, 0x33, 0x4b, 0x4b, 0x3b, 0xdf, 0x39, 0xf2, 0x1b, 0xa3, 0x6a, 0xbe, - 0x31, 0xaa, 0x2c, 0x07, 0x6d, 0x9c, 0x09, 0xf2, 0xdb, 0xe2, 0xfd, 0xf3, 0x34, 0x16, 0xe6, 0x5d, - 0xb4, 0xa4, 0x7a, 0x35, 0x07, 0x5a, 0x70, 0x16, 0x53, 0x41, 0x8e, 0x7c, 0x73, 0xb7, 0xfe, 0xc6, - 0x62, 0xb1, 0x4b, 0x7d, 0xd1, 0x9d, 0xef, 0xb7, 0x76, 0x17, 0x9c, 0xf5, 0xa4, 0x52, 0x3f, 0xf2, - 0x85, 0xf5, 0x3b, 0xd4, 0xa9, 0x01, 0x9a, 0xeb, 0x68, 0xbe, 0xc4, 0x9a, 0xa7, 0xbe, 0xf9, 0x10, - 0x6d, 0x55, 0x40, 0x4d, 0xba, 0xcf, 0x10, 0xdb, 0xce, 0xbd, 0x52, 0xa0, 0xc1, 0xf8, 0xc2, 0x7a, - 0x8a, 0x36, 0x8f, 0x2a, 0x72, 0x29, 0x87, 0x49, 0x23, 0x42, 0xa3, 0x39, 0x8c, 0xb7, 0x51, 0xbb, - 0xfc, 0x91, 0xd0, 0xd1, 0x2f, 0x38, 0xd5, 0x86, 0x15, 0xa2, 0x5b, 0x67, 0x82, 0x9c, 0x40, 0xe4, - 0x57, 0x60, 0x37, 0x5c, 0xc0, 0xe1, 0x65, 0xa0, 0x99, 0x1f, 0xaa, 0x95, 0xb9, 0x3f, 0x1b, 0xa8, - 0x7b, 0x0c, 0xd3, 0x03, 0x21, 0xe8, 0x38, 0x0a, 0x21, 0x92, 0x8a, 0x2c, 0x30, 0x01, 0xf5, 0x69, - 0xbe, 0x8b, 0xd6, 0xca, 0x46, 0x2b, 0xfb, 0x6b, 0xd5, 0x59, 0x2d, 0x36, 0x75, 0x63, 0x3d, 0x44, - 0x28, 0xe6, 0x90, 0xba, 0xc4, 0x3d, 0x87, 0x69, 0xee, 0xc6, 0x76, 0x7d, 0xd6, 0x64, 0xff, 0x29, - 0xf6, 0x28, 0xf1, 0x02, 0x4a, 0x8e, 0x61, 0xea, 0xac, 0x28, 0xf9, 0xe1, 0x31, 0x4c, 0xd5, 0xdb, - 0x21, 0x66, 0x2f, 0x81, 0xeb, 0x01, 0xd1, 0x72, 0xb2, 0x85, 0xf5, 0x17, 0x03, 0xdd, 0x3b, 0xc3, - 0x01, 0xf5, 0xb1, 0x64, 0xbc, 0xb8, 0xef, 0x51, 0xe2, 0x29, 0x8d, 0x37, 0xdc, 0xeb, 0x15, 0x6f, - 0xe7, 0xaf, 0xf1, 0xf6, 0x63, 0xb4, 0x5a, 0x66, 0x58, 0xf9, 0xdb, 0x9a, 0xc1, 0xdf, 0x4e, 0xa1, - 0x71, 0x0c, 0x53, 0xeb, 0x8f, 0x35, 0xdf, 0x0e, 0xa7, 0xb5, 0xe6, 0xe5, 0xff, 0xc5, 0xb7, 0xd2, - 0x6c, 0xdd, 0x37, 0x52, 0xd7, 0xbf, 0x12, 0x40, 0xeb, 0x6a, 0x00, 0xd6, 0xdf, 0x0c, 0xb4, 0x59, - 0xb7, 0x2a, 0x4e, 0xd9, 0x88, 0x27, 0x11, 0xbc, 0xc9, 0x7a, 0x55, 0x3f, 0xf3, 0xf5, 0xfa, 0x79, - 0x86, 0xd6, 0x1b, 0x4e, 0x89, 0xfc, 0x36, 0x7e, 0x39, 0x13, 0x85, 0xd4, 0xe8, 0xc1, 0x59, 0xab, - 0xc7, 0x21, 0x0e, 0x9f, 0x7d, 0x7b, 0xd1, 0x33, 0xbe, 0xbb, 0xe8, 0x19, 0xff, 0xbc, 0xe8, 0x19, - 0x5f, 0xbd, 0xee, 0xcd, 0x7d, 0xf7, 0xba, 0x37, 0xf7, 0x8f, 0xd7, 0xbd, 0xb9, 0xdf, 0x7f, 0x34, - 0xa6, 0x72, 0x92, 0x78, 0x36, 0x61, 0xe1, 0x20, 0xff, 0x09, 0xad, 0x6c, 0x7d, 0x50, 0xfe, 0xd3, - 0xa7, 0xfb, 0x83, 0x57, 0xcd, 0x1f, 0x7b, 0x39, 0x8d, 0x41, 0x78, 0x4b, 0xba, 0xac, 0x3f, 0xfc, - 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7b, 0x4c, 0xb0, 0x24, 0x09, 0x10, 0x00, 0x00, + // 1649 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4f, 0x73, 0xdc, 0xb6, + 0x15, 0x17, 0xb5, 0xfa, 0xb7, 0x58, 0xfd, 0x33, 0x2d, 0xc7, 0x2b, 0x57, 0x5d, 0xc9, 0x4c, 0x9b, + 0x51, 0x27, 0x13, 0x6e, 0xa5, 0x5c, 0x3a, 0x9e, 0x66, 0x32, 0xd2, 0x3a, 0x8e, 0x15, 0x35, 0xf1, + 0x86, 0x52, 0xe5, 0x69, 0x7b, 0xe0, 0x80, 0xe0, 0xf3, 0x2e, 0x46, 0x24, 0x41, 0x03, 0x20, 0xed, + 0xbd, 0xf4, 0xdc, 0x63, 0x7a, 0xcb, 0xb4, 0x97, 0xb4, 0x5f, 0xa0, 0x5f, 0x23, 0xc7, 0x1c, 0x7b, + 0x4a, 0x3a, 0xf6, 0xa1, 0x87, 0x7e, 0x89, 0x0e, 0xc0, 0xff, 0x2b, 0x29, 0x5d, 0x4f, 0x9a, 0x1b, + 0x01, 0xfc, 0xde, 0xef, 0x3d, 0xe0, 0x3d, 0xfc, 0x1e, 0x76, 0xd1, 0x21, 0x8d, 0x24, 0x70, 0x32, + 0xc6, 0x34, 0x72, 0x05, 0x90, 0x84, 0x53, 0x39, 0xe9, 0x13, 0x92, 0xf6, 0x63, 0xce, 0x52, 0xea, + 0x03, 0xef, 0xa7, 0x07, 0xe5, 0xb7, 0x1d, 0x73, 0x26, 0x99, 0xf9, 0xf6, 0x35, 0x36, 0x36, 0x21, + 0xa9, 0x5d, 0xe2, 0xd2, 0x83, 0x7b, 0x5b, 0x23, 0x36, 0x62, 0x1a, 0xdf, 0x57, 0x5f, 0x99, 0xe9, + 0xbd, 0xdd, 0x11, 0x63, 0xa3, 0x00, 0xfa, 0x7a, 0xe4, 0x25, 0xcf, 0xfa, 0x92, 0x86, 0x20, 0x24, + 0x0e, 0xe3, 0x1c, 0xd0, 0x9b, 0x06, 0xf8, 0x09, 0xc7, 0x92, 0xb2, 0xa8, 0x20, 0xa0, 0x1e, 0xe9, + 0x13, 0xc6, 0xa1, 0x4f, 0x02, 0x0a, 0x91, 0x54, 0xe1, 0x65, 0x5f, 0x39, 0xa0, 0xaf, 0x00, 0x01, + 0x1d, 0x8d, 0x65, 0x36, 0x2d, 0xfa, 0x12, 0x22, 0x1f, 0x78, 0x48, 0x33, 0x70, 0x35, 0xca, 0x0d, + 0x76, 0x6a, 0xeb, 0x84, 0x4f, 0x62, 0xc9, 0xfa, 0x97, 0x30, 0x11, 0xf9, 0xea, 0x3b, 0x84, 0x89, + 0x90, 0x89, 0x3e, 0xa8, 0x8d, 0x45, 0x04, 0xfa, 0xe9, 0x81, 0x07, 0x12, 0x1f, 0x94, 0x13, 0x45, + 0xdc, 0x39, 0xce, 0xc3, 0xa2, 0xc2, 0x10, 0x46, 0xf3, 0xb8, 0xad, 0xef, 0x96, 0x50, 0x77, 0xc0, + 0x22, 0x91, 0x84, 0xc0, 0x8f, 0x7c, 0x9f, 0xaa, 0x2d, 0x0d, 0x39, 0x8b, 0x99, 0xc0, 0x81, 0xb9, + 0x85, 0x16, 0x25, 0x95, 0x01, 0x74, 0x8d, 0x3d, 0x63, 0xbf, 0xed, 0x64, 0x03, 0x73, 0x0f, 0x75, + 0x7c, 0x10, 0x84, 0xd3, 0x58, 0x81, 0xbb, 0xf3, 0x7a, 0xad, 0x3e, 0x65, 0x6e, 0xa3, 0x95, 0x2c, + 0x0b, 0xd4, 0xef, 0xb6, 0xf4, 0xf2, 0xb2, 0x1e, 0x9f, 0xf8, 0xe6, 0xc7, 0x68, 0x9d, 0x46, 0x54, + 0x52, 0x1c, 0xb8, 0x63, 0x50, 0xa7, 0xd1, 0x5d, 0xd8, 0x33, 0xf6, 0x3b, 0x87, 0xf7, 0x6c, 0xea, + 0x11, 0x5b, 0x1d, 0xa0, 0x9d, 0x1f, 0x5b, 0x7a, 0x60, 0x3f, 0xd6, 0x88, 0xe3, 0x85, 0xaf, 0xbf, + 0xdd, 0x9d, 0x73, 0xd6, 0x72, 0xbb, 0x6c, 0xd2, 0xbc, 0x8f, 0x56, 0x47, 0x10, 0x81, 0xa0, 0xc2, + 0x1d, 0x63, 0x31, 0xee, 0x2e, 0xee, 0x19, 0xfb, 0xab, 0x4e, 0x27, 0x9f, 0x7b, 0x8c, 0xc5, 0xd8, + 0xdc, 0x45, 0x1d, 0x8f, 0x46, 0x98, 0x4f, 0x32, 0xc4, 0x92, 0x46, 0xa0, 0x6c, 0x4a, 0x03, 0x06, + 0x08, 0x89, 0x18, 0xbf, 0x88, 0x5c, 0x95, 0xed, 0xee, 0x72, 0x1e, 0x48, 0x96, 0x69, 0xbb, 0xc8, + 0xb4, 0x7d, 0x5e, 0x94, 0xc2, 0xf1, 0x8a, 0x0a, 0xe4, 0x8b, 0xef, 0x76, 0x0d, 0xa7, 0xad, 0xed, + 0xd4, 0x8a, 0xf9, 0x19, 0xda, 0x4c, 0x22, 0x8f, 0x45, 0x3e, 0x8d, 0x46, 0x6e, 0x0c, 0x9c, 0x32, + 0xbf, 0xbb, 0xa2, 0xa9, 0xb6, 0xaf, 0x50, 0x3d, 0xcc, 0x8b, 0x26, 0x63, 0xfa, 0x52, 0x31, 0x6d, + 0x94, 0xc6, 0x43, 0x6d, 0x6b, 0x7e, 0x8e, 0x4c, 0x42, 0x52, 0x1d, 0x12, 0x4b, 0x64, 0xc1, 0xd8, + 0x9e, 0x9d, 0x71, 0x93, 0x90, 0xf4, 0x3c, 0xb3, 0xce, 0x29, 0xff, 0x80, 0xee, 0x4a, 0x8e, 0x23, + 0xf1, 0x0c, 0xf8, 0x34, 0x2f, 0x9a, 0x9d, 0xf7, 0x4e, 0xc1, 0xd1, 0x24, 0x7f, 0x8c, 0xf6, 0x48, + 0x5e, 0x40, 0x2e, 0x07, 0x9f, 0x0a, 0xc9, 0xa9, 0x97, 0x28, 0x5b, 0xf7, 0x19, 0xc7, 0x44, 0xd7, + 0x48, 0x47, 0x17, 0x41, 0xaf, 0xc0, 0x39, 0x0d, 0xd8, 0xa3, 0x1c, 0x65, 0x3e, 0x41, 0x3f, 0xf3, + 0x02, 0x46, 0x2e, 0x85, 0x0a, 0xce, 0x6d, 0x30, 0x69, 0xd7, 0x21, 0x15, 0x42, 0xb1, 0xad, 0xee, + 0x19, 0xfb, 0x2d, 0xe7, 0x7e, 0x86, 0x1d, 0x02, 0x7f, 0x58, 0x43, 0x9e, 0xd7, 0x80, 0xe6, 0x7b, + 0xc8, 0x1c, 0x53, 0x21, 0x19, 0xa7, 0x04, 0x07, 0x2e, 0x44, 0x92, 0x53, 0x10, 0xdd, 0x35, 0x6d, + 0x7e, 0xab, 0x5a, 0xf9, 0x28, 0x5b, 0x30, 0x3f, 0x41, 0xf7, 0x6f, 0x74, 0xea, 0x92, 0x31, 0x8e, + 0x22, 0x08, 0xba, 0xeb, 0x7a, 0x2b, 0xbb, 0xfe, 0x0d, 0x3e, 0x07, 0x19, 0xec, 0xc1, 0xca, 0x9f, + 0xbe, 0xda, 0x9d, 0xfb, 0xf2, 0xab, 0xdd, 0x39, 0xeb, 0x1f, 0x06, 0xba, 0x3b, 0x28, 0x37, 0x1e, + 0xb2, 0x14, 0x07, 0x3f, 0xe6, 0x05, 0x3b, 0x42, 0x6d, 0x21, 0x59, 0x9c, 0x95, 0xf4, 0xc2, 0x1b, + 0x94, 0xf4, 0x8a, 0x32, 0x53, 0x0b, 0xd6, 0x5f, 0x0d, 0xb4, 0xf5, 0xd1, 0xf3, 0x84, 0xa6, 0x8c, + 0xe0, 0xff, 0x8b, 0x1e, 0x9c, 0xa2, 0x35, 0xa8, 0xf1, 0x89, 0x6e, 0x6b, 0xaf, 0xb5, 0xdf, 0x39, + 0xfc, 0xb9, 0x9d, 0x89, 0x93, 0x5d, 0x6a, 0x56, 0x2e, 0x50, 0x76, 0xdd, 0xbb, 0xd3, 0xb4, 0xb5, + 0xfe, 0x6e, 0xa0, 0x7b, 0xea, 0x94, 0x47, 0xe0, 0xc0, 0x0b, 0xcc, 0xfd, 0x87, 0x10, 0xb1, 0x50, + 0xfc, 0xe0, 0x18, 0x2d, 0xb4, 0xe6, 0x6b, 0x26, 0x57, 0x32, 0x17, 0xfb, 0xbe, 0x8e, 0x51, 0x63, + 0xd4, 0xe4, 0x39, 0x3b, 0xf2, 0x7d, 0x73, 0x1f, 0x6d, 0x56, 0x18, 0xae, 0x72, 0xa9, 0x8e, 0x58, + 0xc1, 0xd6, 0x0b, 0x98, 0xce, 0x30, 0x58, 0xff, 0x31, 0xd0, 0xe6, 0xc7, 0x01, 0xf3, 0x70, 0x70, + 0x16, 0x60, 0x31, 0x56, 0x15, 0x36, 0x51, 0xa9, 0xe1, 0x90, 0x5f, 0x6d, 0x1d, 0xde, 0xcc, 0xa9, + 0x51, 0x66, 0x5a, 0x6c, 0x3e, 0x44, 0xb7, 0xca, 0xcb, 0x56, 0x56, 0x80, 0xde, 0xcd, 0xf1, 0xed, + 0x57, 0xdf, 0xee, 0x6e, 0x14, 0x85, 0x36, 0xd0, 0xd5, 0xf0, 0xd0, 0xd9, 0x20, 0x8d, 0x09, 0xdf, + 0xec, 0xa1, 0x0e, 0xf5, 0x88, 0x2b, 0xe0, 0xb9, 0x1b, 0x25, 0xa1, 0x2e, 0x9e, 0x05, 0xa7, 0x4d, + 0x3d, 0x72, 0x06, 0xcf, 0x3f, 0x4b, 0x42, 0xf3, 0x7d, 0xf4, 0x56, 0xd1, 0x2d, 0xdd, 0x14, 0x07, + 0xae, 0xb2, 0x57, 0xc7, 0xc1, 0x75, 0x2d, 0xad, 0x3a, 0xb7, 0x8b, 0xd5, 0x0b, 0x1c, 0x28, 0x67, + 0x47, 0xbe, 0xcf, 0xad, 0x7f, 0x2f, 0xa2, 0xa5, 0x21, 0xe6, 0x38, 0x14, 0xe6, 0x39, 0xda, 0x90, + 0x10, 0xc6, 0x01, 0x96, 0xe0, 0x66, 0x42, 0x9e, 0xef, 0xf4, 0x5d, 0x2d, 0xf0, 0xf5, 0x06, 0x68, + 0xd7, 0x5a, 0x5e, 0x7a, 0x60, 0x0f, 0xf4, 0xec, 0x99, 0xc4, 0x12, 0x9c, 0xf5, 0x82, 0x23, 0x9b, + 0x34, 0x7f, 0x85, 0xba, 0x92, 0x27, 0x42, 0x56, 0x12, 0x5b, 0x69, 0x4b, 0x96, 0xcb, 0xb7, 0x8a, + 0xf5, 0x4c, 0x95, 0x4a, 0x4d, 0xb9, 0x5e, 0x4d, 0x5b, 0x3f, 0x44, 0x4d, 0xcf, 0xd0, 0x6d, 0xd5, + 0x8a, 0xa6, 0x39, 0x17, 0x66, 0xe7, 0xbc, 0xa5, 0xec, 0x9b, 0xa4, 0x9f, 0x23, 0x33, 0x15, 0x64, + 0x9a, 0x73, 0xf1, 0x0d, 0xe2, 0x4c, 0x05, 0x69, 0x52, 0xfa, 0x68, 0x47, 0xa8, 0xe2, 0x73, 0x43, + 0x90, 0x5a, 0x9b, 0xe3, 0x00, 0x22, 0x2a, 0xc6, 0x05, 0xf9, 0xd2, 0xec, 0xe4, 0xdb, 0x9a, 0xe8, + 0x53, 0xc5, 0xe3, 0x14, 0x34, 0xb9, 0x97, 0x01, 0xea, 0x5d, 0xef, 0xa5, 0x4c, 0xd0, 0xb2, 0x4e, + 0xd0, 0x4f, 0xae, 0xa1, 0x28, 0xb3, 0x74, 0x88, 0xee, 0x84, 0xf8, 0xa5, 0x2b, 0xc7, 0x9c, 0x49, + 0x19, 0x80, 0xef, 0xc6, 0x98, 0x5c, 0x82, 0x14, 0xba, 0x91, 0xb6, 0x9c, 0xdb, 0x21, 0x7e, 0x79, + 0x5e, 0xac, 0x0d, 0xb3, 0x25, 0x53, 0xa0, 0x77, 0x6a, 0x7d, 0x47, 0x29, 0x81, 0xab, 0x2f, 0xa1, + 0xcb, 0x61, 0xa4, 0xc4, 0x19, 0x67, 0x2d, 0x08, 0xa0, 0xec, 0x9d, 0xb9, 0xda, 0xa8, 0xa7, 0x50, + 0xa9, 0x34, 0x03, 0x46, 0xa3, 0xfc, 0x81, 0x61, 0x55, 0xed, 0xa9, 0xd4, 0x15, 0xa7, 0xc6, 0xf5, + 0x08, 0xc0, 0xf2, 0xd0, 0xad, 0xc7, 0x38, 0xf2, 0xc5, 0x18, 0x5f, 0xc2, 0xa7, 0x20, 0xb1, 0x8f, + 0x25, 0x6e, 0xdc, 0x99, 0x67, 0x00, 0x6e, 0xcc, 0x58, 0x90, 0xdd, 0x99, 0x4c, 0x83, 0xca, 0x3b, + 0xf3, 0x08, 0x60, 0xc8, 0x58, 0xa0, 0xee, 0x8c, 0xd9, 0x45, 0xcb, 0x29, 0x70, 0x51, 0x55, 0x70, + 0x31, 0xb4, 0x7e, 0x81, 0xda, 0x5a, 0x34, 0x8e, 0xc8, 0xa5, 0x30, 0x77, 0x50, 0x5b, 0x31, 0x81, + 0x10, 0x20, 0xba, 0x86, 0xd6, 0x9a, 0x6a, 0xc2, 0x92, 0x68, 0xfb, 0xa6, 0xc7, 0x9b, 0x30, 0x9f, + 0xa2, 0xe5, 0x18, 0xf4, 0xcb, 0x42, 0x1b, 0x76, 0x0e, 0x3f, 0xb0, 0x67, 0x78, 0x20, 0xdb, 0x37, + 0x11, 0x3a, 0x05, 0x9b, 0xc5, 0xab, 0x27, 0xe3, 0x54, 0x43, 0x13, 0xe6, 0xc5, 0xb4, 0xd3, 0x5f, + 0xbf, 0x91, 0xd3, 0x29, 0xbe, 0xca, 0xe7, 0xbb, 0xa8, 0x73, 0x94, 0x6d, 0xfb, 0x37, 0x54, 0xc8, + 0xab, 0xc7, 0xb2, 0x5a, 0x3f, 0x96, 0x4f, 0xd0, 0x7a, 0xde, 0x87, 0xcf, 0x99, 0x16, 0x3e, 0xf3, + 0xa7, 0x08, 0xe5, 0x0d, 0x5c, 0x09, 0x66, 0x96, 0x96, 0x76, 0x3e, 0x73, 0xe2, 0x37, 0xfa, 0xe9, + 0x7c, 0xa3, 0x9f, 0x5a, 0x0e, 0xda, 0xb8, 0x10, 0xe4, 0xb7, 0xc5, 0x23, 0xed, 0x49, 0x2c, 0xcc, + 0x3b, 0x68, 0x49, 0xdd, 0xd5, 0x9c, 0x68, 0xc1, 0x59, 0x4c, 0x05, 0x39, 0xd1, 0xdd, 0xa1, 0x7a, + 0x08, 0xb2, 0xd8, 0xa5, 0xbe, 0xe8, 0xce, 0xef, 0xb5, 0xf6, 0x17, 0x9c, 0xf5, 0xa4, 0x32, 0x3f, + 0xf1, 0x85, 0xf5, 0x3b, 0xd4, 0xa9, 0x11, 0x9a, 0xeb, 0x68, 0xbe, 0xe4, 0x9a, 0xa7, 0xbe, 0xf9, + 0x00, 0x6d, 0x57, 0x44, 0x4d, 0xb9, 0xcf, 0x18, 0xdb, 0xce, 0xdd, 0x12, 0xd0, 0x50, 0x7c, 0x61, + 0x3d, 0x41, 0x5b, 0x27, 0x95, 0xb8, 0x94, 0xcd, 0xa4, 0xb1, 0x43, 0xa3, 0xf9, 0x62, 0xd8, 0x41, + 0xed, 0xf2, 0xd7, 0x8e, 0xde, 0xfd, 0x82, 0x53, 0x4d, 0x58, 0x21, 0xda, 0xbc, 0x10, 0xe4, 0x0c, + 0x22, 0xbf, 0x22, 0xbb, 0xe1, 0x00, 0x8e, 0xa7, 0x89, 0x66, 0x7e, 0x4d, 0x57, 0xee, 0xfe, 0x6c, + 0xa0, 0xee, 0x29, 0x4c, 0x8e, 0x84, 0xa0, 0xa3, 0x28, 0x84, 0x48, 0x2a, 0xb1, 0xc0, 0x04, 0xd4, + 0xa7, 0xf9, 0x36, 0x5a, 0x2b, 0x2f, 0x5a, 0x79, 0xbf, 0x56, 0x9d, 0xd5, 0x62, 0x52, 0x5f, 0xac, + 0x07, 0x08, 0xc5, 0x1c, 0x52, 0x97, 0xb8, 0x97, 0x30, 0xc9, 0xc3, 0xd8, 0xa9, 0xf7, 0x9a, 0xec, + 0xc7, 0x94, 0x3d, 0x4c, 0xbc, 0x80, 0x92, 0x53, 0x98, 0x38, 0x2b, 0x0a, 0x3f, 0x38, 0x85, 0x89, + 0x7a, 0x3c, 0xc4, 0xec, 0x05, 0x70, 0xdd, 0x20, 0x5a, 0x4e, 0x36, 0xb0, 0xfe, 0x62, 0xa0, 0xbb, + 0x17, 0x38, 0xa0, 0x3e, 0x96, 0x8c, 0x17, 0xe7, 0x3d, 0x4c, 0x3c, 0x65, 0xf1, 0x3d, 0xe7, 0x7a, + 0x25, 0xda, 0xf9, 0x6b, 0xa2, 0xfd, 0x10, 0xad, 0x96, 0x19, 0x56, 0xf1, 0xb6, 0x66, 0x88, 0xb7, + 0x53, 0x58, 0x9c, 0xc2, 0xc4, 0xfa, 0x63, 0x2d, 0xb6, 0xe3, 0x49, 0xed, 0xf2, 0xf2, 0xff, 0x11, + 0x5b, 0xe9, 0xb6, 0x1e, 0x1b, 0xa9, 0xdb, 0x5f, 0xd9, 0x40, 0xeb, 0xea, 0x06, 0xac, 0xbf, 0x19, + 0x68, 0xab, 0xee, 0x55, 0x9c, 0xb3, 0x21, 0x4f, 0x22, 0xf8, 0x3e, 0xef, 0x55, 0xfd, 0xcc, 0xd7, + 0xeb, 0xe7, 0x29, 0x5a, 0x6f, 0x04, 0x25, 0xf2, 0xd3, 0xf8, 0xe5, 0x4c, 0x12, 0x52, 0x93, 0x07, + 0x67, 0xad, 0xbe, 0x0f, 0x71, 0xfc, 0xf4, 0xeb, 0x57, 0x3d, 0xe3, 0x9b, 0x57, 0x3d, 0xe3, 0x5f, + 0xaf, 0x7a, 0xc6, 0x17, 0xaf, 0x7b, 0x73, 0xdf, 0xbc, 0xee, 0xcd, 0xfd, 0xf3, 0x75, 0x6f, 0xee, + 0xf7, 0x1f, 0x8c, 0xa8, 0x1c, 0x27, 0x9e, 0x4d, 0x58, 0xd8, 0xcf, 0x7f, 0x29, 0x57, 0xbe, 0xde, + 0x2b, 0xff, 0x78, 0x48, 0x0f, 0xfb, 0x2f, 0x9b, 0xff, 0x3e, 0xc8, 0x49, 0x0c, 0xc2, 0x5b, 0xd2, + 0x65, 0xfd, 0xfe, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x09, 0x3c, 0x54, 0xc1, 0xae, 0x10, 0x00, + 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -1557,6 +1636,61 @@ func (m *EquivocationProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ChangeRewardDenomsProposal) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ChangeRewardDenomsProposal) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ChangeRewardDenomsProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.DenomsToRemove) > 0 { + for iNdEx := len(m.DenomsToRemove) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.DenomsToRemove[iNdEx]) + copy(dAtA[i:], m.DenomsToRemove[iNdEx]) + i = encodeVarintProvider(dAtA, i, uint64(len(m.DenomsToRemove[iNdEx]))) + i-- + dAtA[i] = 0x22 + } + } + if len(m.DenomsToAdd) > 0 { + for iNdEx := len(m.DenomsToAdd) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.DenomsToAdd[iNdEx]) + copy(dAtA[i:], m.DenomsToAdd[iNdEx]) + i = encodeVarintProvider(dAtA, i, uint64(len(m.DenomsToAdd[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } + if len(m.Description) > 0 { + i -= len(m.Description) + copy(dAtA[i:], m.Description) + i = encodeVarintProvider(dAtA, i, uint64(len(m.Description))) + i-- + dAtA[i] = 0x12 + } + if len(m.Title) > 0 { + i -= len(m.Title) + copy(dAtA[i:], m.Title) + i = encodeVarintProvider(dAtA, i, uint64(len(m.Title))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *GlobalSlashEntry) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -2366,6 +2500,35 @@ func (m *EquivocationProposal) Size() (n int) { return n } +func (m *ChangeRewardDenomsProposal) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Title) + if l > 0 { + n += 1 + l + sovProvider(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovProvider(uint64(l)) + } + if len(m.DenomsToAdd) > 0 { + for _, s := range m.DenomsToAdd { + l = len(s) + n += 1 + l + sovProvider(uint64(l)) + } + } + if len(m.DenomsToRemove) > 0 { + for _, s := range m.DenomsToRemove { + l = len(s) + n += 1 + l + sovProvider(uint64(l)) + } + } + return n +} + func (m *GlobalSlashEntry) Size() (n int) { if m == nil { return 0 @@ -3479,6 +3642,184 @@ func (m *EquivocationProposal) Unmarshal(dAtA []byte) error { } return nil } +func (m *ChangeRewardDenomsProposal) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ChangeRewardDenomsProposal: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ChangeRewardDenomsProposal: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProvider + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthProvider + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Title = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProvider + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthProvider + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DenomsToAdd", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProvider + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthProvider + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DenomsToAdd = append(m.DenomsToAdd, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DenomsToRemove", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProvider + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthProvider + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DenomsToRemove = append(m.DenomsToRemove, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProvider(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthProvider + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *GlobalSlashEntry) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index 3603695359..40314e1c41 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -111,91 +111,9 @@ func (m *MsgAssignConsumerKeyResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgAssignConsumerKeyResponse proto.InternalMessageInfo -// MsgRegisterConsumerRewardDenom allows an account to register -// a consumer reward denom, i.e., add it to the list of denoms -// accepted by the provider as rewards. -type MsgRegisterConsumerRewardDenom struct { - Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` - Depositor string `protobuf:"bytes,2,opt,name=depositor,proto3" json:"depositor,omitempty"` -} - -func (m *MsgRegisterConsumerRewardDenom) Reset() { *m = MsgRegisterConsumerRewardDenom{} } -func (m *MsgRegisterConsumerRewardDenom) String() string { return proto.CompactTextString(m) } -func (*MsgRegisterConsumerRewardDenom) ProtoMessage() {} -func (*MsgRegisterConsumerRewardDenom) Descriptor() ([]byte, []int) { - return fileDescriptor_43221a4391e9fbf4, []int{2} -} -func (m *MsgRegisterConsumerRewardDenom) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgRegisterConsumerRewardDenom) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgRegisterConsumerRewardDenom.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgRegisterConsumerRewardDenom) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgRegisterConsumerRewardDenom.Merge(m, src) -} -func (m *MsgRegisterConsumerRewardDenom) XXX_Size() int { - return m.Size() -} -func (m *MsgRegisterConsumerRewardDenom) XXX_DiscardUnknown() { - xxx_messageInfo_MsgRegisterConsumerRewardDenom.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgRegisterConsumerRewardDenom proto.InternalMessageInfo - -// MsgRegisterConsumerRewardDenomResponse defines the Msg/RegisterConsumerRewardDenom response type. -type MsgRegisterConsumerRewardDenomResponse struct { -} - -func (m *MsgRegisterConsumerRewardDenomResponse) Reset() { - *m = MsgRegisterConsumerRewardDenomResponse{} -} -func (m *MsgRegisterConsumerRewardDenomResponse) String() string { return proto.CompactTextString(m) } -func (*MsgRegisterConsumerRewardDenomResponse) ProtoMessage() {} -func (*MsgRegisterConsumerRewardDenomResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_43221a4391e9fbf4, []int{3} -} -func (m *MsgRegisterConsumerRewardDenomResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgRegisterConsumerRewardDenomResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgRegisterConsumerRewardDenomResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgRegisterConsumerRewardDenomResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgRegisterConsumerRewardDenomResponse.Merge(m, src) -} -func (m *MsgRegisterConsumerRewardDenomResponse) XXX_Size() int { - return m.Size() -} -func (m *MsgRegisterConsumerRewardDenomResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgRegisterConsumerRewardDenomResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgRegisterConsumerRewardDenomResponse proto.InternalMessageInfo - func init() { proto.RegisterType((*MsgAssignConsumerKey)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKey") proto.RegisterType((*MsgAssignConsumerKeyResponse)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKeyResponse") - proto.RegisterType((*MsgRegisterConsumerRewardDenom)(nil), "interchain_security.ccv.provider.v1.MsgRegisterConsumerRewardDenom") - proto.RegisterType((*MsgRegisterConsumerRewardDenomResponse)(nil), "interchain_security.ccv.provider.v1.MsgRegisterConsumerRewardDenomResponse") } func init() { @@ -203,36 +121,31 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 453 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x3d, 0x6b, 0x14, 0x41, - 0x18, 0xc7, 0x77, 0x13, 0xd4, 0x64, 0x8c, 0x82, 0xc3, 0x15, 0x97, 0xf3, 0xd8, 0xd3, 0x15, 0x24, - 0x85, 0xee, 0x90, 0x58, 0x88, 0x01, 0x8b, 0x4b, 0x6c, 0x24, 0x5c, 0xb3, 0x8d, 0x60, 0xe1, 0xb1, - 0x37, 0x33, 0x4e, 0x06, 0xb3, 0xf3, 0x2c, 0xf3, 0xcc, 0xad, 0xd9, 0x6f, 0x60, 0xa9, 0x95, 0x6d, - 0xbe, 0x81, 0x5f, 0x43, 0xb0, 0x49, 0x69, 0x25, 0x72, 0xd7, 0x58, 0xfb, 0x09, 0x64, 0xdf, 0x3c, - 0xc5, 0xe3, 0x08, 0x92, 0xee, 0x79, 0xdb, 0xff, 0xff, 0xb7, 0x33, 0xf3, 0x90, 0x07, 0xda, 0x38, - 0x69, 0xf9, 0x71, 0xa2, 0xcd, 0x18, 0x25, 0x9f, 0x5a, 0xed, 0x0a, 0xc6, 0x79, 0xce, 0x32, 0x0b, - 0xb9, 0x16, 0xd2, 0xb2, 0x7c, 0x97, 0xb9, 0xd3, 0x28, 0xb3, 0xe0, 0x80, 0xde, 0x5b, 0x32, 0x1d, - 0x71, 0x9e, 0x47, 0xed, 0x74, 0x94, 0xef, 0xf6, 0xfa, 0x0a, 0x40, 0x9d, 0x48, 0x96, 0x64, 0x9a, - 0x25, 0xc6, 0x80, 0x4b, 0x9c, 0x06, 0x83, 0xb5, 0x44, 0xaf, 0xa3, 0x40, 0x41, 0x15, 0xb2, 0x32, - 0x6a, 0xaa, 0xdb, 0x1c, 0x30, 0x05, 0x1c, 0xd7, 0x8d, 0x3a, 0x69, 0x5b, 0x8d, 0x5c, 0x95, 0x4d, - 0xa6, 0xaf, 0x59, 0x62, 0x8a, 0xba, 0x15, 0x7e, 0xf4, 0x49, 0x67, 0x84, 0x6a, 0x88, 0xa8, 0x95, - 0x39, 0x04, 0x83, 0xd3, 0x54, 0xda, 0x23, 0x59, 0xd0, 0x6d, 0xb2, 0x51, 0x43, 0x6a, 0xd1, 0xf5, - 0xef, 0xf8, 0x3b, 0x9b, 0xf1, 0xb5, 0x2a, 0x7f, 0x2e, 0xe8, 0x63, 0x72, 0xa3, 0x85, 0x1d, 0x27, - 0x42, 0xd8, 0xee, 0x5a, 0xd9, 0x3f, 0xa0, 0x3f, 0xbf, 0x0d, 0x6e, 0x16, 0x49, 0x7a, 0xb2, 0x1f, - 0x96, 0x55, 0x89, 0x18, 0xc6, 0x5b, 0xed, 0xe0, 0x50, 0x08, 0x4b, 0xef, 0x92, 0x2d, 0xde, 0x58, - 0x8c, 0xdf, 0xc8, 0xa2, 0xbb, 0x5e, 0xe9, 0x5e, 0xe7, 0x0b, 0xdb, 0xfd, 0x8d, 0x77, 0x67, 0x03, - 0xef, 0xc7, 0xd9, 0xc0, 0x0b, 0x03, 0xd2, 0x5f, 0x06, 0x16, 0x4b, 0xcc, 0xc0, 0xa0, 0x0c, 0x5f, - 0x91, 0x60, 0x84, 0x2a, 0x96, 0x4a, 0xa3, 0x93, 0xb6, 0x9d, 0x88, 0xe5, 0xdb, 0xc4, 0x8a, 0x67, - 0xd2, 0x40, 0x4a, 0x3b, 0xe4, 0x8a, 0x28, 0x83, 0x86, 0xbf, 0x4e, 0x68, 0x9f, 0x6c, 0x0a, 0x99, - 0x01, 0x6a, 0x07, 0x0d, 0x79, 0xbc, 0x28, 0xfc, 0xe1, 0xbf, 0x43, 0xee, 0xaf, 0xd6, 0x6f, 0x49, - 0xf6, 0xbe, 0xac, 0x91, 0xf5, 0x11, 0x2a, 0xfa, 0xc1, 0x27, 0xb7, 0xfe, 0x3d, 0xc8, 0x27, 0xd1, - 0x05, 0x6e, 0x3c, 0x5a, 0xf6, 0xab, 0xbd, 0xe1, 0x7f, 0x7f, 0xda, 0xb2, 0xd1, 0x4f, 0x3e, 0xb9, - 0xbd, 0xea, 0x8c, 0x0e, 0x2f, 0x6a, 0xb1, 0x42, 0xa4, 0x77, 0x74, 0x09, 0x22, 0x2d, 0xf1, 0xc1, - 0x8b, 0xcf, 0xb3, 0xc0, 0x3f, 0x9f, 0x05, 0xfe, 0xf7, 0x59, 0xe0, 0xbf, 0x9f, 0x07, 0xde, 0xf9, - 0x3c, 0xf0, 0xbe, 0xce, 0x03, 0xef, 0xe5, 0x53, 0xa5, 0xdd, 0xf1, 0x74, 0x12, 0x71, 0x48, 0x9b, - 0xf7, 0xcd, 0x16, 0xbe, 0x0f, 0x7f, 0xaf, 0x5e, 0xbe, 0xc7, 0x4e, 0xff, 0xde, 0x3f, 0x57, 0x64, - 0x12, 0x27, 0x57, 0xab, 0x17, 0xff, 0xe8, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7a, 0x53, 0xb5, - 0xb8, 0xb0, 0x03, 0x00, 0x00, + // 375 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x52, 0x3d, 0x4f, 0xeb, 0x30, + 0x14, 0x8d, 0x5f, 0xa5, 0xf7, 0xfa, 0xfc, 0xfa, 0x9e, 0xf4, 0xa2, 0x0e, 0x6d, 0x55, 0xa5, 0x10, + 0x16, 0x06, 0x88, 0xd5, 0x32, 0x20, 0x2a, 0x31, 0xb4, 0x4c, 0x08, 0x75, 0xe9, 0x82, 0xc4, 0x12, + 0xa5, 0x8e, 0x71, 0x2d, 0x1a, 0x3b, 0xb2, 0x9d, 0xa8, 0xf9, 0x07, 0x8c, 0x30, 0x21, 0xb6, 0xfe, + 0x1c, 0xc6, 0x8e, 0x4c, 0x08, 0xb5, 0x0b, 0x33, 0xbf, 0x00, 0x35, 0x1f, 0x54, 0x88, 0x0e, 0x88, + 0xed, 0xde, 0x7b, 0x8e, 0xcf, 0x39, 0xf2, 0xbd, 0x70, 0x8f, 0x71, 0x4d, 0x24, 0x1e, 0x7b, 0x8c, + 0xbb, 0x8a, 0xe0, 0x48, 0x32, 0x9d, 0x20, 0x8c, 0x63, 0x14, 0x4a, 0x11, 0x33, 0x9f, 0x48, 0x14, + 0xb7, 0x91, 0x9e, 0x3a, 0xa1, 0x14, 0x5a, 0x98, 0x3b, 0x1b, 0xd8, 0x0e, 0xc6, 0xb1, 0x53, 0xb0, + 0x9d, 0xb8, 0xdd, 0x68, 0x52, 0x21, 0xe8, 0x84, 0x20, 0x2f, 0x64, 0xc8, 0xe3, 0x5c, 0x68, 0x4f, + 0x33, 0xc1, 0x55, 0x26, 0xd1, 0xa8, 0x52, 0x41, 0x45, 0x5a, 0xa2, 0x55, 0x95, 0x4f, 0xeb, 0x58, + 0xa8, 0x40, 0x28, 0x37, 0x03, 0xb2, 0xa6, 0x80, 0x72, 0xb9, 0xb4, 0x1b, 0x45, 0x97, 0xc8, 0xe3, + 0x49, 0x06, 0xd9, 0x77, 0x00, 0x56, 0x07, 0x8a, 0xf6, 0x94, 0x62, 0x94, 0x9f, 0x08, 0xae, 0xa2, + 0x80, 0xc8, 0x33, 0x92, 0x98, 0x75, 0x58, 0xce, 0x42, 0x32, 0xbf, 0x06, 0xb6, 0xc0, 0xee, 0xef, + 0xe1, 0xaf, 0xb4, 0x3f, 0xf5, 0xcd, 0x43, 0xf8, 0xb7, 0x08, 0xeb, 0x7a, 0xbe, 0x2f, 0x6b, 0x3f, + 0x56, 0x78, 0xdf, 0x7c, 0x7d, 0x6a, 0xfd, 0x4b, 0xbc, 0x60, 0xd2, 0xb5, 0x57, 0x53, 0xa2, 0x94, + 0x3d, 0xac, 0x14, 0xc4, 0x9e, 0xef, 0x4b, 0x73, 0x1b, 0x56, 0x70, 0x6e, 0xe1, 0x5e, 0x91, 0xa4, + 0x56, 0x4a, 0x75, 0xff, 0xe0, 0xb5, 0x6d, 0xb7, 0x7c, 0x3d, 0x6b, 0x19, 0x2f, 0xb3, 0x96, 0x61, + 0x5b, 0xb0, 0xb9, 0x29, 0xd8, 0x90, 0xa8, 0x50, 0x70, 0x45, 0x3a, 0xf7, 0x00, 0x96, 0x06, 0x8a, + 0x9a, 0xb7, 0x00, 0xfe, 0xff, 0x1c, 0xff, 0xc8, 0xf9, 0xc2, 0x3f, 0x3b, 0x9b, 0x0c, 0x1a, 0xbd, + 0x6f, 0x3f, 0x2d, 0xb2, 0xf5, 0xcf, 0x1f, 0x16, 0x16, 0x98, 0x2f, 0x2c, 0xf0, 0xbc, 0xb0, 0xc0, + 0xcd, 0xd2, 0x32, 0xe6, 0x4b, 0xcb, 0x78, 0x5c, 0x5a, 0xc6, 0xc5, 0x31, 0x65, 0x7a, 0x1c, 0x8d, + 0x1c, 0x2c, 0x82, 0x7c, 0x47, 0x68, 0xed, 0xb6, 0xff, 0x7e, 0x3e, 0x71, 0x07, 0x4d, 0x3f, 0xde, + 0x90, 0x4e, 0x42, 0xa2, 0x46, 0x3f, 0xd3, 0xad, 0x1d, 0xbc, 0x05, 0x00, 0x00, 0xff, 0xff, 0x9a, + 0x29, 0xaf, 0xde, 0x74, 0x02, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -248,7 +161,6 @@ const _ = grpc.SupportPackageIsVersion4 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { AssignConsumerKey(ctx context.Context, in *MsgAssignConsumerKey, opts ...grpc.CallOption) (*MsgAssignConsumerKeyResponse, error) - RegisterConsumerRewardDenom(ctx context.Context, in *MsgRegisterConsumerRewardDenom, opts ...grpc.CallOption) (*MsgRegisterConsumerRewardDenomResponse, error) } type msgClient struct { @@ -268,19 +180,9 @@ func (c *msgClient) AssignConsumerKey(ctx context.Context, in *MsgAssignConsumer return out, nil } -func (c *msgClient) RegisterConsumerRewardDenom(ctx context.Context, in *MsgRegisterConsumerRewardDenom, opts ...grpc.CallOption) (*MsgRegisterConsumerRewardDenomResponse, error) { - out := new(MsgRegisterConsumerRewardDenomResponse) - err := c.cc.Invoke(ctx, "/interchain_security.ccv.provider.v1.Msg/RegisterConsumerRewardDenom", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - // MsgServer is the server API for Msg service. type MsgServer interface { AssignConsumerKey(context.Context, *MsgAssignConsumerKey) (*MsgAssignConsumerKeyResponse, error) - RegisterConsumerRewardDenom(context.Context, *MsgRegisterConsumerRewardDenom) (*MsgRegisterConsumerRewardDenomResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -290,9 +192,6 @@ type UnimplementedMsgServer struct { func (*UnimplementedMsgServer) AssignConsumerKey(ctx context.Context, req *MsgAssignConsumerKey) (*MsgAssignConsumerKeyResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AssignConsumerKey not implemented") } -func (*UnimplementedMsgServer) RegisterConsumerRewardDenom(ctx context.Context, req *MsgRegisterConsumerRewardDenom) (*MsgRegisterConsumerRewardDenomResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method RegisterConsumerRewardDenom not implemented") -} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -316,24 +215,6 @@ func _Msg_AssignConsumerKey_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } -func _Msg_RegisterConsumerRewardDenom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgRegisterConsumerRewardDenom) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).RegisterConsumerRewardDenom(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/interchain_security.ccv.provider.v1.Msg/RegisterConsumerRewardDenom", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).RegisterConsumerRewardDenom(ctx, req.(*MsgRegisterConsumerRewardDenom)) - } - return interceptor(ctx, in, info, handler) -} - var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "interchain_security.ccv.provider.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -342,10 +223,6 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "AssignConsumerKey", Handler: _Msg_AssignConsumerKey_Handler, }, - { - MethodName: "RegisterConsumerRewardDenom", - Handler: _Msg_RegisterConsumerRewardDenom_Handler, - }, }, Streams: []grpc.StreamDesc{}, Metadata: "interchain_security/ccv/provider/v1/tx.proto", @@ -418,66 +295,6 @@ func (m *MsgAssignConsumerKeyResponse) MarshalToSizedBuffer(dAtA []byte) (int, e return len(dAtA) - i, nil } -func (m *MsgRegisterConsumerRewardDenom) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgRegisterConsumerRewardDenom) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgRegisterConsumerRewardDenom) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Depositor) > 0 { - i -= len(m.Depositor) - copy(dAtA[i:], m.Depositor) - i = encodeVarintTx(dAtA, i, uint64(len(m.Depositor))) - i-- - dAtA[i] = 0x12 - } - if len(m.Denom) > 0 { - i -= len(m.Denom) - copy(dAtA[i:], m.Denom) - i = encodeVarintTx(dAtA, i, uint64(len(m.Denom))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgRegisterConsumerRewardDenomResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgRegisterConsumerRewardDenomResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgRegisterConsumerRewardDenomResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -519,32 +336,6 @@ func (m *MsgAssignConsumerKeyResponse) Size() (n int) { return n } -func (m *MsgRegisterConsumerRewardDenom) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Denom) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.Depositor) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - return n -} - -func (m *MsgRegisterConsumerRewardDenomResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -747,170 +538,6 @@ func (m *MsgAssignConsumerKeyResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgRegisterConsumerRewardDenom) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgRegisterConsumerRewardDenom: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgRegisterConsumerRewardDenom: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Denom = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Depositor", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Depositor = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgRegisterConsumerRewardDenomResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgRegisterConsumerRewardDenomResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgRegisterConsumerRewardDenomResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/ccv/types/events.go b/x/ccv/types/events.go index ba71e063f3..23962fbbaf 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -2,13 +2,14 @@ package types // CCV events const ( - EventTypeTimeout = "timeout" - EventTypePacket = "ccv_packet" - EventTypeChannelEstablished = "channel_established" - EventTypeFeeTransferChannelOpened = "fee_transfer_channel_opened" - EventTypeConsumerClientCreated = "consumer_client_created" - EventTypeAssignConsumerKey = "assign_consumer_key" - EventTypeRegisterConsumerRewardDenom = "register_consumer_reward_denom" + EventTypeTimeout = "timeout" + EventTypePacket = "ccv_packet" + EventTypeChannelEstablished = "channel_established" + EventTypeFeeTransferChannelOpened = "fee_transfer_channel_opened" + EventTypeConsumerClientCreated = "consumer_client_created" + EventTypeAssignConsumerKey = "assign_consumer_key" + EventTypeAddConsumerRewardDenom = "add_consumer_reward_denom" + EventTypeRemoveConsumerRewardDenom = "remove_consumer_reward_denom" EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash" EventTypeFeeDistribution = "fee_distribution" @@ -40,6 +41,5 @@ const ( AttributeDistributionTotal = "total" AttributeDistributionToProvider = "provider_amount" - AttributeConsumerRewardDenom = "consumer_reward_denom" - AttributeConsumerRewardDepositor = "consumer_reward_depositor" + AttributeConsumerRewardDenom = "consumer_reward_denom" ) From a71f1feb8156ab4e01d00d91a83a4350d5d7147a Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 13 Sep 2023 17:24:15 +0200 Subject: [PATCH 11/40] save --- proto/interchain_security/ccv/provider/v1/tx.proto | 8 ++++---- tests/e2e/actions.go | 10 +++++----- tests/e2e/actions_consumer_misbehaviour.go | 2 ++ tests/e2e/main.go | 4 ++-- tests/e2e/steps_consumer_misbehaviour.go | 6 +++--- tests/e2e/steps_double_sign.go | 2 +- x/ccv/provider/client/cli/tx.go | 12 +++++++++++- x/ccv/provider/keeper/double_vote.go | 5 +---- x/ccv/provider/keeper/misbehaviour.go | 4 ++-- x/ccv/provider/types/codec.go | 5 +++++ 10 files changed, 36 insertions(+), 22 deletions(-) diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 64e4c88b70..12187ac9b5 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -49,8 +49,8 @@ message MsgRegisterConsumerRewardDenom { // MsgRegisterConsumerRewardDenomResponse defines the Msg/RegisterConsumerRewardDenom response type. message MsgRegisterConsumerRewardDenomResponse {} -// MsgSubmitConsumerMisbehaviour defines a message that reports a misbehaviour -// observed on a consumer chain +// MsgSubmitConsumerMisbehaviour defines a message that reports a light client attack, +// also known as misbehaviour, observed on a consumer chain // Note that the misbheaviour' headers must contain the same trusted states message MsgSubmitConsumerMisbehaviour { option (gogoproto.equal) = false; @@ -64,8 +64,8 @@ message MsgSubmitConsumerMisbehaviour { message MsgSubmitConsumerMisbehaviourResponse {} -// MsgSubmitConsumerDoubleVoting defines a message that reports an equivocation -// observed on a consumer chain +// MsgSubmitConsumerDoubleVoting defines a message that reports +// a double signing infraction observed on a consumer chain message MsgSubmitConsumerDoubleVoting { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 0c47b452e2..20740d7196 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -524,7 +524,7 @@ func (tr TestRun) voteGovProposal( } wg.Wait() - time.Sleep((time.Duration(tr.chainConfigs[action.chain].votingWaitTime)) * time.Second) + time.Sleep(time.Duration(tr.chainConfigs[action.chain].votingWaitTime) * time.Second) } type startConsumerChainAction struct { @@ -841,7 +841,6 @@ func (tr TestRun) addChainToHermes( saveMnemonicCommand := fmt.Sprintf(`echo '%s' > %s`, mnemonic, "/root/.hermes/mnemonic.txt") fmt.Println("Add to hermes", action.validator) - fmt.Println(mnemonic) //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. bz, err = exec.Command("docker", "exec", tr.containerConfig.instanceName, "bash", "-c", saveMnemonicCommand, @@ -1849,15 +1848,16 @@ func (tr TestRun) GetPathNameForGorelayer(chainA, chainB chainID) string { // which detects evidences committed to the blocks of a consumer chain. // Each infraction detected is reported to the provider chain using // either a SubmitConsumerDoubleVoting or a SubmitConsumerMisbehaviour message. -type detectConsumerEvidenceAction struct { +type startConsumerEvidenceDetectorAction struct { chain chainID } -func (tr TestRun) detectConsumerEvidence( - action detectConsumerEvidenceAction, +func (tr TestRun) startConsumerEvidenceDetector( + action startConsumerEvidenceDetectorAction, verbose bool, ) { chainConfig := tr.chainConfigs[action.chain] + // run in detached mode so it will keep running in the background //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. bz, err := exec.Command("docker", "exec", "-d", tr.containerConfig.instanceName, "hermes", "evidence", "--chain", string(chainConfig.chainId)).CombinedOutput() diff --git a/tests/e2e/actions_consumer_misbehaviour.go b/tests/e2e/actions_consumer_misbehaviour.go index 0da2f9e56f..b5c9efbc2a 100644 --- a/tests/e2e/actions_consumer_misbehaviour.go +++ b/tests/e2e/actions_consumer_misbehaviour.go @@ -9,6 +9,8 @@ import ( "time" ) +// forkConsumerChainAction forks the consumer chain by cloning of a validator node +// Note that the chain fork is running in an different network type forkConsumerChainAction struct { consumerChain chainID providerChain chainID diff --git a/tests/e2e/main.go b/tests/e2e/main.go index ac9becdd5e..be868a8100 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -182,8 +182,8 @@ func (tr *TestRun) runStep(step Step, verbose bool) { tr.updateLightClient(action, verbose) case assertChainIsHaltedAction: tr.assertChainIsHalted(action, verbose) - case detectConsumerEvidenceAction: - tr.detectConsumerEvidence(action, verbose) + case startConsumerEvidenceDetectorAction: + tr.startConsumerEvidenceDetector(action, verbose) default: log.Fatalf("unknown action in testRun %s: %#v", tr.name, action) } diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index 53cfb78fae..92402d6dc6 100644 --- a/tests/e2e/steps_consumer_misbehaviour.go +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -204,7 +204,7 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { forkRelayerConfig := "/root/.hermes/config_fork.toml" return []Step{ { - // fork the consumer chain by cloning of its validator node + // fork the consumer chain by cloning the alice validator node action: forkConsumerChainAction{ consumerChain: chainID(consumerName), providerChain: chainID("provi"), @@ -218,10 +218,10 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { action: startRelayerAction{}, state: State{}, }, - // detect the ICS misbehaviour + // run Hermes relayer instance to detect the ICS misbehaviour // and jail alice on the provider { - action: detectConsumerEvidenceAction{ + action: startConsumerEvidenceDetectorAction{ chain: chainID(consumerName), }, state: State{ diff --git a/tests/e2e/steps_double_sign.go b/tests/e2e/steps_double_sign.go index dcce236b46..2adcac14cb 100644 --- a/tests/e2e/steps_double_sign.go +++ b/tests/e2e/steps_double_sign.go @@ -157,7 +157,7 @@ func stepsCauseDoubleSignOnConsumer(consumerName, providerName string) []Step { // detect the double voting infraction // and jail bob on the provider { - action: detectConsumerEvidenceAction{ + action: startConsumerEvidenceDetectorAction{ chain: chainID(consumerName), }, state: State{ diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index a4b6e233e0..01b62c7181 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -155,7 +155,17 @@ func NewSubmitConsumerDoubleVotingCmd() *cobra.Command { cmd := &cobra.Command{ Use: "submit-consumer-double-voting [evidence] [infraction_header]", Short: "submit a double voting evidence for a consumer chain", - Args: cobra.ExactArgs(2), + Long: strings.TrimSpace( + fmt.Sprintf(`Submit a Tendermint duplicated vote evidence detected on a consumer chain along with + an IBC light client header at the infraction height. + The DuplicateVoteEvidence type definition can be found in the Tendermint type messages, + see cometbft/proto/tendermint/types/evidence.proto and the light client header + definition is available in ibc-go/proto/ibc/lightclients/tendermint/v1/tendermint.proto. + +Examples: +%s tx provider submit-consumer-double-voting [path/to/evidence.json] --from node0 --home ../node0 --chain-id $CID +`, version.AppName)), + Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index ee5f716ef8..d61019e239 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -59,9 +59,6 @@ func (k Keeper) VerifyDoubleVotingEvidence( // Note that since we're only jailing validators for double voting on a consumer chain, // the age of the evidence is irrelevant and therefore isn't checked. - // TODO: check the age of the evidence once we slash - // validators for double voting on a consumer chain - // H/R/S must be the same if evidence.VoteA.Height != evidence.VoteB.Height || evidence.VoteA.Round != evidence.VoteB.Round || @@ -73,7 +70,7 @@ func (k Keeper) VerifyDoubleVotingEvidence( evidence.VoteB.Height, evidence.VoteB.Round, evidence.VoteB.Type) } - // Address must be the same + // Addresses must be the same if !bytes.Equal(evidence.VoteA.ValidatorAddress, evidence.VoteB.ValidatorAddress) { return sdkerrors.Wrapf( ccvtypes.ErrInvalidEvidence, diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 35d9219324..3a40b890e9 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -11,7 +11,7 @@ import ( ) // HandleConsumerMisbehaviour checks if the given IBC misbehaviour corresponds to an equivocation light client attack, -// and in this case, jails and tombstones the Byzantine validators +// and in this case, jails the Byzantine validators func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { logger := k.Logger(ctx) @@ -34,7 +34,7 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty provAddrs := make([]types.ProviderConsAddress, len(byzantineValidators)) - // jail and tombstone the Byzantine validators + // jailthe Byzantine validators for _, v := range byzantineValidators { providerAddr := k.GetProviderAddrFromConsumerAddr( ctx, diff --git a/x/ccv/provider/types/codec.go b/x/ccv/provider/types/codec.go index 0ef3c2d296..d0e243723a 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -41,6 +41,11 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { &MsgSubmitConsumerMisbehaviour{}, ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgSubmitConsumerDoubleVoting{}, + ) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } From 3be76ada57cb7cdfea3c57e31df9e68ad0f296e3 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 14 Sep 2023 09:12:04 +0200 Subject: [PATCH 12/40] fix nits --- Dockerfile | 2 +- .../ccv/provider/v1/tx.proto | 3 +- x/ccv/provider/client/cli/tx.go | 12 +++--- x/ccv/provider/keeper/double_vote.go | 6 +-- x/ccv/provider/keeper/misbehaviour.go | 8 ++-- x/ccv/provider/keeper/msg_server.go | 6 +-- x/ccv/provider/types/msg.go | 5 +++ x/ccv/types/errors.go | 40 +++++++++---------- x/ccv/types/events.go | 1 + 9 files changed, 44 insertions(+), 39 deletions(-) diff --git a/Dockerfile b/Dockerfile index 03939617be..7f58c49632 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ RUN go mod tidy RUN make install # Get Hermes build -FROM otacrew/hermes-ics:latest AS hermes-builder +FROM otacrew/hermes-ics:evidence-cmd AS hermes-builder # Get CometMock FROM informalofftermatt/cometmock:latest as cometmock-builder diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 12187ac9b5..2c04394658 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -50,8 +50,7 @@ message MsgRegisterConsumerRewardDenom { message MsgRegisterConsumerRewardDenomResponse {} // MsgSubmitConsumerMisbehaviour defines a message that reports a light client attack, -// also known as misbehaviour, observed on a consumer chain -// Note that the misbheaviour' headers must contain the same trusted states +// also known as a misbehaviour, observed on a consumer chain message MsgSubmitConsumerMisbehaviour { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 01b62c7181..946d728153 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -156,14 +156,14 @@ func NewSubmitConsumerDoubleVotingCmd() *cobra.Command { Use: "submit-consumer-double-voting [evidence] [infraction_header]", Short: "submit a double voting evidence for a consumer chain", Long: strings.TrimSpace( - fmt.Sprintf(`Submit a Tendermint duplicated vote evidence detected on a consumer chain along with - an IBC light client header at the infraction height. - The DuplicateVoteEvidence type definition can be found in the Tendermint type messages, - see cometbft/proto/tendermint/types/evidence.proto and the light client header - definition is available in ibc-go/proto/ibc/lightclients/tendermint/v1/tendermint.proto. + fmt.Sprintf(`Submit a Tendermint duplicated vote evidence detected on a consumer chain with + the IBC light client header for the infraction height. + The DuplicateVoteEvidence type definition can be found in the Tendermint messages, + , see cometbft/proto/tendermint/types/evidence.proto and the IBC header + definition can be found in the IBC messages, see ibc-go/proto/ibc/lightclients/tendermint/v1/tendermint.proto. Examples: -%s tx provider submit-consumer-double-voting [path/to/evidence.json] --from node0 --home ../node0 --chain-id $CID +%s tx provider submit-consumer-double-voting [path/to/evidence.json] [path/to/infraction_header.json] --from node0 --home ../node0 --chain-id $CID `, version.AppName)), Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index d61019e239..ccca1967fb 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -64,7 +64,7 @@ func (k Keeper) VerifyDoubleVotingEvidence( evidence.VoteA.Round != evidence.VoteB.Round || evidence.VoteA.Type != evidence.VoteB.Type { return sdkerrors.Wrapf( - ccvtypes.ErrInvalidEvidence, + ccvtypes.ErrInvalidDoubleVotingEvidence, "h/r/s does not match: %d/%d/%v vs %d/%d/%v", evidence.VoteA.Height, evidence.VoteA.Round, evidence.VoteA.Type, evidence.VoteB.Height, evidence.VoteB.Round, evidence.VoteB.Type) @@ -73,7 +73,7 @@ func (k Keeper) VerifyDoubleVotingEvidence( // Addresses must be the same if !bytes.Equal(evidence.VoteA.ValidatorAddress, evidence.VoteB.ValidatorAddress) { return sdkerrors.Wrapf( - ccvtypes.ErrInvalidEvidence, + ccvtypes.ErrInvalidDoubleVotingEvidence, "validator addresses do not match: %X vs %X", evidence.VoteA.ValidatorAddress, evidence.VoteB.ValidatorAddress, @@ -83,7 +83,7 @@ func (k Keeper) VerifyDoubleVotingEvidence( // BlockIDs must be different if evidence.VoteA.BlockID.Equals(evidence.VoteB.BlockID) { return sdkerrors.Wrapf( - ccvtypes.ErrInvalidEvidence, + ccvtypes.ErrInvalidDoubleVotingEvidence, "block IDs are the same (%v) - not a real duplicate vote", evidence.VoteA.BlockID, ) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 3a40b890e9..f149470118 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -34,7 +34,7 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty provAddrs := make([]types.ProviderConsAddress, len(byzantineValidators)) - // jailthe Byzantine validators + // jail the Byzantine validators for _, v := range byzantineValidators { providerAddr := k.GetProviderAddrFromConsumerAddr( ctx, @@ -125,15 +125,15 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe // Check that the headers are at the same height to ensure that // the misbehaviour is for a light client attack and not a time violation, - // https://github.com/cosmos/ibc-go/blob/v4.2.0/modules/light-clients/07-tendermint/types/misbehaviour_handle.go#L53-L58 + // see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go if !misbehaviour.Header1.GetHeight().EQ(misbehaviour.Header2.GetHeight()) { return sdkerrors.Wrap(ibcclienttypes.ErrInvalidMisbehaviour, "headers are not at same height") } // CheckMisbehaviourAndUpdateState verifies the misbehaviour against the trusted consensus states // but does NOT update the light client state. - // Note that the CometBFT CheckMisbehaviourAndUpdateState method returns an error if the trusted consensus states are expired, - // see https://github.com/cosmos/ibc-go/blob/v4.2.0/modules/light-clients/07-tendermint/types/misbehaviour_handle.go#L120 + // Note that the IBC CheckMisbehaviourAndUpdateState method returns an error if the trusted consensus states are expired, + // see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go _, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbehaviour) if err != nil { return err diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index 9293859b02..83a2eea162 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -133,7 +133,7 @@ func (k msgServer) RegisterConsumerRewardDenom(goCtx context.Context, msg *types func (k msgServer) SubmitConsumerMisbehaviour(goCtx context.Context, msg *types.MsgSubmitConsumerMisbehaviour) (*types.MsgSubmitConsumerMisbehaviourResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) if err := k.Keeper.HandleConsumerMisbehaviour(ctx, *msg.Misbehaviour); err != nil { - return &types.MsgSubmitConsumerMisbehaviourResponse{}, err + return nil, err } ctx.EventManager().EmitEvents(sdk.Events{ @@ -171,7 +171,7 @@ func (k msgServer) SubmitConsumerDoubleVoting(goCtx context.Context, msg *types. _, validator := valset.GetByAddress(evidence.VoteA.ValidatorAddress) if validator == nil { return nil, errorsmod.Wrapf( - ccvtypes.ErrInvalidEvidence, + ccvtypes.ErrInvalidDoubleVotingEvidence, "misbehaving validator %s cannot be found in the infraction block header validator set", evidence.VoteA.ValidatorAddress) } @@ -189,7 +189,7 @@ func (k msgServer) SubmitConsumerDoubleVoting(goCtx context.Context, msg *types. ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( - ccvtypes.EventTypeSubmitConsumerMisbehaviour, + ccvtypes.EventTypeSubmitConsumerDoubleVoting, sdk.NewAttribute(ccvtypes.AttributeConsumerDoubleVoting, msg.DuplicateVoteEvidence.String()), sdk.NewAttribute(ccvtypes.AttributeChainID, msg.InfractionBlockHeader.Header.ChainID), sdk.NewAttribute(ccvtypes.AttributeSubmitterAddress, msg.Submitter), diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index b6741a0a43..e5df03e335 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -22,6 +22,7 @@ const ( var ( _ sdk.Msg = &MsgAssignConsumerKey{} _ sdk.Msg = &MsgSubmitConsumerMisbehaviour{} + _ sdk.Msg = &MsgSubmitConsumerDoubleVoting{} ) // NewMsgAssignConsumerKey creates a new MsgAssignConsumerKey instance. @@ -222,6 +223,10 @@ func (msg MsgSubmitConsumerDoubleVoting) ValidateBasic() error { return fmt.Errorf("invalid signed header in infraction block header, 'SignedHeader.Header' is nil") } + if msg.InfractionBlockHeader.ValidatorSet == nil { + return fmt.Errorf("invalid infraction block header, validator set is nil") + } + return nil } diff --git a/x/ccv/types/errors.go b/x/ccv/types/errors.go index e0cb663219..4fbb65398a 100644 --- a/x/ccv/types/errors.go +++ b/x/ccv/types/errors.go @@ -6,24 +6,24 @@ import ( // CCV sentinel errors var ( - ErrInvalidPacketData = errorsmod.Register(ModuleName, 2, "invalid CCV packet data") - ErrInvalidPacketTimeout = errorsmod.Register(ModuleName, 3, "invalid packet timeout") - ErrInvalidVersion = errorsmod.Register(ModuleName, 4, "invalid CCV version") - ErrInvalidChannelFlow = errorsmod.Register(ModuleName, 5, "invalid message sent to channel end") - ErrInvalidConsumerChain = errorsmod.Register(ModuleName, 6, "invalid consumer chain") - ErrInvalidProviderChain = errorsmod.Register(ModuleName, 7, "invalid provider chain") - ErrInvalidStatus = errorsmod.Register(ModuleName, 8, "invalid channel status") - ErrInvalidGenesis = errorsmod.Register(ModuleName, 9, "invalid genesis state") - ErrDuplicateChannel = errorsmod.Register(ModuleName, 10, "CCV channel already exists") - ErrInvalidVSCMaturedId = errorsmod.Register(ModuleName, 11, "invalid vscId for VSC packet") - ErrInvalidVSCMaturedTime = errorsmod.Register(ModuleName, 12, "invalid maturity time for VSC packet") - ErrInvalidConsumerState = errorsmod.Register(ModuleName, 13, "provider chain has invalid state for consumer chain") - ErrInvalidConsumerClient = errorsmod.Register(ModuleName, 14, "ccv channel is not built on correct client") - ErrInvalidProposal = errorsmod.Register(ModuleName, 15, "invalid proposal") - ErrInvalidHandshakeMetadata = errorsmod.Register(ModuleName, 16, "invalid provider handshake metadata") - ErrChannelNotFound = errorsmod.Register(ModuleName, 17, "channel not found") - ErrClientNotFound = errorsmod.Register(ModuleName, 18, "client not found") - ErrDuplicateConsumerChain = errorsmod.Register(ModuleName, 19, "consumer chain already exists") - ErrConsumerChainNotFound = errorsmod.Register(ModuleName, 20, "consumer chain not found") - ErrInvalidEvidence = errorsmod.Register(ModuleName, 21, "invalid consumer double voting evidence") + ErrInvalidPacketData = errorsmod.Register(ModuleName, 2, "invalid CCV packet data") + ErrInvalidPacketTimeout = errorsmod.Register(ModuleName, 3, "invalid packet timeout") + ErrInvalidVersion = errorsmod.Register(ModuleName, 4, "invalid CCV version") + ErrInvalidChannelFlow = errorsmod.Register(ModuleName, 5, "invalid message sent to channel end") + ErrInvalidConsumerChain = errorsmod.Register(ModuleName, 6, "invalid consumer chain") + ErrInvalidProviderChain = errorsmod.Register(ModuleName, 7, "invalid provider chain") + ErrInvalidStatus = errorsmod.Register(ModuleName, 8, "invalid channel status") + ErrInvalidGenesis = errorsmod.Register(ModuleName, 9, "invalid genesis state") + ErrDuplicateChannel = errorsmod.Register(ModuleName, 10, "CCV channel already exists") + ErrInvalidVSCMaturedId = errorsmod.Register(ModuleName, 11, "invalid vscId for VSC packet") + ErrInvalidVSCMaturedTime = errorsmod.Register(ModuleName, 12, "invalid maturity time for VSC packet") + ErrInvalidConsumerState = errorsmod.Register(ModuleName, 13, "provider chain has invalid state for consumer chain") + ErrInvalidConsumerClient = errorsmod.Register(ModuleName, 14, "ccv channel is not built on correct client") + ErrInvalidProposal = errorsmod.Register(ModuleName, 15, "invalid proposal") + ErrInvalidHandshakeMetadata = errorsmod.Register(ModuleName, 16, "invalid provider handshake metadata") + ErrChannelNotFound = errorsmod.Register(ModuleName, 17, "channel not found") + ErrClientNotFound = errorsmod.Register(ModuleName, 18, "client not found") + ErrDuplicateConsumerChain = errorsmod.Register(ModuleName, 19, "consumer chain already exists") + ErrConsumerChainNotFound = errorsmod.Register(ModuleName, 20, "consumer chain not found") + ErrInvalidDoubleVotingEvidence = errorsmod.Register(ModuleName, 21, "invalid consumer double voting evidence") ) diff --git a/x/ccv/types/events.go b/x/ccv/types/events.go index 28796144fe..6750bde169 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -10,6 +10,7 @@ const ( EventTypeAssignConsumerKey = "assign_consumer_key" EventTypeRegisterConsumerRewardDenom = "register_consumer_reward_denom" EventTypeSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" + EventTypeSubmitConsumerDoubleVoting = "submit_consumer_double_voting" EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash" EventTypeFeeDistribution = "fee_distribution" EventTypeConsumerSlashRequest = "consumer_slash_request" From 88e07170e071b27d1d4522da6392546bf0e90f63 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 14 Sep 2023 14:59:04 +0200 Subject: [PATCH 13/40] update changelog and fix nits --- CHANGELOG.md | 7 +++++++ docs/docs/features/slashing.md | 16 ++++++++++++++++ tests/e2e/actions.go | 2 +- x/ccv/provider/client/proposal_handler.go | 2 +- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfe47bfd21..a5e309af94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ Add an entry to the unreleased section whenever merging a PR to main that is not ## v2.1.0-lsm-provider * (feature!) [#1280](https://github.com/cosmos/interchain-security/pull/1280) provider proposal for changing reward denoms +* (feature!) [#826](https://github.com/cosmos/interchain-security/pull/826) add new endpoint to provider to handle consumer light client attacks +* (feature!) [#1227](https://github.com/cosmos/interchain-security/pull/1227) add new endpoint to provider to handle consumer double signing attacks + + +### Cryptographic verification of equivocation +* New feature enabling the provider chain to verify equivocation evidence on its own instead of trusting consumer chains, see [EPIC](https://github.com/cosmos/interchain-security/issues/732). + ## v2.0.0-lsm diff --git a/docs/docs/features/slashing.md b/docs/docs/features/slashing.md index a28b16e8c2..e1a51bf8b2 100644 --- a/docs/docs/features/slashing.md +++ b/docs/docs/features/slashing.md @@ -34,3 +34,19 @@ The offending validator will effectively get slashed and tombstoned on all consu You can find instructions on creating `EquivocationProposal`s [here](./proposals#equivocationproposal). + +# Cryptographic verification of equivocation +The Cryptographic verification of equivocation allows external agents to submit evidences of light client and double signing attack observed on a consumer chain. When a valid evidence is received, the malicious validators will be permanently jailed on the provider. + +The feature is outlined in this [ADR-005](../adrs/adr-005-cryptographic-equivocation-verification.md) + +By sending a `MsgSubmitConsumerMisbehaviour` or a `MsgSubmitConsumerDoubleVoting` transaction, the provider will + verify the reported equivocation and, if successful, jail the malicious validator. + +:::info +Note that this feature can only lead to the jailing of the validators responsible for an attack on a consumer chain. However, an [equivocation proposal](#double-signing-equivocation) can still be submitted to execute the slashing and the tombstoning of the a malicious validator afterwards. +::: + + + + diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 2185cc963b..4419d1dcff 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -1729,7 +1729,7 @@ func (tr TestRun) submitChangeRewardDenomsProposal(action submitChangeRewardDeno //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. // CHANGE REWARDS DENOM PROPOSAL bz, err = exec.Command("docker", "exec", tr.containerConfig.instanceName, providerChain.binaryName, - "tx", "gov", "submit-legacy-proposal", "change-reward-denoms", "/change-reward-denoms-proposal.json", + "tx", "gov", "submit-proposal", "change-reward-denoms", "/change-reward-denoms-proposal.json", `--from`, `validator`+fmt.Sprint(action.from), `--chain-id`, string(providerChain.chainId), `--home`, tr.getValidatorHome(providerChain.chainId, action.from), diff --git a/x/ccv/provider/client/proposal_handler.go b/x/ccv/provider/client/proposal_handler.go index dd33b83f15..499f4e3b34 100644 --- a/x/ccv/provider/client/proposal_handler.go +++ b/x/ccv/provider/client/proposal_handler.go @@ -227,7 +227,7 @@ func SubmitChangeRewardDenomsProposalTxCmd() *cobra.Command { The proposal details must be supplied via a JSON file. Example: - $ tx gov submit-legacy-proposal change-reward-denoms --from= + $ tx gov submit-proposal change-reward-denoms --from= Where proposal.json contains: { From 022cd5d5283da666ceed3aed238cef63658fa10d Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 27 Sep 2023 15:26:51 +0200 Subject: [PATCH 14/40] feat: implement slashing functionality on the provider chain (ADR-013) (#1275) Implementing the slashing functionality, as described in ADDR, on the provider chain. --- ...cryptographic-equivocation-verification.md | 105 +++++ tests/e2e/steps_consumer_misbehaviour.go | 10 + tests/e2e/steps_double_sign.go | 19 +- tests/integration/double_vote.go | 134 ++++++- tests/integration/misbehaviour.go | 12 +- testutil/keeper/mocks.go | 80 +++- x/ccv/consumer/types/keys.go | 1 + x/ccv/provider/keeper/double_vote.go | 9 +- x/ccv/provider/keeper/misbehaviour.go | 24 +- x/ccv/provider/keeper/punish_validator.go | 100 ++++- .../provider/keeper/punish_validator_test.go | 359 +++++++++++++++++- x/ccv/types/events.go | 9 +- x/ccv/types/expected_keepers.go | 4 + 13 files changed, 814 insertions(+), 52 deletions(-) create mode 100644 docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md diff --git a/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md b/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md new file mode 100644 index 0000000000..dac41b912e --- /dev/null +++ b/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md @@ -0,0 +1,105 @@ +--- +sidebar_position: 4 +title: Cryptographic verification of equivocation evidence +--- +# ADR 005: Cryptographic verification of equivocation evidence + +## Changelog +* 5/1/2023: First draft +* 7/23/23: Add light client attacks handling + +## Status + +Accepted + +## Context + +Currently, we use a governance proposal to slash validators for equivocation (double signing and light client attacks). +Every proposal needs to go through a (two weeks) voting period before it can be approved. +Given a three-week unbonding period, this means that an equivocation proposal needs to be submitted within one week since the infraction occurred. + +This ADR proposes a system to slash validators automatically for equivocation, immediately upon the provider chain's receipt of the evidence. Another thing to note is that we intend to introduce this system in stages, since even the partial ability to slash and/or tombstone is a strict improvement in security. +For the first stage of this work, we will only handle light client attacks. + +### Light Client Attack + +In a nutshell, the light client is a process that solely verifies a specific state machine's +consensus without executing the transactions. The light clients get new headers by querying +multiple nodes, called primary and witness nodes. + +Light clients download new headers committed on chain from a primary. Headers can be verified in two ways: sequentially, +where the block height of headers is serial, or using skipping. This second verification method allows light clients to download headers +with nonconsecutive block height, where some intermediate headers are skipped (see [Tendermint Light Client, Figure 1 and Figure 3](https://arxiv.org/pdf/2010.07031.pdf)). +Additionally, light clients are cross-checking new headers obtained from a primary with witnesses to ensure all nodes share the same state. + +A light client attack occurs when a Byzantine validator sends invalid headers to a light client. +As the light client doesn't execute transactions, it can be deceived into trusting corrupted application state transitions. +For instance, if a light client receives header `A` from the primary and header `B` from a witness for the same block height `H`, +and both headers are successfully verified, it indicates a light client attack. +Note that in this case, either the primary or the witness or both are malicious. + +The types of light client attacks are defined by analyzing the differences between the conflicting headers. +There are three types of light client attacks: lunatic attack, equivocation attack, and amnesia attack. +For details, see the [CometBFT specification](https://github.com/cometbft/cometbft/blob/main/spec/light-client/attacks/notes-on-evidence-handling.md#evidence-handling). + +When a light client agent detects two conflicting headers, it will initially verify their traces (see [cometBFT detector](https://github.com/cometbft/cometbft/blob/2af25aea6cfe6ac4ddac40ceddfb8c8eee17d0e6/light/detector.go#L28)) using its primary and witness nodes. +If these headers pass successful verification, the Byzantine validators will be identified based on the header's commit signatures +and the type of light client attack. The agent will then transmit this information to its nodes using a [`LightClientAttackEvidence`](https://github.com/cometbft/cometbft/blob/feed0ddf564e113a840c4678505601256b93a8bc/docs/architecture/adr-047-handling-evidence-from-light-client.md) to be eventually voted on and added to a block. +Note that from a light client agent perspective, it is not possible to establish whether a primary or a witness node, or both, are malicious. +Therefore, it will create and send two `LightClientAttackEvidence`: one against the primary (sent to the witness), and one against the witness (sent to the primary). +Both nodes will then verify it before broadcasting it and adding it to the [evidence pool](https://github.com/cometbft/cometbft/blob/2af25aea6cfe6ac4ddac40ceddfb8c8eee17d0e6/evidence/pool.go#L28). +If a `LightClientAttackEvidence` is finally committed to a block, the chain's evidence module will execute it, resulting in the jailing and the slashing of the validators responsible for the light client attack. + + +Light clients are a core component of IBC. In the event of a light client attack, IBC relayers notify the affected chains by submitting an [IBC misbehavior message](https://github.com/cosmos/ibc-go/blob/2b7c969066fbcb18f90c7f5bd256439ca12535c7/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L79). +A misbehavior message includes the conflicting headers that constitute a `LightClientAttackEvidence`. Upon receiving such a message, +a chain will first verify whether these headers would have convinced its light client. This verification is achieved by checking +the header states against the light client consensus states (see [IBC misbehaviour handler](https://github.com/cosmos/ibc-go/blob/2b7c969066fbcb18f90c7f5bd256439ca12535c7/modules/light-clients/07-tendermint/types/misbehaviour_handle.go#L101)). If the misbehaviour is successfully verified, the chain will then "freeze" the +light client, halting any further trust in or updating of its states. + + +## Decision + +In the first iteration of the feature, we will introduce a new endpoint: `HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour)`. +The main idea is to leverage the current IBC misbehaviour handling and update it to solely jail and slash the validators that +performed a light client attack. This update will be made under the assumption that the chain connected via this light client +share the same validator set, as it is the case with Replicated Security. + +This endpoint will reuse the IBC client libraries to verify that the misbehaviour headers would have fooled the light client. +Additionally, it’s crucial that the endpoint logic result in the slashing and jailing of validators under the same conditions +as a light client agent detector. Therefore, the endpoint will ensure that the two conditions are met: +the headers in the misbehaviour message have the same block height, and +the light client isn’t expired. + +After having successfully verified a misbehaviour, the endpoint will execute the jailing and slashing of the malicious validators similarly as in the evidence module. + +### Current limitations: + +- This only handles light client attacks, not double signing. In the future, we will add the code to also verify double signing. + +- We cannot derive an infraction height from the evidence, so it is only possible to tombstone validators, not actually slash them. +To explain the technical reasons behind this limitation, let's recap the initial consumer initiated slashing logic. +In a nutshell, consumer heights are mapped to provider heights through VSCPackets, namely through the so called vscIDs. +When an infraction occurs on the consumer, a SlashPacket containing the vscID obtained from mapping the consumer infraction height +is sent to the provider. Upon receiving the packet, the provider maps the consumer infraction height to a local infraction height, +which is used to slash the misbehaving validator. In the context of untrusted consumer chains, all their states, including vscIDs, +could be corrupted and therefore cannot be used for slashing purposes. + +- Currently, the endpoint can only handle "equivocation" light client attacks. This is because the "lunatic" attacks require the endpoint to possess the ability to dissociate which header is conflicted or trusted upon receiving a misbehavior message. Without this information, it's not possible to define the Byzantine validators from the conflicting headers (see [comment](https://github.com/cosmos/interchain-security/pull/826#discussion_r1268668684)). + + +## Consequences + +### Positive + +- After this ADR is applied, it will be possible for the provider chain to tombstone validators who committed a light client attack. + +### Negative + +- N/A + + +## References + +* [ICS misbehaviour handling PR](https://github.com/cosmos/interchain-security/pull/826) +* [Architectural diagrams](https://docs.google.com/document/d/1fe1uSJl1ZIYWXoME3Yf4Aodvz7V597Ric875JH-rigM/edit#heading=h.rv4t8i6d6jfn) diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index 92402d6dc6..feb00c2c50 100644 --- a/tests/e2e/steps_consumer_misbehaviour.go +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -230,6 +230,10 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { validatorID("alice"): 511, validatorID("bob"): 20, }, + RepresentativePowers: &map[validatorID]uint{ + validatorID("alice"): 511000000, + validatorID("bob"): 20000000, + }, }, chainID(consumerName): ChainState{ ValPowers: &map[validatorID]uint{ @@ -255,6 +259,12 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { validatorID("alice"): 0, validatorID("bob"): 20, }, + // "alice" should be slashed on the provider, hence representative + // power is 511000000 - 0.05 * 511000000 = 485450000 + RepresentativePowers: &map[validatorID]uint{ + validatorID("alice"): 485450000, + validatorID("bob"): 20000000, + }, // The consumer light client should be frozen on the provider ClientsFrozenHeights: &map[string]clienttypes.Height{ consumerClientID: { diff --git a/tests/e2e/steps_double_sign.go b/tests/e2e/steps_double_sign.go index 2adcac14cb..63e187237a 100644 --- a/tests/e2e/steps_double_sign.go +++ b/tests/e2e/steps_double_sign.go @@ -144,6 +144,11 @@ func stepsCauseDoubleSignOnConsumer(consumerName, providerName string) []Step { validatorID("bob"): 500, validatorID("carol"): 500, }, + RepresentativePowers: &map[validatorID]uint{ + validatorID("alice"): 500000000, + validatorID("bob"): 500000000, + validatorID("carol"): 500000000, + }, }, chainID(consumerName): ChainState{ ValPowers: &map[validatorID]uint{ @@ -155,7 +160,7 @@ func stepsCauseDoubleSignOnConsumer(consumerName, providerName string) []Step { }, }, // detect the double voting infraction - // and jail bob on the provider + // and jail and slashing of bob on the provider { action: startConsumerEvidenceDetectorAction{ chain: chainID(consumerName), @@ -167,6 +172,13 @@ func stepsCauseDoubleSignOnConsumer(consumerName, providerName string) []Step { validatorID("bob"): 0, validatorID("carol"): 500, }, + // "bob" gets slashed on the provider chain, hence representative + // power is 500000000 - 0.05 * 500000000 = 475000000 + RepresentativePowers: &map[validatorID]uint{ + validatorID("alice"): 500000000, + validatorID("bob"): 475000000, + validatorID("carol"): 500000000, + }, }, chainID(consumerName): ChainState{ ValPowers: &map[validatorID]uint{ @@ -192,6 +204,11 @@ func stepsCauseDoubleSignOnConsumer(consumerName, providerName string) []Step { validatorID("bob"): 0, validatorID("carol"): 500, }, + RepresentativePowers: &map[validatorID]uint{ + validatorID("alice"): 500000000, + validatorID("bob"): 475000000, + validatorID("carol"): 500000000, + }, }, chainID(consumerName): ChainState{ ValPowers: &map[validatorID]uint{ diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index c79b92115e..d2a00e583b 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -10,7 +10,7 @@ import ( ) // TestHandleConsumerDoubleVoting verifies that handling a double voting evidence -// of a consumer chain results in the expected jailing of the malicious validator +// of a consumer chain results in the expected tombstoning and jailing the misbehaved validator func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 @@ -24,11 +24,11 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { consuValSet, err := tmtypes.ValidatorSetFromProto(s.consumerChain.LastHeader.ValidatorSet) s.Require().NoError(err) consuVal := consuValSet.Validators[0] - s.Require().NoError(err) consuSigner := s.consumerChain.Signers[consuVal.Address.String()] provValSet, err := tmtypes.ValidatorSetFromProto(s.providerChain.LastHeader.ValidatorSet) s.Require().NoError(err) + provVal := provValSet.Validators[0] provSigner := s.providerChain.Signers[provVal.Address.String()] @@ -156,13 +156,16 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { }, } - consuAddr := types.NewConsumerConsAddress(sdk.ConsAddress(consuVal.Address.Bytes())) - provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) - for _, tc := range testCases { s.Run(tc.name, func() { + consuAddr := types.NewConsumerConsAddress(sdk.ConsAddress(tc.ev.VoteA.ValidatorAddress.Bytes())) + provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) + + validator, _ := s.providerApp.GetTestStakingKeeper().GetValidator(s.providerCtx(), provAddr.ToSdkConsAddr().Bytes()) + initialTokens := validator.GetTokens().ToDec() + // reset context for each run - provCtx := s.providerCtx() + provCtx, _ := s.providerCtx().CacheContext() // if the evidence was built using the validator provider address and key, // we remove the consumer key assigned to the validator otherwise @@ -185,14 +188,129 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { if tc.expPass { s.Require().NoError(err) - // verifies that the jailing has occurred + // verifies that the jailing and tombstoning has occurred s.Require().True(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(provCtx, provAddr.ToSdkConsAddr())) + s.Require().True(s.providerApp.GetTestSlashingKeeper().IsTombstoned(provCtx, provAddr.ToSdkConsAddr())) + + // verifies that the val gets slashed and has fewer tokens after the slashing + val, _ := s.providerApp.GetTestStakingKeeper().GetValidator(provCtx, provAddr.ToSdkConsAddr().Bytes()) + slashFraction := s.providerApp.GetTestSlashingKeeper().SlashFractionDoubleSign(provCtx) + actualTokens := val.GetTokens().ToDec() + s.Require().True(initialTokens.Sub(initialTokens.Mul(slashFraction)).Equal(actualTokens)) } else { s.Require().Error(err) - // verifies that no jailing and has occurred + // verifies that no jailing and no tombstoning has occurred s.Require().False(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(provCtx, provAddr.ToSdkConsAddr())) + s.Require().False(s.providerApp.GetTestSlashingKeeper().IsTombstoned(provCtx, provAddr.ToSdkConsAddr())) } }) } } + +// TestHandleConsumerDoubleVotingSlashesUndelegations verifies that handling a successful double voting +// evidence of a consumer chain results in the expected slashing of the misbehave validator undelegations +func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegations() { + s.SetupCCVChannel(s.path) + // required to have the consumer client revision height greater than 0 + s.SendEmptyVSCPacket() + + // create signing info for all validators + for _, v := range s.providerChain.Vals.Validators { + s.setDefaultValSigningInfo(*v) + } + + consuValSet, err := tmtypes.ValidatorSetFromProto(s.consumerChain.LastHeader.ValidatorSet) + s.Require().NoError(err) + consuVal := consuValSet.Validators[0] + consuSigner := s.consumerChain.Signers[consuVal.Address.String()] + + blockID1 := testutil.MakeBlockID([]byte("blockhash"), 1000, []byte("partshash")) + blockID2 := testutil.MakeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) + + // create two votes using the consumer validator key + consuVote := testutil.MakeAndSignVote( + blockID1, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + consuValSet, + consuSigner, + s.consumerChain.ChainID, + ) + + consuBadVote := testutil.MakeAndSignVote( + blockID2, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + consuValSet, + consuSigner, + s.consumerChain.ChainID, + ) + + // In order to create an evidence for a consumer chain, + // we create two votes that only differ by their Block IDs and + // signed them using the same validator private key and chain ID + // of the consumer chain + evidence := &tmtypes.DuplicateVoteEvidence{ + VoteA: consuVote, + VoteB: consuBadVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + } + + chainID := s.consumerChain.ChainID + pubKey := consuVal.PubKey + + consuAddr := types.NewConsumerConsAddress(sdk.ConsAddress(consuVal.Address.Bytes())) + provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) + + validator, found := s.providerApp.GetTestStakingKeeper().GetValidator(s.providerCtx(), provAddr.ToSdkConsAddr().Bytes()) + s.Require().True(found) + + s.Run("slash undelegations when getting double voting evidence", func() { + // convert validator public key + pk, err := cryptocodec.FromTmPubKeyInterface(pubKey) + s.Require().NoError(err) + + // perform a delegation and an undelegation of the whole amount + bondAmt := sdk.NewInt(10000000) + delAddr := s.providerChain.SenderAccount.GetAddress() + + // in order to perform a delegation we need to know the validator's `idx` (that might not be 0) + // loop through all validators to find the right `idx` + idx := 0 + for i := 0; i <= len(s.providerChain.Vals.Validators); i++ { + _, valAddr := s.getValByIdx(i) + if validator.OperatorAddress == valAddr.String() { + idx = i + break + } + } + + _, shares, valAddr := delegateByIdx(s, delAddr, bondAmt, idx) + _ = undelegate(s, delAddr, valAddr, shares) + + _, shares, _ = delegateByIdx(s, delAddr, sdk.NewInt(50000000), idx) + _ = undelegate(s, delAddr, valAddr, shares) + + err = s.providerApp.GetProviderKeeper().HandleConsumerDoubleVoting( + s.providerCtx(), + evidence, + chainID, + pk, + ) + s.Require().NoError(err) + + slashFraction := s.providerApp.GetTestSlashingKeeper().SlashFractionDoubleSign(s.providerCtx()) + + // check undelegations are slashed + ubds, _ := s.providerApp.GetTestStakingKeeper().GetUnbondingDelegation(s.providerCtx(), delAddr, validator.GetOperator()) + s.Require().True(len(ubds.Entries) > 0) + for _, unb := range ubds.Entries { + initialBalance := unb.InitialBalance.ToDec() + currentBalance := unb.Balance.ToDec() + s.Require().True(initialBalance.Sub(initialBalance.Mul(slashFraction)).Equal(currentBalance)) + } + }) +} diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index f7cebc84c0..caa0647542 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -53,16 +53,26 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { ), } + // we assume that all validators have the same number of initial tokens + validator, _ := s.getValByIdx(0) + initialTokens := validator.GetTokens().ToDec() + err := s.providerApp.GetProviderKeeper().HandleConsumerMisbehaviour(s.providerCtx(), *misb) s.NoError(err) - // verify that validators are jailed and tombstoned + // verify that validators are jailed, tombstoned, and slashed for _, v := range clientTMValset.Validators { consuAddr := sdk.ConsAddress(v.Address.Bytes()) provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, types.NewConsumerConsAddress(consuAddr)) val, ok := s.providerApp.GetTestStakingKeeper().GetValidatorByConsAddr(s.providerCtx(), provAddr.Address) s.Require().True(ok) s.Require().True(val.Jailed) + s.Require().True(s.providerApp.GetTestSlashingKeeper().IsTombstoned(s.providerCtx(), provAddr.ToSdkConsAddr())) + + validator, _ := s.providerApp.GetTestStakingKeeper().GetValidator(s.providerCtx(), provAddr.ToSdkConsAddr().Bytes()) + slashFraction := s.providerApp.GetTestSlashingKeeper().SlashFractionDoubleSign(s.providerCtx()) + actualTokens := validator.GetTokens().ToDec() + s.Require().True(initialTokens.Sub(initialTokens.Mul(slashFraction)).Equal(actualTokens)) } } diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index c005424de5..dffbc1ca09 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -116,6 +116,34 @@ func (mr *MockStakingKeeperMockRecorder) GetLastValidators(ctx interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastValidators", reflect.TypeOf((*MockStakingKeeper)(nil).GetLastValidators), ctx) } +// GetRedelegationsFromSrcValidator mocks base method. +func (m *MockStakingKeeper) GetRedelegationsFromSrcValidator(ctx types.Context, valAddr types.ValAddress) []types4.Redelegation { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRedelegationsFromSrcValidator", ctx, valAddr) + ret0, _ := ret[0].([]types4.Redelegation) + return ret0 +} + +// GetRedelegationsFromSrcValidator indicates an expected call of GetRedelegationsFromSrcValidator. +func (mr *MockStakingKeeperMockRecorder) GetRedelegationsFromSrcValidator(ctx, valAddr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRedelegationsFromSrcValidator", reflect.TypeOf((*MockStakingKeeper)(nil).GetRedelegationsFromSrcValidator), ctx, valAddr) +} + +// GetUnbondingDelegationsFromValidator mocks base method. +func (m *MockStakingKeeper) GetUnbondingDelegationsFromValidator(ctx types.Context, valAddr types.ValAddress) []types4.UnbondingDelegation { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUnbondingDelegationsFromValidator", ctx, valAddr) + ret0, _ := ret[0].([]types4.UnbondingDelegation) + return ret0 +} + +// GetUnbondingDelegationsFromValidator indicates an expected call of GetUnbondingDelegationsFromValidator. +func (mr *MockStakingKeeperMockRecorder) GetUnbondingDelegationsFromValidator(ctx, valAddr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnbondingDelegationsFromValidator", reflect.TypeOf((*MockStakingKeeper)(nil).GetUnbondingDelegationsFromValidator), ctx, valAddr) +} + // GetUnbondingType mocks base method. func (m *MockStakingKeeper) GetUnbondingType(ctx types.Context, id uint64) (types4.UnbondingType, bool) { m.ctrl.T.Helper() @@ -279,6 +307,34 @@ func (mr *MockStakingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Slash", reflect.TypeOf((*MockStakingKeeper)(nil).Slash), arg0, arg1, arg2, arg3, arg4, arg5) } +// SlashRedelegation mocks base method. +func (m *MockStakingKeeper) SlashRedelegation(arg0 types.Context, arg1 types4.Validator, arg2 types4.Redelegation, arg3 int64, arg4 types.Dec) types.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SlashRedelegation", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(types.Int) + return ret0 +} + +// SlashRedelegation indicates an expected call of SlashRedelegation. +func (mr *MockStakingKeeperMockRecorder) SlashRedelegation(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashRedelegation", reflect.TypeOf((*MockStakingKeeper)(nil).SlashRedelegation), arg0, arg1, arg2, arg3, arg4) +} + +// SlashUnbondingDelegation mocks base method. +func (m *MockStakingKeeper) SlashUnbondingDelegation(arg0 types.Context, arg1 types4.UnbondingDelegation, arg2 int64, arg3 types.Dec) types.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SlashUnbondingDelegation", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(types.Int) + return ret0 +} + +// SlashUnbondingDelegation indicates an expected call of SlashUnbondingDelegation. +func (mr *MockStakingKeeperMockRecorder) SlashUnbondingDelegation(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashUnbondingDelegation", reflect.TypeOf((*MockStakingKeeper)(nil).SlashUnbondingDelegation), arg0, arg1, arg2, arg3) +} + // UnbondingCanComplete mocks base method. func (m *MockStakingKeeper) UnbondingCanComplete(ctx types.Context, id uint64) error { m.ctrl.T.Helper() @@ -796,6 +852,18 @@ func (mr *MockClientKeeperMockRecorder) GetSelfConsensusState(ctx, height interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSelfConsensusState", reflect.TypeOf((*MockClientKeeper)(nil).GetSelfConsensusState), ctx, height) } +// SetClientState mocks base method. +func (m *MockClientKeeper) SetClientState(ctx types.Context, clientID string, clientState exported.ClientState) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetClientState", ctx, clientID, clientState) +} + +// SetClientState indicates an expected call of SetClientState. +func (mr *MockClientKeeperMockRecorder) SetClientState(ctx, clientID, clientState interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetClientState", reflect.TypeOf((*MockClientKeeper)(nil).SetClientState), ctx, clientID, clientState) +} + // MockDistributionKeeper is a mock of DistributionKeeper interface. type MockDistributionKeeper struct { ctrl *gomock.Controller @@ -833,18 +901,6 @@ func (mr *MockDistributionKeeperMockRecorder) FundCommunityPool(ctx, amount, sen return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FundCommunityPool", reflect.TypeOf((*MockDistributionKeeper)(nil).FundCommunityPool), ctx, amount, sender) } -// SetClientState mocks base method. -func (m *MockClientKeeper) SetClientState(ctx types.Context, clientID string, clientState exported.ClientState) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetClientState", ctx, clientID, clientState) -} - -// SetClientState indicates an expected call of SetClientState. -func (mr *MockClientKeeperMockRecorder) SetClientState(ctx, clientID, clientState interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetClientState", reflect.TypeOf((*MockClientKeeper)(nil).SetClientState), ctx, clientID, clientState) -} - // MockConsumerHooks is a mock of ConsumerHooks interface. type MockConsumerHooks struct { ctrl *gomock.Controller diff --git a/x/ccv/consumer/types/keys.go b/x/ccv/consumer/types/keys.go index 093f78b450..fb35626698 100644 --- a/x/ccv/consumer/types/keys.go +++ b/x/ccv/consumer/types/keys.go @@ -25,6 +25,7 @@ const ( ConsumerRedistributeName = "cons_redistribute" // ConsumerToSendToProviderName is a "buffer" address for outgoing fees to be transferred to the provider chain + //#nosec G101 -- (false positive) this is not a hardcoded credential ConsumerToSendToProviderName = "cons_to_send_to_provider" ) diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index ccca1967fb..d3813f8da4 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -5,7 +5,6 @@ import ( "fmt" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" @@ -33,8 +32,12 @@ func (k Keeper) HandleConsumerDoubleVoting( types.NewConsumerConsAddress(sdk.ConsAddress(evidence.VoteA.ValidatorAddress.Bytes())), ) - // execute the jailing - k.JailValidator(ctx, providerAddr) + if err := k.SlashValidator(ctx, providerAddr); err != nil { + return err + } + if err := k.JailAndTombstoneValidator(ctx, providerAddr); err != nil { + return err + } k.Logger(ctx).Info( "confirmed equivocation", diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index f149470118..c53e11b450 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -34,14 +36,22 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty provAddrs := make([]types.ProviderConsAddress, len(byzantineValidators)) - // jail the Byzantine validators + var errors []error + // slash, jail, and tombstone the Byzantine validators for _, v := range byzantineValidators { providerAddr := k.GetProviderAddrFromConsumerAddr( ctx, misbehaviour.Header1.Header.ChainID, types.NewConsumerConsAddress(sdk.ConsAddress(v.Address.Bytes())), ) - k.JailValidator(ctx, providerAddr) + err := k.SlashValidator(ctx, providerAddr) + if err != nil { + errors = append(errors, err) + } + err = k.JailAndTombstoneValidator(ctx, providerAddr) + if err != nil { + errors = append(errors, err) + } provAddrs = append(provAddrs, providerAddr) } @@ -50,6 +60,16 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty "byzantine validators", provAddrs, ) + // If we fail to slash all validators we return an error. However, if we only fail to slash some validators + // we just log an error to avoid having the whole `MsgSubmitMisbehaviour` failing and reverting the partial slashing. + if len(errors) == len(byzantineValidators) { + return fmt.Errorf("failed to slash, jail, or tombstone all validators: %v", errors) + } + + if len(errors) > 0 { + logger.Error("failed to slash, jail, or tombstone validators: %v", errors) + } + return nil } diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go index f4648cc641..6c9e88f260 100644 --- a/x/ccv/provider/keeper/punish_validator.go +++ b/x/ccv/provider/keeper/punish_validator.go @@ -1,38 +1,104 @@ package keeper import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" ) -// JailValidator jails the validator with the given provider consensus address -// Note that the tombstoning is temporarily removed until we slash validator -// for double signing on a consumer chain, see comment -// https://github.com/cosmos/interchain-security/pull/1232#issuecomment-1693127641. -func (k Keeper) JailValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress) { - logger := k.Logger(ctx) +// JailAndTombstoneValidator jails and tombstones the validator with the given provider consensus address +func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress) error { + validator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) + if !found { + return errorsmod.Wrapf(slashingtypes.ErrNoValidatorForAddress, "provider consensus address: %s", providerAddr.String()) + } - // get validator - val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) - if !ok || val.IsUnbonded() { - logger.Error("validator not found or is unbonded", providerAddr.String()) - return + if validator.IsUnbonded() { + return fmt.Errorf("validator is unbonded. provider consensus address: %s", providerAddr.String()) } - // check that the validator isn't tombstoned if k.slashingKeeper.IsTombstoned(ctx, providerAddr.ToSdkConsAddr()) { - logger.Info("validator is already tombstoned", "provider cons addr", providerAddr.String()) - return + return fmt.Errorf("validator is tombstoned. provider consensus address: %s", providerAddr.String()) } // jail validator if not already - if !val.IsJailed() { + if !validator.IsJailed() { k.stakingKeeper.Jail(ctx, providerAddr.ToSdkConsAddr()) } - // update jail time to end after double sign jail duration + // Jail the validator to trigger the unbonding of the validator + // (see cosmos/cosmos-sdk/blob/v0.45.16-ics-lsm/x/staking/keeper/val_state_change.go#L192). k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime) - // TODO: add tombstoning back once we integrate the slashing + // Tombstone the validator so that we cannot slash the validator more than once + // (see cosmos/cosmos-sdk/blob/v0.45.16-ics-lsm/x/evidence/keeper/infraction.go#L81). + // Note that we cannot simply use the fact that a validator is jailed to avoid slashing more than once + // because then a validator could i) perform an equivocation, ii) get jailed (e.g., through downtime) + // and in such a case the validator would not get slashed when we call `SlashValidator`. + k.slashingKeeper.Tombstone(ctx, providerAddr.ToSdkConsAddr()) + + return nil +} + +// ComputePowerToSlash computes the power to be slashed based on the tokens in non-matured `undelegations` and +// `redelegations`, as well as the current `power` of the validator. +// Note that this method does not perform any slashing. +func (k Keeper) ComputePowerToSlash(ctx sdk.Context, validator stakingtypes.Validator, undelegations []stakingtypes.UnbondingDelegation, + redelegations []stakingtypes.Redelegation, power int64, powerReduction sdk.Int, +) int64 { + // compute the total numbers of tokens currently being undelegated + undelegationsInTokens := sdk.NewInt(0) + + // Note that we use a **cached** context to avoid any actual slashing of undelegations or redelegations. + cachedCtx, _ := ctx.CacheContext() + for _, u := range undelegations { + amountSlashed := k.stakingKeeper.SlashUnbondingDelegation(cachedCtx, u, 0, sdk.NewDec(1)) + undelegationsInTokens = undelegationsInTokens.Add(amountSlashed) + } + + // compute the total numbers of tokens currently being redelegated + redelegationsInTokens := sdk.NewInt(0) + for _, r := range redelegations { + amountSlashed := k.stakingKeeper.SlashRedelegation(cachedCtx, validator, r, 0, sdk.NewDec(1)) + redelegationsInTokens = redelegationsInTokens.Add(amountSlashed) + } + + // The power we pass to staking's keeper `Slash` method is the current power of the validator together with the total + // power of all the currently undelegated and redelegated tokens (see docs/docs/adrs/adr-013-equivocation-slashing.md). + undelegationsAndRedelegationsInPower := sdk.TokensToConsensusPower( + undelegationsInTokens.Add(redelegationsInTokens), powerReduction) + + return power + undelegationsAndRedelegationsInPower +} + +// SlashValidator slashes validator with `providerAddr` +func (k Keeper) SlashValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress) error { + validator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) + if !found { + return errorsmod.Wrapf(slashingtypes.ErrNoValidatorForAddress, "provider consensus address: %s", providerAddr.String()) + } + + if validator.IsUnbonded() { + return fmt.Errorf("validator is unbonded. provider consensus address: %s", providerAddr.String()) + } + + if k.slashingKeeper.IsTombstoned(ctx, providerAddr.ToSdkConsAddr()) { + return fmt.Errorf("validator is tombstoned. provider consensus address: %s", providerAddr.String()) + } + + undelegations := k.stakingKeeper.GetUnbondingDelegationsFromValidator(ctx, validator.GetOperator()) + redelegations := k.stakingKeeper.GetRedelegationsFromSrcValidator(ctx, validator.GetOperator()) + lastPower := k.stakingKeeper.GetLastValidatorPower(ctx, validator.GetOperator()) + powerReduction := k.stakingKeeper.PowerReduction(ctx) + totalPower := k.ComputePowerToSlash(ctx, validator, undelegations, redelegations, lastPower, powerReduction) + slashFraction := k.slashingKeeper.SlashFractionDoubleSign(ctx) + + k.stakingKeeper.Slash(ctx, providerAddr.ToSdkConsAddr(), 0, totalPower, slashFraction, stakingtypes.DoubleSign) + return nil } diff --git a/x/ccv/provider/keeper/punish_validator_test.go b/x/ccv/provider/keeper/punish_validator_test.go index 50da9ae4bb..1166fbf063 100644 --- a/x/ccv/provider/keeper/punish_validator_test.go +++ b/x/ccv/provider/keeper/punish_validator_test.go @@ -1,7 +1,12 @@ package keeper_test import ( + "fmt" "testing" + "time" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" @@ -10,11 +15,14 @@ import ( testkeeper "github.com/cosmos/interchain-security/v2/testutil/keeper" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" "github.com/golang/mock/gomock" + + "github.com/stretchr/testify/require" + tmtypes "github.com/tendermint/tendermint/types" ) -// TestJailValidator tests that the jailing of a validator is only executed -// under the conditions that the validator is neither unbonded, already jailed, nor tombstoned. -func TestJailValidator(t *testing.T) { +// TestJailAndTombstoneValidator tests that the jailing of a validator is only executed +// under the conditions that the validator is neither unbonded, nor jailed, nor tombstoned. +func TestJailAndTombstoneValidator(t *testing.T) { providerConsAddr := cryptotestutil.NewCryptoIdentityFromIntSeed(7842334).ProviderConsAddress() testCases := []struct { name string @@ -88,6 +96,9 @@ func TestJailValidator(t *testing.T) { mocks.MockSlashingKeeper.EXPECT().JailUntil( ctx, providerConsAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime). Times(1), + mocks.MockSlashingKeeper.EXPECT().Tombstone( + ctx, providerConsAddr.ToSdkConsAddr()). + Times(1), } }, }, @@ -112,6 +123,9 @@ func TestJailValidator(t *testing.T) { mocks.MockSlashingKeeper.EXPECT().JailUntil( ctx, providerConsAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime). Times(1), + mocks.MockSlashingKeeper.EXPECT().Tombstone( + ctx, providerConsAddr.ToSdkConsAddr()). + Times(1), } }, }, @@ -125,8 +139,345 @@ func TestJailValidator(t *testing.T) { gomock.InOrder(tc.expectedCalls(ctx, mocks, tc.provAddr)...) // Execute method and assert expected mock calls - providerKeeper.JailValidator(ctx, tc.provAddr) + providerKeeper.JailAndTombstoneValidator(ctx, tc.provAddr) ctrl.Finish() } } + +// createUndelegation creates an undelegation with `len(initialBalances)` entries +func createUndelegation(initialBalances []int64, completionTimes []time.Time) stakingtypes.UnbondingDelegation { + var entries []stakingtypes.UnbondingDelegationEntry + for i, balance := range initialBalances { + entry := stakingtypes.UnbondingDelegationEntry{ + InitialBalance: sdk.NewInt(balance), + CompletionTime: completionTimes[i], + } + entries = append(entries, entry) + } + + return stakingtypes.UnbondingDelegation{Entries: entries} +} + +// createRedelegation creates a redelegation with `len(initialBalances)` entries +func createRedelegation(initialBalances []int64, completionTimes []time.Time) stakingtypes.Redelegation { + var entries []stakingtypes.RedelegationEntry + for i, balance := range initialBalances { + entry := stakingtypes.RedelegationEntry{ + InitialBalance: sdk.NewInt(balance), + CompletionTime: completionTimes[i], + } + entries = append(entries, entry) + } + + return stakingtypes.Redelegation{Entries: entries} +} + +// TestComputePowerToSlash tests that `ComputePowerToSlash` computes the correct power to be slashed based on +// the tokens in non-mature undelegation and redelegation entries, as well as the current power of the validator +func TestComputePowerToSlash(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // undelegation or redelegation entries with completion time `now` have matured + now := ctx.BlockHeader().Time + // undelegation or redelegation entries with completion time one hour in the future have not yet matured + nowPlus1Hour := now.Add(time.Hour) + + testCases := []struct { + name string + undelegations []stakingtypes.UnbondingDelegation + redelegations []stakingtypes.Redelegation + power int64 + powerReduction sdk.Int + expectedPower int64 + }{ + { + "both undelegations and redelegations 1", + // 1000 total undelegation tokens + []stakingtypes.UnbondingDelegation{ + createUndelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createUndelegation([]int64{500}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + }, + // 1000 total redelegation tokens + []stakingtypes.Redelegation{ + createRedelegation([]int64{500}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createRedelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + }, + int64(1000), + sdk.NewInt(1), + int64(2000/1 + 1000), + }, + { + "both undelegations and redelegations 2", + // 2000 total undelegation tokens + []stakingtypes.UnbondingDelegation{ + createUndelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createUndelegation([]int64{}, []time.Time{}), + createUndelegation([]int64{100, 100}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createUndelegation([]int64{800}, []time.Time{nowPlus1Hour}), + createUndelegation([]int64{500}, []time.Time{nowPlus1Hour}), + }, + // 3500 total redelegation tokens + []stakingtypes.Redelegation{ + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{1600}, []time.Time{nowPlus1Hour}), + createRedelegation([]int64{350, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createRedelegation([]int64{700, 200}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{400}, []time.Time{nowPlus1Hour}), + }, + int64(8391), + sdk.NewInt(2), + int64((2000+3500)/2 + 8391), + }, + { + "no undelegations or redelegations, return provided power", + []stakingtypes.UnbondingDelegation{}, + []stakingtypes.Redelegation{}, + int64(3000), + sdk.NewInt(5), + int64(3000), // expectedPower is 0/5 + 3000 + }, + { + "no undelegations", + []stakingtypes.UnbondingDelegation{}, + // 2000 total redelegation tokens + []stakingtypes.Redelegation{ + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{500}, []time.Time{nowPlus1Hour}), + createRedelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createRedelegation([]int64{700, 200}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{100}, []time.Time{nowPlus1Hour}), + }, + int64(17), + sdk.NewInt(3), + int64(2000/3 + 17), + }, + { + "no redelegations", + // 2000 total undelegation tokens + []stakingtypes.UnbondingDelegation{ + createUndelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createUndelegation([]int64{}, []time.Time{}), + createUndelegation([]int64{100, 100}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createUndelegation([]int64{800}, []time.Time{nowPlus1Hour}), + createUndelegation([]int64{500}, []time.Time{nowPlus1Hour}), + }, + []stakingtypes.Redelegation{}, + int64(1), + sdk.NewInt(3), + int64(2000/3 + 1), + }, + { + "both (mature) undelegations and redelegations", + // 2000 total undelegation tokens, 250 + 100 + 500 = 850 of those are from mature undelegations, + // so 2000 - 850 = 1150 + []stakingtypes.UnbondingDelegation{ + createUndelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, now}), + createUndelegation([]int64{}, []time.Time{}), + createUndelegation([]int64{100, 100}, []time.Time{now, nowPlus1Hour}), + createUndelegation([]int64{800}, []time.Time{nowPlus1Hour}), + createUndelegation([]int64{500}, []time.Time{now}), + }, + // 3500 total redelegation tokens, 350 + 200 + 400 = 950 of those are from mature redelegations + // so 3500 - 950 = 2550 + []stakingtypes.Redelegation{ + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{1600}, []time.Time{nowPlus1Hour}), + createRedelegation([]int64{350, 250}, []time.Time{now, nowPlus1Hour}), + createRedelegation([]int64{700, 200}, []time.Time{nowPlus1Hour, now}), + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{400}, []time.Time{now}), + }, + int64(8391), + sdk.NewInt(2), + int64((1150+2550)/2 + 8391), + }, + } + + pubKey, _ := cryptocodec.FromTmPubKeyInterface(tmtypes.NewMockPV().PrivKey.PubKey()) + validator, _ := stakingtypes.NewValidator(pubKey.Address().Bytes(), pubKey, stakingtypes.Description{}) + + for _, tc := range testCases { + gomock.InOrder(mocks.MockStakingKeeper.EXPECT(). + SlashUnbondingDelegation(gomock.Any(), gomock.Any(), int64(0), sdk.NewDec(1)). + DoAndReturn( + func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) sdk.Int { + sum := sdk.NewInt(0) + for _, r := range undelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + mocks.MockStakingKeeper.EXPECT(). + SlashRedelegation(gomock.Any(), gomock.Any(), gomock.Any(), int64(0), sdk.NewDec(1)). + DoAndReturn( + func(ctx sdk.Context, _ stakingtypes.Validator, redelegation stakingtypes.Redelegation, _ int64, _ sdk.Dec) sdk.Int { + sum := sdk.NewInt(0) + for _, r := range redelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + ) + + actualPower := providerKeeper.ComputePowerToSlash(ctx, validator, + tc.undelegations, tc.redelegations, tc.power, tc.powerReduction) + + if tc.expectedPower != actualPower { + require.Fail(t, fmt.Sprintf("\"%s\" failed", tc.name), + "expected is %d but actual is %d", tc.expectedPower, actualPower) + } + } +} + +// TestSlashValidator asserts that `SlashValidator` calls the staking module's `Slash` method +// with the correct arguments (i.e., `infractionHeight` of 0 and the expected slash power) +func TestSlashValidator(t *testing.T) { + keeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // undelegation or redelegation entries with completion time `now` have matured + now := ctx.BlockHeader().Time + // undelegation or redelegation entries with completion time one hour in the future have not yet matured + nowPlus1Hour := now.Add(time.Hour) + + keeperParams := testkeeper.NewInMemKeeperParams(t) + testkeeper.NewInMemProviderKeeper(keeperParams, mocks) + + pubKey, _ := cryptocodec.FromTmPubKeyInterface(tmtypes.NewMockPV().PrivKey.PubKey()) + pkAny, _ := codectypes.NewAnyWithValue(pubKey) + + // manually build a validator instead of using `stakingtypes.NewValidator` to guarantee that the validator is bonded + validator := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(pubKey.Address().Bytes()).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: sdk.ZeroInt(), + DelegatorShares: sdk.ZeroDec(), + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + UnbondingOnHoldRefCount: 0, + ValidatorBondShares: sdk.ZeroDec(), + LiquidShares: sdk.ZeroDec(), + } + + consAddr, _ := validator.GetConsAddr() + providerAddr := types.NewProviderConsAddress(consAddr) + + // we create 1000 tokens worth of undelegations, 750 of them are non-matured + // we also create 1000 tokens worth of redelegations, 750 of them are non-matured + undelegations := []stakingtypes.UnbondingDelegation{ + createUndelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, now}), + createUndelegation([]int64{500}, []time.Time{nowPlus1Hour}), + } + redelegations := []stakingtypes.Redelegation{ + createRedelegation([]int64{250, 250}, []time.Time{now, nowPlus1Hour}), + createRedelegation([]int64{500}, []time.Time{nowPlus1Hour}), + } + + // validator's current power + currentPower := int64(3000) + + powerReduction := sdk.NewInt(2) + slashFraction, _ := sdk.NewDecFromStr("0.5") + + // the call to `Slash` should provide an `infractionHeight` of 0 and an expected power of + // (750 (undelegations) + 750 (redelegations)) / 2 (= powerReduction) + 3000 (currentPower) = 3750 + expectedInfractionHeight := int64(0) + expectedSlashPower := int64(3750) + + expectedCalls := []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT(). + GetValidatorByConsAddr(ctx, gomock.Any()). + Return(validator, true), + mocks.MockSlashingKeeper.EXPECT(). + IsTombstoned(ctx, consAddr). + Return(false), + mocks.MockStakingKeeper.EXPECT(). + GetUnbondingDelegationsFromValidator(ctx, validator.GetOperator()). + Return(undelegations), + mocks.MockStakingKeeper.EXPECT(). + GetRedelegationsFromSrcValidator(ctx, validator.GetOperator()). + Return(redelegations), + mocks.MockStakingKeeper.EXPECT(). + GetLastValidatorPower(ctx, validator.GetOperator()). + Return(currentPower), + mocks.MockStakingKeeper.EXPECT(). + PowerReduction(ctx). + Return(powerReduction), + mocks.MockStakingKeeper.EXPECT(). + SlashUnbondingDelegation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn( + func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) sdk.Int { + sum := sdk.NewInt(0) + for _, r := range undelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + mocks.MockStakingKeeper.EXPECT(). + SlashRedelegation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn( + func(_ sdk.Context, _ stakingtypes.Validator, redelegation stakingtypes.Redelegation, _ int64, _ sdk.Dec) sdk.Int { + sum := sdk.NewInt(0) + for _, r := range redelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + mocks.MockSlashingKeeper.EXPECT(). + SlashFractionDoubleSign(ctx). + Return(slashFraction), + mocks.MockStakingKeeper.EXPECT(). + Slash(ctx, consAddr, expectedInfractionHeight, expectedSlashPower, slashFraction, stakingtypes.DoubleSign). + Times(1), + } + + gomock.InOrder(expectedCalls...) + keeper.SlashValidator(ctx, providerAddr) +} + +// TestSlashValidatorDoesNotSlashIfValidatorIsUnbonded asserts that `SlashValidator` does not call +// the staking module's `Slash` method if the validator to be slashed is unbonded +func TestSlashValidatorDoesNotSlashIfValidatorIsUnbonded(t *testing.T) { + keeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + keeperParams := testkeeper.NewInMemKeeperParams(t) + testkeeper.NewInMemProviderKeeper(keeperParams, mocks) + + pubKey, _ := cryptocodec.FromTmPubKeyInterface(tmtypes.NewMockPV().PrivKey.PubKey()) + + // validator is initially unbonded + validator, _ := stakingtypes.NewValidator(pubKey.Address().Bytes(), pubKey, stakingtypes.Description{}) + + consAddr, _ := validator.GetConsAddr() + providerAddr := types.NewProviderConsAddress(consAddr) + + expectedCalls := []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT(). + GetValidatorByConsAddr(ctx, gomock.Any()). + Return(validator, true), + } + + gomock.InOrder(expectedCalls...) + keeper.SlashValidator(ctx, providerAddr) +} diff --git a/x/ccv/types/events.go b/x/ccv/types/events.go index 07c9f9ba59..1ee1817554 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -43,10 +43,11 @@ const ( AttributeConsumerDoubleVoting = "consumer_double_voting" AttributeDistributionCurrentHeight = "current_distribution_height" - AttributeDistributionNextHeight = "next_distribution_height" - AttributeDistributionFraction = "distribution_fraction" - AttributeDistributionTotal = "total" - AttributeDistributionToProvider = "provider_amount" + //#nosec G101 -- (false positive) this is not a hardcoded credential + AttributeDistributionNextHeight = "next_distribution_height" + AttributeDistributionFraction = "distribution_fraction" + AttributeDistributionTotal = "total" + AttributeDistributionToProvider = "provider_amount" AttributeConsumerRewardDenom = "consumer_reward_denom" ) diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 4169d510e6..1aa2b6dc8f 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -30,6 +30,8 @@ type StakingKeeper interface { // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction Jail(sdk.Context, sdk.ConsAddress) // jail a validator Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec, stakingtypes.InfractionType) + SlashUnbondingDelegation(sdk.Context, stakingtypes.UnbondingDelegation, int64, sdk.Dec) sdk.Int + SlashRedelegation(sdk.Context, stakingtypes.Validator, stakingtypes.Redelegation, int64, sdk.Dec) sdk.Int Unjail(ctx sdk.Context, addr sdk.ConsAddress) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool) IterateLastValidatorPowers(ctx sdk.Context, cb func(addr sdk.ValAddress, power int64) (stop bool)) @@ -44,6 +46,8 @@ type StakingKeeper interface { GetLastTotalPower(ctx sdk.Context) sdk.Int GetLastValidators(ctx sdk.Context) (validators []stakingtypes.Validator) BondDenom(ctx sdk.Context) (res string) + GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sdk.ValAddress) (ubds []stakingtypes.UnbondingDelegation) + GetRedelegationsFromSrcValidator(ctx sdk.Context, valAddr sdk.ValAddress) (reds []stakingtypes.Redelegation) GetUnbondingType(ctx sdk.Context, id uint64) (unbondingType stakingtypes.UnbondingType, found bool) } From 14bf5a60e7309304802f266c1e23035134b4f274 Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Wed, 13 Sep 2023 16:35:30 +0200 Subject: [PATCH 15/40] remove equivocation proposal --- Makefile | 4 - app/provider/app.go | 14 - docs/docs/features/proposals.md | 58 -- docs/docs/introduction/overview.md | 4 - go.sum | 2 + .../ccv/provider/v1/provider.proto | 10 - tests/e2e/actions.go | 70 --- tests/e2e/main.go | 2 - tests/e2e/state.go | 19 - tests/e2e/steps.go | 2 - .../e2e/steps_submit_equivocation_proposal.go | 150 ----- testutil/keeper/mocks.go | 102 ++-- testutil/keeper/unit_test_helpers.go | 3 - x/ccv/provider/client/proposal_handler.go | 126 ---- x/ccv/provider/keeper/keeper.go | 11 +- x/ccv/provider/keeper/proposal.go | 12 - x/ccv/provider/keeper/proposal_test.go | 61 -- x/ccv/provider/proposal_handler.go | 4 +- x/ccv/provider/proposal_handler_test.go | 25 +- x/ccv/provider/types/codec.go | 4 - x/ccv/provider/types/proposal.go | 38 -- x/ccv/provider/types/proposal_test.go | 58 -- x/ccv/provider/types/provider.pb.go | 543 ++++-------------- x/ccv/types/expected_keepers.go | 5 - 24 files changed, 168 insertions(+), 1159 deletions(-) delete mode 100644 tests/e2e/steps_submit_equivocation_proposal.go diff --git a/Makefile b/Makefile index 351852cd21..d02c64f3f2 100644 --- a/Makefile +++ b/Makefile @@ -164,7 +164,6 @@ SDK_QUERY = third_party/proto/cosmos/base/query/v1beta1 SDK_BASE = third_party/proto/cosmos/base/v1beta1 SDK_UPGRADE = third_party/proto/cosmos/upgrade/v1beta1 SDK_STAKING = third_party/proto/cosmos/staking/v1beta1 -SDK_EVIDENCE = third_party/proto/cosmos/evidence/v1beta1 GOGO_PROTO_TYPES = third_party/proto/gogoproto CONFIO_TYPES = third_party/proto/confio @@ -186,9 +185,6 @@ proto-update-deps: @mkdir -p $(SDK_STAKING) @curl -sSL $(SDK_PROTO_URL)/staking/v1beta1/staking.proto > $(SDK_STAKING)/staking.proto - @mkdir -p $(SDK_EVIDENCE) - @curl -sSL $(SDK_PROTO_URL)/evidence/v1beta1/evidence.proto > $(SDK_EVIDENCE)/evidence.proto - ## Importing of tendermint protobuf definitions currently requires the ## use of `sed` in order to build properly with cosmos-sdk's proto file layout ## (which is the standard Buf.build FILE_LAYOUT) diff --git a/app/provider/app.go b/app/provider/app.go index 0555cf4aa5..6e65d20a96 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -48,7 +48,6 @@ import ( distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/evidence" - evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" "github.com/cosmos/cosmos-sdk/x/genutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" @@ -139,7 +138,6 @@ var ( ibcclientclient.UpgradeProposalHandler, ibcproviderclient.ConsumerAdditionProposalHandler, ibcproviderclient.ConsumerRemovalProposalHandler, - ibcproviderclient.EquivocationProposalHandler, ibcproviderclient.ChangeRewardDenomsProposalHandler, ), params.AppModuleBasic{}, @@ -207,7 +205,6 @@ type App struct { // nolint: golint UpgradeKeeper upgradekeeper.Keeper ParamsKeeper paramskeeper.Keeper IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly - EvidenceKeeper evidencekeeper.Keeper TransferKeeper ibctransferkeeper.Keeper ProviderKeeper ibcproviderkeeper.Keeper @@ -390,14 +387,6 @@ func New( scopedIBCKeeper, ) - // create evidence keeper with router - app.EvidenceKeeper = *evidencekeeper.NewKeeper( - appCodec, - keys[evidencetypes.StoreKey], - app.StakingKeeper, - app.SlashingKeeper, - ) - app.ProviderKeeper = ibcproviderkeeper.NewKeeper( appCodec, keys[providertypes.StoreKey], @@ -410,7 +399,6 @@ func New( app.StakingKeeper, app.SlashingKeeper, app.AccountKeeper, - app.EvidenceKeeper, app.DistrKeeper, app.BankKeeper, authtypes.FeeCollectorName, @@ -481,7 +469,6 @@ func New( distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper), upgrade.NewAppModule(app.UpgradeKeeper), - evidence.NewAppModule(app.EvidenceKeeper), ibc.NewAppModule(app.IBCKeeper), params.NewAppModule(app.ParamsKeeper), transferModule, @@ -585,7 +572,6 @@ func New( distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), params.NewAppModule(app.ParamsKeeper), - evidence.NewAppModule(app.EvidenceKeeper), ibc.NewAppModule(app.IBCKeeper), transferModule, ) diff --git a/docs/docs/features/proposals.md b/docs/docs/features/proposals.md index 66b9d3a751..2196697559 100644 --- a/docs/docs/features/proposals.md +++ b/docs/docs/features/proposals.md @@ -74,37 +74,6 @@ Minimal example: } ``` -## `EquivocationProposal` -:::tip -`EquivocationProposal` will only be accepted on the provider chain if at least one of the consumer chains submits equivocation evidence to the provider. -Sending equivocation evidence to the provider is handled automatically by the interchain security protocol when an equivocation infraction is detected on the consumer chain. -::: - -Proposal type used to suggest slashing a validator for double signing on consumer chain. -When proposals of this type are passed, the validator in question will be slashed for equivocation on the provider chain. - -:::warning -Take note that an equivocation slash causes a validator to be tombstoned (can never re-enter the active set). -Tombstoning a validator on the provider chain will remove the validator from the validator set of all consumer chains. -::: - -Minimal example: -```js -{ - "title": "Validator-1 double signed on consumerchain-1", - "description": "Here is more information about the infraction so you can verify it yourself", - // the list of equivocations that will be processed - "equivocations": [ - { - "height": 14444680, - "time": "2023-02-28T20:40:00.000000Z", - "power": 5500000, - "consensus_address": "" - } - ] -} -``` - ## ChangeRewardDenomProposal :::tip `ChangeRewardDenomProposal` will only be accepted on the provider chain if at least one of the denomsToAdd or denomsToRemove fields is populated with at least one denom. Also, a denom cannot be repeated in both sets. @@ -121,30 +90,3 @@ Minimal example: "denomsToRemove": [] } ``` - -### Notes -When submitting equivocation evidence through an `EquivocationProposal` please take note that you need to use the consensus address (`valcons`) of the offending validator on the **provider chain**. -Besides that, the `height` and the `time` fields should be mapped to the **provider chain** to avoid your evidence being rejected. - -Before submitting the proposal please check that the evidence is not outdated by comparing the infraction height with the `max_age_duration` and `max_age_num_blocks` consensus parameters of the **provider chain**. - -### Gaia example: -``` -âžœ ~ cat genesis.json | jq ".consensus_params" -{ - "block": { - ... - }, - "evidence": { - "max_age_duration": "172800000000000", - "max_age_num_blocks": "1000000", - "max_bytes": "50000" - }, - "validator": { - ... - }, - "version": {} -} -``` - -Any `EquivocationProposal` transactions that submit evidence with `height` older than `max_age_num_blocks` and `time` older than `max_age_duration` will be considered invalid. diff --git a/docs/docs/introduction/overview.md b/docs/docs/introduction/overview.md index f61fe4d209..aba31751e1 100644 --- a/docs/docs/introduction/overview.md +++ b/docs/docs/introduction/overview.md @@ -30,10 +30,6 @@ To ensure the security of the consumer chain, provider delegators cannot unbond If downtime is initiated by a validator on a consumer chain, a downtime packet will be relayed to the provider to jail that validator for a set amount of time. The validator who committed downtime will then miss out on staking rewards for the configured jailing period. -### Equivocation (Double Sign) Slashing - -Evidence of equivocation must be submitted to provider governance and be voted on. This behavior is an extra safeguard before a validator is slashed, and may be replaced by a more automated system in the future. - ### Tokenomics and Rewards Consumer chains are free to create their own native token which can be used for fees, and can be created on the consumer chain in the form of inflationary rewards. These rewards can be used to incentivize user behavior, for example, LPing or staking. A portion of these fees and rewards will be sent to provider chain stakers, but that proportion is completely customizable by the developers, and subject to governance. diff --git a/go.sum b/go.sum index 3d7480161f..0378b823b2 100644 --- a/go.sum +++ b/go.sum @@ -1128,6 +1128,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1380,6 +1381,7 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index c024d7eaef..ee79d9561d 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -10,7 +10,6 @@ import "google/protobuf/duration.proto"; import "ibc/core/client/v1/client.proto"; import "ibc/lightclients/tendermint/v1/tendermint.proto"; import "tendermint/crypto/keys.proto"; -import "cosmos/evidence/v1beta1/evidence.proto"; import "cosmos/base/v1beta1/coin.proto"; // ConsumerAdditionProposal is a governance proposal on the provider chain to spawn a new consumer chain. @@ -86,15 +85,6 @@ message ConsumerRemovalProposal { [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; } -message EquivocationProposal { - // the title of the proposal - string title = 1; - // the description of the proposal - string description = 2; - // the list of equivocations that will be processed - repeated cosmos.evidence.v1beta1.Equivocation equivocations = 3; -} - // ChangeRewardDenomsProposal is a governance proposal on the provider chain to // mutate the set of denoms accepted by the provider as rewards. message ChangeRewardDenomsProposal { diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 4419d1dcff..3da9e0d394 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -11,7 +11,6 @@ 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/v2/x/ccv/consumer/types" @@ -417,75 +416,6 @@ 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 diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 2ec2c2da9d..7428e14cb5 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -132,8 +132,6 @@ 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: diff --git a/tests/e2e/state.go b/tests/e2e/state.go index d0d908a494..059f541ced 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -74,16 +74,6 @@ 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, @@ -454,15 +444,6 @@ 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), diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 1183e0bda5..b11d5d932c 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -23,9 +23,7 @@ var happyPathSteps = concatSteps( stepsDowntimeWithOptOut("consu"), stepsRedelegate("consu"), stepsDowntime("consu"), - stepsRejectEquivocationProposal("consu", 2), // prop to tombstone bob is rejected stepsDoubleSignOnProviderAndConsumer("consu"), // carol double signs on provider, bob double signs on consumer - stepsSubmitEquivocationProposal("consu", 2), // now prop to tombstone bob is submitted and accepted stepsStartRelayer(), stepsConsumerRemovalPropNotPassing("consu", 3), // submit removal prop but vote no on it - chain should stay stepsStopChain("consu", 4), // stop chain diff --git a/tests/e2e/steps_submit_equivocation_proposal.go b/tests/e2e/steps_submit_equivocation_proposal.go deleted file mode 100644 index 8af1d2464d..0000000000 --- a/tests/e2e/steps_submit_equivocation_proposal.go +++ /dev/null @@ -1,150 +0,0 @@ -package main - -import "time" - -// submits an invalid equivocation proposal which should be rejected -func stepsRejectEquivocationProposal(consumerName string, propNumber uint) []Step { - return []Step{ - { - // bob submits a proposal to slash himself - action: submitEquivocationProposalAction{ - chain: chainID("consu"), - from: validatorID("bob"), - deposit: 10000001, - height: 10, - time: time.Now(), - power: 500, - validator: validatorID("bob"), - }, - state: State{ - chainID("provi"): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 500, - validatorID("carol"): 495, - }, - ValBalances: &map[validatorID]uint{ - validatorID("bob"): 9500000000, - }, - Proposals: &map[uint]Proposal{ - // proposal does not exist - propNumber: TextProposal{}, - }, - }, - chainID(consumerName): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 500, - validatorID("carol"): 495, - }, - }, - }, - }, - } -} - -// submits an equivocation proposal, votes on it, and tomstones the equivocating validator -func stepsSubmitEquivocationProposal(consumerName string, propNumber uint) []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"): 509, - validatorID("bob"): 500, - validatorID("carol"): 0, - }, - ValBalances: &map[validatorID]uint{ - validatorID("bob"): 9489999999, - }, - Proposals: &map[uint]Proposal{ - propNumber: EquivocationProposal{ - Deposit: 10000001, - Status: "PROPOSAL_STATUS_VOTING_PERIOD", - ConsensusAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - Power: 500, - Height: 10, - }, - }, - }, - chainID(consumerName): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 500, - validatorID("carol"): 0, - }, - }, - }, - }, - { - action: voteGovProposalAction{ - chain: chainID("provi"), - from: []validatorID{validatorID("alice"), validatorID("bob"), validatorID("carol")}, - vote: []string{"yes", "yes", "yes"}, - propNumber: propNumber, - }, - state: State{ - chainID("provi"): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 0, // bob is tombstoned after proposal passes - validatorID("carol"): 0, - }, - Proposals: &map[uint]Proposal{ - propNumber: EquivocationProposal{ - Deposit: 10000001, - Status: "PROPOSAL_STATUS_PASSED", - ConsensusAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - Power: 500, - Height: 10, - }, - }, - }, - chainID(consumerName): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 500, // slash not reflected in consumer chain - validatorID("carol"): 0, - }, - }, - }, - }, - { - // relay power change to consumer1 - action: relayPacketsAction{ - chainA: chainID("provi"), - chainB: chainID(consumerName), - port: "provider", - channel: 0, - }, - state: State{ - chainID("provi"): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 0, - validatorID("carol"): 0, - }, - }, - chainID(consumerName): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 0, // slash relayed to consumer chain - validatorID("carol"): 0, - }, - }, - }, - }, - } - - return s -} diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index dffbc1ca09..26606eed40 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -12,15 +12,14 @@ import ( types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/auth/types" types1 "github.com/cosmos/cosmos-sdk/x/capability/types" - types2 "github.com/cosmos/cosmos-sdk/x/evidence/types" - types3 "github.com/cosmos/cosmos-sdk/x/slashing/types" - types4 "github.com/cosmos/cosmos-sdk/x/staking/types" - types5 "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" - types6 "github.com/cosmos/ibc-go/v4/modules/core/03-connection/types" - types7 "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" + types2 "github.com/cosmos/cosmos-sdk/x/slashing/types" + types3 "github.com/cosmos/cosmos-sdk/x/staking/types" + types4 "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" + types5 "github.com/cosmos/ibc-go/v4/modules/core/03-connection/types" + types6 "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" exported "github.com/cosmos/ibc-go/v4/modules/core/exported" gomock "github.com/golang/mock/gomock" - types8 "github.com/tendermint/tendermint/abci/types" + types7 "github.com/tendermint/tendermint/abci/types" ) // MockStakingKeeper is a mock of StakingKeeper interface. @@ -61,10 +60,10 @@ func (mr *MockStakingKeeperMockRecorder) BondDenom(ctx interface{}) *gomock.Call } // Delegation mocks base method. -func (m *MockStakingKeeper) Delegation(ctx types.Context, addr types.AccAddress, valAddr types.ValAddress) types4.DelegationI { +func (m *MockStakingKeeper) Delegation(ctx types.Context, addr types.AccAddress, valAddr types.ValAddress) types3.DelegationI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Delegation", ctx, addr, valAddr) - ret0, _ := ret[0].(types4.DelegationI) + ret0, _ := ret[0].(types3.DelegationI) return ret0 } @@ -103,10 +102,10 @@ func (mr *MockStakingKeeperMockRecorder) GetLastValidatorPower(ctx, operator int } // GetLastValidators mocks base method. -func (m *MockStakingKeeper) GetLastValidators(ctx types.Context) []types4.Validator { +func (m *MockStakingKeeper) GetLastValidators(ctx types.Context) []types3.Validator { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetLastValidators", ctx) - ret0, _ := ret[0].([]types4.Validator) + ret0, _ := ret[0].([]types3.Validator) return ret0 } @@ -145,10 +144,10 @@ func (mr *MockStakingKeeperMockRecorder) GetUnbondingDelegationsFromValidator(ct } // GetUnbondingType mocks base method. -func (m *MockStakingKeeper) GetUnbondingType(ctx types.Context, id uint64) (types4.UnbondingType, bool) { +func (m *MockStakingKeeper) GetUnbondingType(ctx types.Context, id uint64) (types3.UnbondingType, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetUnbondingType", ctx, id) - ret0, _ := ret[0].(types4.UnbondingType) + ret0, _ := ret[0].(types3.UnbondingType) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -160,10 +159,10 @@ func (mr *MockStakingKeeperMockRecorder) GetUnbondingType(ctx, id interface{}) * } // GetValidator mocks base method. -func (m *MockStakingKeeper) GetValidator(ctx types.Context, addr types.ValAddress) (types4.Validator, bool) { +func (m *MockStakingKeeper) GetValidator(ctx types.Context, addr types.ValAddress) (types3.Validator, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidator", ctx, addr) - ret0, _ := ret[0].(types4.Validator) + ret0, _ := ret[0].(types3.Validator) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -175,10 +174,10 @@ func (mr *MockStakingKeeperMockRecorder) GetValidator(ctx, addr interface{}) *go } // GetValidatorByConsAddr mocks base method. -func (m *MockStakingKeeper) GetValidatorByConsAddr(ctx types.Context, consAddr types.ConsAddress) (types4.Validator, bool) { +func (m *MockStakingKeeper) GetValidatorByConsAddr(ctx types.Context, consAddr types.ConsAddress) (types3.Validator, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidatorByConsAddr", ctx, consAddr) - ret0, _ := ret[0].(types4.Validator) + ret0, _ := ret[0].(types3.Validator) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -190,10 +189,10 @@ func (mr *MockStakingKeeperMockRecorder) GetValidatorByConsAddr(ctx, consAddr in } // GetValidatorUpdates mocks base method. -func (m *MockStakingKeeper) GetValidatorUpdates(ctx types.Context) []types8.ValidatorUpdate { +func (m *MockStakingKeeper) GetValidatorUpdates(ctx types.Context) []types7.ValidatorUpdate { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidatorUpdates", ctx) - ret0, _ := ret[0].([]types8.ValidatorUpdate) + ret0, _ := ret[0].([]types7.ValidatorUpdate) return ret0 } @@ -230,7 +229,7 @@ func (mr *MockStakingKeeperMockRecorder) IterateLastValidatorPowers(ctx, cb inte } // IterateValidators mocks base method. -func (m *MockStakingKeeper) IterateValidators(ctx types.Context, f func(int64, types4.ValidatorI) bool) { +func (m *MockStakingKeeper) IterateValidators(ctx types.Context, f func(int64, types3.ValidatorI) bool) { m.ctrl.T.Helper() m.ctrl.Call(m, "IterateValidators", ctx, f) } @@ -296,7 +295,7 @@ func (mr *MockStakingKeeperMockRecorder) PutUnbondingOnHold(ctx, id interface{}) } // Slash mocks base method. -func (m *MockStakingKeeper) Slash(arg0 types.Context, arg1 types.ConsAddress, arg2, arg3 int64, arg4 types.Dec, arg5 types4.InfractionType) { +func (m *MockStakingKeeper) Slash(arg0 types.Context, arg1 types.ConsAddress, arg2, arg3 int64, arg4 types.Dec, arg5 types3.InfractionType) { m.ctrl.T.Helper() m.ctrl.Call(m, "Slash", arg0, arg1, arg2, arg3, arg4, arg5) } @@ -376,10 +375,10 @@ func (mr *MockStakingKeeperMockRecorder) Unjail(ctx, addr interface{}) *gomock.C } // Validator mocks base method. -func (m *MockStakingKeeper) Validator(ctx types.Context, addr types.ValAddress) types4.ValidatorI { +func (m *MockStakingKeeper) Validator(ctx types.Context, addr types.ValAddress) types3.ValidatorI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Validator", ctx, addr) - ret0, _ := ret[0].(types4.ValidatorI) + ret0, _ := ret[0].(types3.ValidatorI) return ret0 } @@ -390,10 +389,10 @@ func (mr *MockStakingKeeperMockRecorder) Validator(ctx, addr interface{}) *gomoc } // ValidatorByConsAddr mocks base method. -func (m *MockStakingKeeper) ValidatorByConsAddr(ctx types.Context, consAddr types.ConsAddress) types4.ValidatorI { +func (m *MockStakingKeeper) ValidatorByConsAddr(ctx types.Context, consAddr types.ConsAddress) types3.ValidatorI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ValidatorByConsAddr", ctx, consAddr) - ret0, _ := ret[0].(types4.ValidatorI) + ret0, _ := ret[0].(types3.ValidatorI) return ret0 } @@ -403,41 +402,6 @@ func (mr *MockStakingKeeperMockRecorder) ValidatorByConsAddr(ctx, consAddr inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidatorByConsAddr", reflect.TypeOf((*MockStakingKeeper)(nil).ValidatorByConsAddr), ctx, consAddr) } -// MockEvidenceKeeper is a mock of EvidenceKeeper interface. -type MockEvidenceKeeper struct { - ctrl *gomock.Controller - recorder *MockEvidenceKeeperMockRecorder -} - -// MockEvidenceKeeperMockRecorder is the mock recorder for MockEvidenceKeeper. -type MockEvidenceKeeperMockRecorder struct { - mock *MockEvidenceKeeper -} - -// NewMockEvidenceKeeper creates a new mock instance. -func NewMockEvidenceKeeper(ctrl *gomock.Controller) *MockEvidenceKeeper { - mock := &MockEvidenceKeeper{ctrl: ctrl} - mock.recorder = &MockEvidenceKeeperMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockEvidenceKeeper) EXPECT() *MockEvidenceKeeperMockRecorder { - return m.recorder -} - -// HandleEquivocationEvidence mocks base method. -func (m *MockEvidenceKeeper) HandleEquivocationEvidence(ctx types.Context, evidence *types2.Equivocation) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "HandleEquivocationEvidence", ctx, evidence) -} - -// HandleEquivocationEvidence indicates an expected call of HandleEquivocationEvidence. -func (mr *MockEvidenceKeeperMockRecorder) HandleEquivocationEvidence(ctx, evidence interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleEquivocationEvidence", reflect.TypeOf((*MockEvidenceKeeper)(nil).HandleEquivocationEvidence), ctx, evidence) -} - // MockSlashingKeeper is a mock of SlashingKeeper interface. type MockSlashingKeeper struct { ctrl *gomock.Controller @@ -476,10 +440,10 @@ func (mr *MockSlashingKeeperMockRecorder) DowntimeJailDuration(arg0 interface{}) } // GetValidatorSigningInfo mocks base method. -func (m *MockSlashingKeeper) GetValidatorSigningInfo(ctx types.Context, address types.ConsAddress) (types3.ValidatorSigningInfo, bool) { +func (m *MockSlashingKeeper) GetValidatorSigningInfo(ctx types.Context, address types.ConsAddress) (types2.ValidatorSigningInfo, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidatorSigningInfo", ctx, address) - ret0, _ := ret[0].(types3.ValidatorSigningInfo) + ret0, _ := ret[0].(types2.ValidatorSigningInfo) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -594,10 +558,10 @@ func (mr *MockChannelKeeperMockRecorder) ChanCloseInit(ctx, portID, channelID, c } // GetChannel mocks base method. -func (m *MockChannelKeeper) GetChannel(ctx types.Context, srcPort, srcChan string) (types7.Channel, bool) { +func (m *MockChannelKeeper) GetChannel(ctx types.Context, srcPort, srcChan string) (types6.Channel, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetChannel", ctx, srcPort, srcChan) - ret0, _ := ret[0].(types7.Channel) + ret0, _ := ret[0].(types6.Channel) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -712,10 +676,10 @@ func (m *MockConnectionKeeper) EXPECT() *MockConnectionKeeperMockRecorder { } // GetConnection mocks base method. -func (m *MockConnectionKeeper) GetConnection(ctx types.Context, connectionID string) (types6.ConnectionEnd, bool) { +func (m *MockConnectionKeeper) GetConnection(ctx types.Context, connectionID string) (types5.ConnectionEnd, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetConnection", ctx, connectionID) - ret0, _ := ret[0].(types6.ConnectionEnd) + ret0, _ := ret[0].(types5.ConnectionEnd) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -1062,7 +1026,7 @@ func (m *MockIBCTransferKeeper) EXPECT() *MockIBCTransferKeeperMockRecorder { } // SendTransfer mocks base method. -func (m *MockIBCTransferKeeper) SendTransfer(ctx types.Context, sourcePort, sourceChannel string, token types.Coin, sender types.AccAddress, receiver string, timeoutHeight types5.Height, timeoutTimestamp uint64) error { +func (m *MockIBCTransferKeeper) SendTransfer(ctx types.Context, sourcePort, sourceChannel string, token types.Coin, sender types.AccAddress, receiver string, timeoutHeight types4.Height, timeoutTimestamp uint64) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendTransfer", ctx, sourcePort, sourceChannel, token, sender, receiver, timeoutHeight, timeoutTimestamp) ret0, _ := ret[0].(error) @@ -1099,10 +1063,10 @@ func (m *MockIBCCoreKeeper) EXPECT() *MockIBCCoreKeeperMockRecorder { } // ChannelOpenInit mocks base method. -func (m *MockIBCCoreKeeper) ChannelOpenInit(goCtx context.Context, msg *types7.MsgChannelOpenInit) (*types7.MsgChannelOpenInitResponse, error) { +func (m *MockIBCCoreKeeper) ChannelOpenInit(goCtx context.Context, msg *types6.MsgChannelOpenInit) (*types6.MsgChannelOpenInitResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ChannelOpenInit", goCtx, msg) - ret0, _ := ret[0].(*types7.MsgChannelOpenInitResponse) + ret0, _ := ret[0].(*types6.MsgChannelOpenInitResponse) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index 77a597c726..7e32927563 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -87,7 +87,6 @@ type MockedKeepers struct { *MockBankKeeper *MockIBCTransferKeeper *MockIBCCoreKeeper - *MockEvidenceKeeper *MockDistributionKeeper } @@ -105,7 +104,6 @@ func NewMockedKeepers(ctrl *gomock.Controller) MockedKeepers { MockBankKeeper: NewMockBankKeeper(ctrl), MockIBCTransferKeeper: NewMockIBCTransferKeeper(ctrl), MockIBCCoreKeeper: NewMockIBCCoreKeeper(ctrl), - MockEvidenceKeeper: NewMockEvidenceKeeper(ctrl), MockDistributionKeeper: NewMockDistributionKeeper(ctrl), } } @@ -124,7 +122,6 @@ func NewInMemProviderKeeper(params InMemKeeperParams, mocks MockedKeepers) provi mocks.MockStakingKeeper, mocks.MockSlashingKeeper, mocks.MockAccountKeeper, - mocks.MockEvidenceKeeper, mocks.MockDistributionKeeper, mocks.MockBankKeeper, authtypes.FeeCollectorName, diff --git a/x/ccv/provider/client/proposal_handler.go b/x/ccv/provider/client/proposal_handler.go index 499f4e3b34..642cb529e8 100644 --- a/x/ccv/provider/client/proposal_handler.go +++ b/x/ccv/provider/client/proposal_handler.go @@ -25,7 +25,6 @@ import ( var ( ConsumerAdditionProposalHandler = govclient.NewProposalHandler(SubmitConsumerAdditionPropTxCmd, ConsumerAdditionProposalRESTHandler) ConsumerRemovalProposalHandler = govclient.NewProposalHandler(SubmitConsumerRemovalProposalTxCmd, ConsumerRemovalProposalRESTHandler) - EquivocationProposalHandler = govclient.NewProposalHandler(SubmitEquivocationProposalTxCmd, EquivocationProposalRESTHandler) ChangeRewardDenomsProposalHandler = govclient.NewProposalHandler(SubmitChangeRewardDenomsProposalTxCmd, ChangeRewardDenomsProposalRESTHandler) ) @@ -158,64 +157,6 @@ Where proposal.json contains: } } -// SubmitEquivocationProposalTxCmd returns a CLI command handler for submitting -// a equivocation proposal via a transaction. -func SubmitEquivocationProposalTxCmd() *cobra.Command { - return &cobra.Command{ - Use: "equivocation [proposal-file]", - Args: cobra.ExactArgs(1), - Short: "Submit an equivocation proposal", - Long: fmt.Sprintf(`Submit an equivocation proposal along with an initial deposit. -The proposal details must be supplied via a JSON file. - -Example: -$ tx gov submit-proposal equivocation --from= - -Where proposal.json contains: -{ - "title": "Equivoque Foo validator", - "description": "He double-signs on the Foobar consumer chain", - "equivocations": [ - { - "height": 10420042, - "time": "2023-01-27T15:59:50.121607-08:00", - "power": 10, - "consensus_address": "%s1s5afhd6gxevu37mkqcvvsj8qeylhn0rz46zdlq" - } - ], - "deposit": "10000stake" -} -`, sdk.GetConfig().GetBech32ConsensusAddrPrefix()), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - proposal, err := ParseEquivocationProposalJSON(args[0]) - if err != nil { - return err - } - - content := types.NewEquivocationProposal(proposal.Title, proposal.Description, proposal.Equivocations) - - from := clientCtx.GetFromAddress() - - deposit, err := sdk.ParseCoinsNormalized(proposal.Deposit) - if err != nil { - return err - } - - msg, err := govtypes.NewMsgSubmitProposal(content, deposit, from) - if err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - } -} - // SubmitChangeRewardDenomsProposalTxCmd returns a CLI command handler for submitting // a change reward denoms proposal via a transaction. func SubmitChangeRewardDenomsProposalTxCmd() *cobra.Command { @@ -361,46 +302,6 @@ func ParseConsumerRemovalProposalJSON(proposalFile string) (ConsumerRemovalPropo return proposal, nil } -type EquivocationProposalJSON struct { - // evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - types.EquivocationProposal - - Deposit string `json:"deposit"` -} - -type EquivocationProposalReq struct { - BaseReq rest.BaseReq `json:"base_req"` - Proposer sdk.AccAddress `json:"proposer"` - - // evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - types.EquivocationProposal - - Deposit sdk.Coins `json:"deposit"` -} - -func ParseEquivocationProposalJSON(proposalFile string) (EquivocationProposalJSON, error) { - proposal := EquivocationProposalJSON{} - - contents, err := os.ReadFile(filepath.Clean(proposalFile)) - if err != nil { - return proposal, err - } - - if err := json.Unmarshal(contents, &proposal); err != nil { - return proposal, err - } - - return proposal, nil -} - -// EquivocationProposalRESTHandler returns a ProposalRESTHandler that exposes the equivocation rest handler. -func EquivocationProposalRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler { - return govrest.ProposalRESTHandler{ - SubRoute: "equivocation", - Handler: postEquivocationProposalHandlerFn(clientCtx), - } -} - type ChangeRewardDenomsProposalJSON struct { Summary string `json:"summary"` types.ChangeRewardDenomsProposal @@ -511,33 +412,6 @@ func postConsumerRemovalProposalHandlerFn(clientCtx client.Context) http.Handler } } -func postEquivocationProposalHandlerFn(clientCtx client.Context) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var req EquivocationProposalReq - if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) { - return - } - - req.BaseReq = req.BaseReq.Sanitize() - if !req.BaseReq.ValidateBasic(w) { - return - } - - content := types.NewEquivocationProposal(req.Title, req.Description, req.Equivocations) - - msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer) - if rest.CheckBadRequestError(w, err) { - return - } - - if rest.CheckBadRequestError(w, msg.ValidateBasic()) { - return - } - - tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg) - } -} - func CheckPropUnbondingPeriod(clientCtx client.Context, propUnbondingPeriod time.Duration) { queryClient := stakingtypes.NewQueryClient(clientCtx) diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 7494057643..919996c2e9 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -39,7 +39,6 @@ type Keeper struct { stakingKeeper ccv.StakingKeeper slashingKeeper ccv.SlashingKeeper accountKeeper ccv.AccountKeeper - evidenceKeeper ccv.EvidenceKeeper distributionKeeper ccv.DistributionKeeper bankKeeper ccv.BankKeeper feeCollectorName string @@ -51,8 +50,8 @@ func NewKeeper( channelKeeper ccv.ChannelKeeper, portKeeper ccv.PortKeeper, connectionKeeper ccv.ConnectionKeeper, clientKeeper ccv.ClientKeeper, stakingKeeper ccv.StakingKeeper, slashingKeeper ccv.SlashingKeeper, - accountKeeper ccv.AccountKeeper, evidenceKeeper ccv.EvidenceKeeper, - distributionKeeper ccv.DistributionKeeper, bankKeeper ccv.BankKeeper, + accountKeeper ccv.AccountKeeper, distributionKeeper ccv.DistributionKeeper, + bankKeeper ccv.BankKeeper, feeCollectorName string, ) Keeper { // set KeyTable if it has not already been set @@ -72,7 +71,6 @@ func NewKeeper( stakingKeeper: stakingKeeper, slashingKeeper: slashingKeeper, accountKeeper: accountKeeper, - evidenceKeeper: evidenceKeeper, distributionKeeper: distributionKeeper, bankKeeper: bankKeeper, feeCollectorName: feeCollectorName, @@ -92,8 +90,8 @@ func (k *Keeper) SetParamSpace(ctx sdk.Context, ps paramtypes.Subspace) { // non-nil values for all its fields. Otherwise this method will panic. func (k Keeper) mustValidateFields() { // Ensures no fields are missed in this validation - if reflect.ValueOf(k).NumField() != 15 { - panic("number of fields in provider keeper is not 15") + if reflect.ValueOf(k).NumField() != 14 { + panic("number of fields in provider keeper is not 14") } ccv.PanicIfZeroOrNil(k.cdc, "cdc") // 1 @@ -107,7 +105,6 @@ func (k Keeper) mustValidateFields() { ccv.PanicIfZeroOrNil(k.clientKeeper, "clientKeeper") // 9 ccv.PanicIfZeroOrNil(k.stakingKeeper, "stakingKeeper") // 10 ccv.PanicIfZeroOrNil(k.slashingKeeper, "slashingKeeper") // 11 - ccv.PanicIfZeroOrNil(k.evidenceKeeper, "evidenceKeeper") // 12 ccv.PanicIfZeroOrNil(k.distributionKeeper, "distributionKeeper") // 13 ccv.PanicIfZeroOrNil(k.bankKeeper, "bankKeeper") // 14 ccv.PanicIfZeroOrNil(k.feeCollectorName, "feeCollectorName") // 15 diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 10c9339735..6fb6e8e6f2 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -604,18 +604,6 @@ func (k Keeper) StopConsumerChainInCachedCtx(ctx sdk.Context, p types.ConsumerRe return } -// HandleEquivocationProposal handles an equivocation proposal. -// Proposal will be accepted if a record in the SlashLog exists for a given validator address. -func (k Keeper) HandleEquivocationProposal(ctx sdk.Context, p *types.EquivocationProposal) error { - for _, ev := range p.Equivocations { - if !k.GetSlashLog(ctx, types.NewProviderConsAddress(ev.GetConsensusAddress())) { - return fmt.Errorf("no equivocation record found for validator %s", ev.GetConsensusAddress().String()) - } - k.evidenceKeeper.HandleEquivocationEvidence(ctx, ev) - } - return nil -} - func (k Keeper) HandleConsumerRewardDenomProposal(ctx sdk.Context, p *types.ChangeRewardDenomsProposal) error { for _, denomToAdd := range p.DenomsToAdd { // Log error and move on if one of the denoms is already registered diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index 68ee7e9663..cbaf58ef38 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -9,7 +9,6 @@ import ( _go "github.com/confio/ics23/go" sdk "github.com/cosmos/cosmos-sdk/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" "github.com/golang/mock/gomock" @@ -1040,63 +1039,3 @@ func TestBeginBlockCCR(t *testing.T) { ctx, invalidProp.ChainId, invalidProp.StopTime) require.False(t, found) } - -func TestHandleEquivocationProposal(t *testing.T) { - equivocations := []*evidencetypes.Equivocation{ - { - Time: time.Now(), - Height: 1, - Power: 1, - ConsensusAddress: "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", - }, - { - Time: time.Now(), - Height: 1, - Power: 1, - ConsensusAddress: "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", - }, - } - - prop := &providertypes.EquivocationProposal{ - Equivocations: []*evidencetypes.Equivocation{equivocations[0], equivocations[1]}, - } - - testCases := []struct { - name string - setSlashLogs bool - expectEquivsHandled bool - expectErr bool - }{ - {name: "slash logs not set", setSlashLogs: false, expectEquivsHandled: false, expectErr: true}, - {name: "slash logs set", setSlashLogs: true, expectEquivsHandled: true, expectErr: false}, - } - for _, tc := range testCases { - - keeperParams := testkeeper.NewInMemKeeperParams(t) - keeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) - - if tc.setSlashLogs { - // Set slash logs according to cons addrs in equivocations - consAddr := equivocations[0].GetConsensusAddress() - require.NotNil(t, consAddr, "consensus address could not be parsed") - keeper.SetSlashLog(ctx, providertypes.NewProviderConsAddress(consAddr)) - consAddr = equivocations[1].GetConsensusAddress() - require.NotNil(t, consAddr, "consensus address could not be parsed") - keeper.SetSlashLog(ctx, providertypes.NewProviderConsAddress(consAddr)) - } - - if tc.expectEquivsHandled { - mocks.MockEvidenceKeeper.EXPECT().HandleEquivocationEvidence(ctx, equivocations[0]) - mocks.MockEvidenceKeeper.EXPECT().HandleEquivocationEvidence(ctx, equivocations[1]) - } - - err := keeper.HandleEquivocationProposal(ctx, prop) - - if tc.expectErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - ctrl.Finish() - } -} diff --git a/x/ccv/provider/proposal_handler.go b/x/ccv/provider/proposal_handler.go index 83ff370f43..9299ed0777 100644 --- a/x/ccv/provider/proposal_handler.go +++ b/x/ccv/provider/proposal_handler.go @@ -10,7 +10,7 @@ import ( ) // NewProviderProposalHandler defines the handler for consumer addition, -// consumer removal and equivocation proposals. +// consumer removal, and consumer reward denom proposals. // Passed proposals are executed during EndBlock. func NewProviderProposalHandler(k keeper.Keeper) govtypes.Handler { return func(ctx sdk.Context, content govtypes.Content) error { @@ -19,8 +19,6 @@ func NewProviderProposalHandler(k keeper.Keeper) govtypes.Handler { return k.HandleConsumerAdditionProposal(ctx, c) case *types.ConsumerRemovalProposal: return k.HandleConsumerRemovalProposal(ctx, c) - case *types.EquivocationProposal: - return k.HandleEquivocationProposal(ctx, c) case *types.ChangeRewardDenomsProposal: return k.HandleConsumerRewardDenomProposal(ctx, c) default: diff --git a/x/ccv/provider/proposal_handler_test.go b/x/ccv/provider/proposal_handler_test.go index b171b2ce31..c7839df25d 100644 --- a/x/ccv/provider/proposal_handler_test.go +++ b/x/ccv/provider/proposal_handler_test.go @@ -6,7 +6,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" @@ -19,12 +18,11 @@ import ( ) // TestProviderProposalHandler tests the highest level handler for proposals -// concerning creating, stopping consumer chains and submitting equivocations. +// concerning creating, stopping consumer chains and changing reward denom. func TestProviderProposalHandler(t *testing.T) { // Snapshot times asserted in tests now := time.Now().UTC() hourFromNow := now.Add(time.Hour).UTC() - equivocation := &evidencetypes.Equivocation{Height: 42} testCases := []struct { name string @@ -32,7 +30,6 @@ func TestProviderProposalHandler(t *testing.T) { blockTime time.Time expValidConsumerAddition bool expValidConsumerRemoval bool - expValidEquivocation bool expValidChangeRewardDenom bool }{ { @@ -58,21 +55,6 @@ func TestProviderProposalHandler(t *testing.T) { blockTime: hourFromNow, expValidConsumerRemoval: true, }, - { - // no slash log for equivocation - name: "invalid equivocation proposal", - content: providertypes.NewEquivocationProposal( - "title", "description", []*evidencetypes.Equivocation{equivocation}), - blockTime: hourFromNow, - expValidEquivocation: false, - }, - { - name: "valid equivocation proposal", - content: providertypes.NewEquivocationProposal( - "title", "description", []*evidencetypes.Equivocation{equivocation}), - blockTime: hourFromNow, - expValidEquivocation: true, - }, { name: "valid change reward denoms proposal", content: providertypes.NewChangeRewardDenomsProposal( @@ -111,9 +93,6 @@ func TestProviderProposalHandler(t *testing.T) { case tc.expValidConsumerRemoval: testkeeper.SetupForStoppingConsumerChain(t, ctx, &providerKeeper, mocks) - case tc.expValidEquivocation: - providerKeeper.SetSlashLog(ctx, providertypes.NewProviderConsAddress(equivocation.GetConsensusAddress())) - mocks.MockEvidenceKeeper.EXPECT().HandleEquivocationEvidence(ctx, equivocation) case tc.expValidChangeRewardDenom: // Nothing to mock } @@ -123,7 +102,7 @@ func TestProviderProposalHandler(t *testing.T) { err := proposalHandler(ctx, tc.content) if tc.expValidConsumerAddition || tc.expValidConsumerRemoval || - tc.expValidEquivocation || tc.expValidChangeRewardDenom { + tc.expValidChangeRewardDenom { require.NoError(t, err) } else { require.Error(t, err) diff --git a/x/ccv/provider/types/codec.go b/x/ccv/provider/types/codec.go index 919386a1b4..de4066a6a4 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -27,10 +27,6 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { (*sdk.Msg)(nil), &MsgAssignConsumerKey{}, ) - registry.RegisterImplementations( - (*govtypes.Content)(nil), - &EquivocationProposal{}, - ) registry.RegisterImplementations( (*govtypes.Content)(nil), &ChangeRewardDenomsProposal{}, diff --git a/x/ccv/provider/types/proposal.go b/x/ccv/provider/types/proposal.go index 6c1ff5057c..d77be8591e 100644 --- a/x/ccv/provider/types/proposal.go +++ b/x/ccv/provider/types/proposal.go @@ -1,7 +1,6 @@ package types import ( - "errors" "fmt" "strings" time "time" @@ -9,7 +8,6 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" ccvtypes "github.com/cosmos/interchain-security/v2/x/ccv/types" @@ -18,21 +16,18 @@ import ( const ( ProposalTypeConsumerAddition = "ConsumerAddition" ProposalTypeConsumerRemoval = "ConsumerRemoval" - ProposalTypeEquivocation = "Equivocation" ProposalTypeChangeRewardDenoms = "ChangeRewardDenoms" ) var ( _ govtypes.Content = &ConsumerAdditionProposal{} _ govtypes.Content = &ConsumerRemovalProposal{} - _ govtypes.Content = &EquivocationProposal{} _ govtypes.Content = &ChangeRewardDenomsProposal{} ) func init() { govtypes.RegisterProposalType(ProposalTypeConsumerAddition) govtypes.RegisterProposalType(ProposalTypeConsumerRemoval) - govtypes.RegisterProposalType(ProposalTypeEquivocation) govtypes.RegisterProposalType(ProposalTypeChangeRewardDenoms) } @@ -201,39 +196,6 @@ func (sccp *ConsumerRemovalProposal) ValidateBasic() error { return nil } -// NewEquivocationProposal creates a new equivocation proposal. -func NewEquivocationProposal(title, description string, equivocations []*evidencetypes.Equivocation) govtypes.Content { - return &EquivocationProposal{ - Title: title, - Description: description, - Equivocations: equivocations, - } -} - -// ProposalRoute returns the routing key of an equivocation proposal. -func (sp *EquivocationProposal) ProposalRoute() string { return RouterKey } - -// ProposalType returns the type of a equivocation proposal. -func (sp *EquivocationProposal) ProposalType() string { - return ProposalTypeEquivocation -} - -// ValidateBasic runs basic stateless validity checks -func (sp *EquivocationProposal) ValidateBasic() error { - if err := govtypes.ValidateAbstract(sp); err != nil { - return err - } - if len(sp.Equivocations) == 0 { - return errors.New("invalid equivocation proposal: empty equivocations") - } - for i := 0; i < len(sp.Equivocations); i++ { - if err := sp.Equivocations[i].ValidateBasic(); err != nil { - return err - } - } - return nil -} - func NewChangeRewardDenomsProposal(title, description string, denomsToAdd, denomsToRemove []string, ) govtypes.Content { diff --git a/x/ccv/provider/types/proposal_test.go b/x/ccv/provider/types/proposal_test.go index 165450f8b2..5900facbaf 100644 --- a/x/ccv/provider/types/proposal_test.go +++ b/x/ccv/provider/types/proposal_test.go @@ -5,8 +5,6 @@ import ( "testing" "time" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - "github.com/golang/protobuf/proto" //nolint:staticcheck // see: https://github.com/cosmos/interchain-security/issues/236 "github.com/stretchr/testify/require" @@ -307,62 +305,6 @@ func TestConsumerAdditionProposalString(t *testing.T) { require.Equal(t, expect, proposal.String(), "string method for ConsumerAdditionProposal returned unexpected string") } -func TestEquivocationProposalValidateBasic(t *testing.T) { - tests := []struct { - name string - proposal govtypes.Content - expectedError string - }{ - { - name: "fail: validate abstract - empty title", - proposal: types.NewEquivocationProposal("", "", nil), - expectedError: "proposal title cannot be blank: invalid proposal content", - }, - { - name: "fail: equivocations is empty", - proposal: types.NewEquivocationProposal("title", "desc", nil), - expectedError: "invalid equivocation proposal: empty equivocations", - }, - { - name: "fail: invalid equivocation", - proposal: types.NewEquivocationProposal("title", "desc", - []*evidencetypes.Equivocation{ - { - Time: time.Now(), - Height: 1, - Power: 1, - ConsensusAddress: "addr", - }, - {}, // invalid one - }), - expectedError: "invalid equivocation time: 0001-01-01 00:00:00 +0000 UTC", - }, - { - name: "ok", - proposal: types.NewEquivocationProposal("title", "desc", - []*evidencetypes.Equivocation{ - { - Time: time.Now(), - Height: 1, - Power: 1, - ConsensusAddress: "addr", - }, - }), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.proposal.ValidateBasic() - - if tt.expectedError != "" { - require.EqualError(t, err, tt.expectedError) - return - } - require.NoError(t, err) - }) - } -} - func TestChangeRewardDenomsProposalValidateBasic(t *testing.T) { tcs := []struct { name string diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index 2146e0a188..59439f4a2f 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -5,10 +5,9 @@ package types import ( fmt "fmt" - types3 "github.com/cosmos/cosmos-sdk/types" - types1 "github.com/cosmos/cosmos-sdk/x/evidence/types" + types2 "github.com/cosmos/cosmos-sdk/types" types "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" - types2 "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" + types1 "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" @@ -190,69 +189,6 @@ func (m *ConsumerRemovalProposal) GetStopTime() time.Time { return time.Time{} } -type EquivocationProposal struct { - // the title of the proposal - Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` - // the description of the proposal - Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` - // the list of equivocations that will be processed - Equivocations []*types1.Equivocation `protobuf:"bytes,3,rep,name=equivocations,proto3" json:"equivocations,omitempty"` -} - -func (m *EquivocationProposal) Reset() { *m = EquivocationProposal{} } -func (m *EquivocationProposal) String() string { return proto.CompactTextString(m) } -func (*EquivocationProposal) ProtoMessage() {} -func (*EquivocationProposal) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{2} -} -func (m *EquivocationProposal) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *EquivocationProposal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_EquivocationProposal.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *EquivocationProposal) XXX_Merge(src proto.Message) { - xxx_messageInfo_EquivocationProposal.Merge(m, src) -} -func (m *EquivocationProposal) XXX_Size() int { - return m.Size() -} -func (m *EquivocationProposal) XXX_DiscardUnknown() { - xxx_messageInfo_EquivocationProposal.DiscardUnknown(m) -} - -var xxx_messageInfo_EquivocationProposal proto.InternalMessageInfo - -func (m *EquivocationProposal) GetTitle() string { - if m != nil { - return m.Title - } - return "" -} - -func (m *EquivocationProposal) GetDescription() string { - if m != nil { - return m.Description - } - return "" -} - -func (m *EquivocationProposal) GetEquivocations() []*types1.Equivocation { - if m != nil { - return m.Equivocations - } - return nil -} - // ChangeRewardDenomsProposal is a governance proposal on the provider chain to // mutate the set of denoms accepted by the provider as rewards. type ChangeRewardDenomsProposal struct { @@ -270,7 +206,7 @@ func (m *ChangeRewardDenomsProposal) Reset() { *m = ChangeRewardDenomsPr func (m *ChangeRewardDenomsProposal) String() string { return proto.CompactTextString(m) } func (*ChangeRewardDenomsProposal) ProtoMessage() {} func (*ChangeRewardDenomsProposal) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{3} + return fileDescriptor_f22ec409a72b7b72, []int{2} } func (m *ChangeRewardDenomsProposal) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -349,7 +285,7 @@ func (m *GlobalSlashEntry) Reset() { *m = GlobalSlashEntry{} } func (m *GlobalSlashEntry) String() string { return proto.CompactTextString(m) } func (*GlobalSlashEntry) ProtoMessage() {} func (*GlobalSlashEntry) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{4} + return fileDescriptor_f22ec409a72b7b72, []int{3} } func (m *GlobalSlashEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -408,7 +344,7 @@ func (m *GlobalSlashEntry) GetProviderValConsAddr() []byte { // Params defines the parameters for CCV Provider module type Params struct { - TemplateClient *types2.ClientState `protobuf:"bytes,1,opt,name=template_client,json=templateClient,proto3" json:"template_client,omitempty"` + TemplateClient *types1.ClientState `protobuf:"bytes,1,opt,name=template_client,json=templateClient,proto3" json:"template_client,omitempty"` // TrustingPeriodFraction is used to compute the consumer and provider IBC client's TrustingPeriod from the chain defined UnbondingPeriod TrustingPeriodFraction string `protobuf:"bytes,2,opt,name=trusting_period_fraction,json=trustingPeriodFraction,proto3" json:"trusting_period_fraction,omitempty"` // Sent IBC packets will timeout after this duration @@ -429,14 +365,14 @@ type Params struct { // that can be queued for a single consumer before the provider chain halts. MaxThrottledPackets int64 `protobuf:"varint,8,opt,name=max_throttled_packets,json=maxThrottledPackets,proto3" json:"max_throttled_packets,omitempty"` // The fee required to be paid to add a reward denom - ConsumerRewardDenomRegistrationFee types3.Coin `protobuf:"bytes,9,opt,name=consumer_reward_denom_registration_fee,json=consumerRewardDenomRegistrationFee,proto3" json:"consumer_reward_denom_registration_fee"` + ConsumerRewardDenomRegistrationFee types2.Coin `protobuf:"bytes,9,opt,name=consumer_reward_denom_registration_fee,json=consumerRewardDenomRegistrationFee,proto3" json:"consumer_reward_denom_registration_fee"` } func (m *Params) Reset() { *m = Params{} } func (m *Params) String() string { return proto.CompactTextString(m) } func (*Params) ProtoMessage() {} func (*Params) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{5} + return fileDescriptor_f22ec409a72b7b72, []int{4} } func (m *Params) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -465,7 +401,7 @@ func (m *Params) XXX_DiscardUnknown() { var xxx_messageInfo_Params proto.InternalMessageInfo -func (m *Params) GetTemplateClient() *types2.ClientState { +func (m *Params) GetTemplateClient() *types1.ClientState { if m != nil { return m.TemplateClient } @@ -521,11 +457,11 @@ func (m *Params) GetMaxThrottledPackets() int64 { return 0 } -func (m *Params) GetConsumerRewardDenomRegistrationFee() types3.Coin { +func (m *Params) GetConsumerRewardDenomRegistrationFee() types2.Coin { if m != nil { return m.ConsumerRewardDenomRegistrationFee } - return types3.Coin{} + return types2.Coin{} } type HandshakeMetadata struct { @@ -537,7 +473,7 @@ func (m *HandshakeMetadata) Reset() { *m = HandshakeMetadata{} } func (m *HandshakeMetadata) String() string { return proto.CompactTextString(m) } func (*HandshakeMetadata) ProtoMessage() {} func (*HandshakeMetadata) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{6} + return fileDescriptor_f22ec409a72b7b72, []int{5} } func (m *HandshakeMetadata) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -590,7 +526,7 @@ func (m *SlashAcks) Reset() { *m = SlashAcks{} } func (m *SlashAcks) String() string { return proto.CompactTextString(m) } func (*SlashAcks) ProtoMessage() {} func (*SlashAcks) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{7} + return fileDescriptor_f22ec409a72b7b72, []int{6} } func (m *SlashAcks) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -636,7 +572,7 @@ func (m *ConsumerAdditionProposals) Reset() { *m = ConsumerAdditionPropo func (m *ConsumerAdditionProposals) String() string { return proto.CompactTextString(m) } func (*ConsumerAdditionProposals) ProtoMessage() {} func (*ConsumerAdditionProposals) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{8} + return fileDescriptor_f22ec409a72b7b72, []int{7} } func (m *ConsumerAdditionProposals) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -682,7 +618,7 @@ func (m *ConsumerRemovalProposals) Reset() { *m = ConsumerRemovalProposa func (m *ConsumerRemovalProposals) String() string { return proto.CompactTextString(m) } func (*ConsumerRemovalProposals) ProtoMessage() {} func (*ConsumerRemovalProposals) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{9} + return fileDescriptor_f22ec409a72b7b72, []int{8} } func (m *ConsumerRemovalProposals) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -727,7 +663,7 @@ func (m *AddressList) Reset() { *m = AddressList{} } func (m *AddressList) String() string { return proto.CompactTextString(m) } func (*AddressList) ProtoMessage() {} func (*AddressList) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{10} + return fileDescriptor_f22ec409a72b7b72, []int{9} } func (m *AddressList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -772,7 +708,7 @@ func (m *ChannelToChain) Reset() { *m = ChannelToChain{} } func (m *ChannelToChain) String() string { return proto.CompactTextString(m) } func (*ChannelToChain) ProtoMessage() {} func (*ChannelToChain) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{11} + return fileDescriptor_f22ec409a72b7b72, []int{10} } func (m *ChannelToChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -826,7 +762,7 @@ func (m *VscUnbondingOps) Reset() { *m = VscUnbondingOps{} } func (m *VscUnbondingOps) String() string { return proto.CompactTextString(m) } func (*VscUnbondingOps) ProtoMessage() {} func (*VscUnbondingOps) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{12} + return fileDescriptor_f22ec409a72b7b72, []int{11} } func (m *VscUnbondingOps) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -881,7 +817,7 @@ func (m *UnbondingOp) Reset() { *m = UnbondingOp{} } func (m *UnbondingOp) String() string { return proto.CompactTextString(m) } func (*UnbondingOp) ProtoMessage() {} func (*UnbondingOp) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{13} + return fileDescriptor_f22ec409a72b7b72, []int{12} } func (m *UnbondingOp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -933,7 +869,7 @@ func (m *InitTimeoutTimestamp) Reset() { *m = InitTimeoutTimestamp{} } func (m *InitTimeoutTimestamp) String() string { return proto.CompactTextString(m) } func (*InitTimeoutTimestamp) ProtoMessage() {} func (*InitTimeoutTimestamp) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{14} + return fileDescriptor_f22ec409a72b7b72, []int{13} } func (m *InitTimeoutTimestamp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -985,7 +921,7 @@ func (m *VscSendTimestamp) Reset() { *m = VscSendTimestamp{} } func (m *VscSendTimestamp) String() string { return proto.CompactTextString(m) } func (*VscSendTimestamp) ProtoMessage() {} func (*VscSendTimestamp) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{15} + return fileDescriptor_f22ec409a72b7b72, []int{14} } func (m *VscSendTimestamp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1038,7 +974,7 @@ func (m *KeyAssignmentReplacement) Reset() { *m = KeyAssignmentReplaceme func (m *KeyAssignmentReplacement) String() string { return proto.CompactTextString(m) } func (*KeyAssignmentReplacement) ProtoMessage() {} func (*KeyAssignmentReplacement) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{16} + return fileDescriptor_f22ec409a72b7b72, []int{15} } func (m *KeyAssignmentReplacement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1100,7 +1036,7 @@ func (m *ValidatorConsumerPubKey) Reset() { *m = ValidatorConsumerPubKey func (m *ValidatorConsumerPubKey) String() string { return proto.CompactTextString(m) } func (*ValidatorConsumerPubKey) ProtoMessage() {} func (*ValidatorConsumerPubKey) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{17} + return fileDescriptor_f22ec409a72b7b72, []int{16} } func (m *ValidatorConsumerPubKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1162,7 +1098,7 @@ func (m *ValidatorByConsumerAddr) Reset() { *m = ValidatorByConsumerAddr func (m *ValidatorByConsumerAddr) String() string { return proto.CompactTextString(m) } func (*ValidatorByConsumerAddr) ProtoMessage() {} func (*ValidatorByConsumerAddr) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{18} + return fileDescriptor_f22ec409a72b7b72, []int{17} } func (m *ValidatorByConsumerAddr) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1224,7 +1160,7 @@ func (m *ConsumerAddrsToPrune) Reset() { *m = ConsumerAddrsToPrune{} } func (m *ConsumerAddrsToPrune) String() string { return proto.CompactTextString(m) } func (*ConsumerAddrsToPrune) ProtoMessage() {} func (*ConsumerAddrsToPrune) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{19} + return fileDescriptor_f22ec409a72b7b72, []int{18} } func (m *ConsumerAddrsToPrune) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1277,7 +1213,6 @@ func (m *ConsumerAddrsToPrune) GetConsumerAddrs() *AddressList { func init() { proto.RegisterType((*ConsumerAdditionProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerAdditionProposal") proto.RegisterType((*ConsumerRemovalProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerRemovalProposal") - proto.RegisterType((*EquivocationProposal)(nil), "interchain_security.ccv.provider.v1.EquivocationProposal") proto.RegisterType((*ChangeRewardDenomsProposal)(nil), "interchain_security.ccv.provider.v1.ChangeRewardDenomsProposal") proto.RegisterType((*GlobalSlashEntry)(nil), "interchain_security.ccv.provider.v1.GlobalSlashEntry") proto.RegisterType((*Params)(nil), "interchain_security.ccv.provider.v1.Params") @@ -1302,111 +1237,107 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1649 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4f, 0x73, 0xdc, 0xb6, - 0x15, 0x17, 0xb5, 0xfa, 0xb7, 0x58, 0xfd, 0x33, 0x2d, 0xc7, 0x2b, 0x57, 0x5d, 0xc9, 0x4c, 0x9b, - 0x51, 0x27, 0x13, 0x6e, 0xa5, 0x5c, 0x3a, 0x9e, 0x66, 0x32, 0xd2, 0x3a, 0x8e, 0x15, 0x35, 0xf1, - 0x86, 0x52, 0xe5, 0x69, 0x7b, 0xe0, 0x80, 0xe0, 0xf3, 0x2e, 0x46, 0x24, 0x41, 0x03, 0x20, 0xed, - 0xbd, 0xf4, 0xdc, 0x63, 0x7a, 0xcb, 0xb4, 0x97, 0xb4, 0x5f, 0xa0, 0x5f, 0x23, 0xc7, 0x1c, 0x7b, - 0x4a, 0x3a, 0xf6, 0xa1, 0x87, 0x7e, 0x89, 0x0e, 0xc0, 0xff, 0x2b, 0x29, 0x5d, 0x4f, 0x9a, 0x1b, - 0x01, 0xfc, 0xde, 0xef, 0x3d, 0xe0, 0x3d, 0xfc, 0x1e, 0x76, 0xd1, 0x21, 0x8d, 0x24, 0x70, 0x32, - 0xc6, 0x34, 0x72, 0x05, 0x90, 0x84, 0x53, 0x39, 0xe9, 0x13, 0x92, 0xf6, 0x63, 0xce, 0x52, 0xea, - 0x03, 0xef, 0xa7, 0x07, 0xe5, 0xb7, 0x1d, 0x73, 0x26, 0x99, 0xf9, 0xf6, 0x35, 0x36, 0x36, 0x21, - 0xa9, 0x5d, 0xe2, 0xd2, 0x83, 0x7b, 0x5b, 0x23, 0x36, 0x62, 0x1a, 0xdf, 0x57, 0x5f, 0x99, 0xe9, - 0xbd, 0xdd, 0x11, 0x63, 0xa3, 0x00, 0xfa, 0x7a, 0xe4, 0x25, 0xcf, 0xfa, 0x92, 0x86, 0x20, 0x24, - 0x0e, 0xe3, 0x1c, 0xd0, 0x9b, 0x06, 0xf8, 0x09, 0xc7, 0x92, 0xb2, 0xa8, 0x20, 0xa0, 0x1e, 0xe9, - 0x13, 0xc6, 0xa1, 0x4f, 0x02, 0x0a, 0x91, 0x54, 0xe1, 0x65, 0x5f, 0x39, 0xa0, 0xaf, 0x00, 0x01, - 0x1d, 0x8d, 0x65, 0x36, 0x2d, 0xfa, 0x12, 0x22, 0x1f, 0x78, 0x48, 0x33, 0x70, 0x35, 0xca, 0x0d, - 0x76, 0x6a, 0xeb, 0x84, 0x4f, 0x62, 0xc9, 0xfa, 0x97, 0x30, 0x11, 0xf9, 0xea, 0x3b, 0x84, 0x89, - 0x90, 0x89, 0x3e, 0xa8, 0x8d, 0x45, 0x04, 0xfa, 0xe9, 0x81, 0x07, 0x12, 0x1f, 0x94, 0x13, 0x45, - 0xdc, 0x39, 0xce, 0xc3, 0xa2, 0xc2, 0x10, 0x46, 0xf3, 0xb8, 0xad, 0xef, 0x96, 0x50, 0x77, 0xc0, - 0x22, 0x91, 0x84, 0xc0, 0x8f, 0x7c, 0x9f, 0xaa, 0x2d, 0x0d, 0x39, 0x8b, 0x99, 0xc0, 0x81, 0xb9, - 0x85, 0x16, 0x25, 0x95, 0x01, 0x74, 0x8d, 0x3d, 0x63, 0xbf, 0xed, 0x64, 0x03, 0x73, 0x0f, 0x75, - 0x7c, 0x10, 0x84, 0xd3, 0x58, 0x81, 0xbb, 0xf3, 0x7a, 0xad, 0x3e, 0x65, 0x6e, 0xa3, 0x95, 0x2c, - 0x0b, 0xd4, 0xef, 0xb6, 0xf4, 0xf2, 0xb2, 0x1e, 0x9f, 0xf8, 0xe6, 0xc7, 0x68, 0x9d, 0x46, 0x54, - 0x52, 0x1c, 0xb8, 0x63, 0x50, 0xa7, 0xd1, 0x5d, 0xd8, 0x33, 0xf6, 0x3b, 0x87, 0xf7, 0x6c, 0xea, - 0x11, 0x5b, 0x1d, 0xa0, 0x9d, 0x1f, 0x5b, 0x7a, 0x60, 0x3f, 0xd6, 0x88, 0xe3, 0x85, 0xaf, 0xbf, - 0xdd, 0x9d, 0x73, 0xd6, 0x72, 0xbb, 0x6c, 0xd2, 0xbc, 0x8f, 0x56, 0x47, 0x10, 0x81, 0xa0, 0xc2, - 0x1d, 0x63, 0x31, 0xee, 0x2e, 0xee, 0x19, 0xfb, 0xab, 0x4e, 0x27, 0x9f, 0x7b, 0x8c, 0xc5, 0xd8, - 0xdc, 0x45, 0x1d, 0x8f, 0x46, 0x98, 0x4f, 0x32, 0xc4, 0x92, 0x46, 0xa0, 0x6c, 0x4a, 0x03, 0x06, - 0x08, 0x89, 0x18, 0xbf, 0x88, 0x5c, 0x95, 0xed, 0xee, 0x72, 0x1e, 0x48, 0x96, 0x69, 0xbb, 0xc8, - 0xb4, 0x7d, 0x5e, 0x94, 0xc2, 0xf1, 0x8a, 0x0a, 0xe4, 0x8b, 0xef, 0x76, 0x0d, 0xa7, 0xad, 0xed, - 0xd4, 0x8a, 0xf9, 0x19, 0xda, 0x4c, 0x22, 0x8f, 0x45, 0x3e, 0x8d, 0x46, 0x6e, 0x0c, 0x9c, 0x32, - 0xbf, 0xbb, 0xa2, 0xa9, 0xb6, 0xaf, 0x50, 0x3d, 0xcc, 0x8b, 0x26, 0x63, 0xfa, 0x52, 0x31, 0x6d, - 0x94, 0xc6, 0x43, 0x6d, 0x6b, 0x7e, 0x8e, 0x4c, 0x42, 0x52, 0x1d, 0x12, 0x4b, 0x64, 0xc1, 0xd8, - 0x9e, 0x9d, 0x71, 0x93, 0x90, 0xf4, 0x3c, 0xb3, 0xce, 0x29, 0xff, 0x80, 0xee, 0x4a, 0x8e, 0x23, - 0xf1, 0x0c, 0xf8, 0x34, 0x2f, 0x9a, 0x9d, 0xf7, 0x4e, 0xc1, 0xd1, 0x24, 0x7f, 0x8c, 0xf6, 0x48, - 0x5e, 0x40, 0x2e, 0x07, 0x9f, 0x0a, 0xc9, 0xa9, 0x97, 0x28, 0x5b, 0xf7, 0x19, 0xc7, 0x44, 0xd7, - 0x48, 0x47, 0x17, 0x41, 0xaf, 0xc0, 0x39, 0x0d, 0xd8, 0xa3, 0x1c, 0x65, 0x3e, 0x41, 0x3f, 0xf3, - 0x02, 0x46, 0x2e, 0x85, 0x0a, 0xce, 0x6d, 0x30, 0x69, 0xd7, 0x21, 0x15, 0x42, 0xb1, 0xad, 0xee, - 0x19, 0xfb, 0x2d, 0xe7, 0x7e, 0x86, 0x1d, 0x02, 0x7f, 0x58, 0x43, 0x9e, 0xd7, 0x80, 0xe6, 0x7b, - 0xc8, 0x1c, 0x53, 0x21, 0x19, 0xa7, 0x04, 0x07, 0x2e, 0x44, 0x92, 0x53, 0x10, 0xdd, 0x35, 0x6d, - 0x7e, 0xab, 0x5a, 0xf9, 0x28, 0x5b, 0x30, 0x3f, 0x41, 0xf7, 0x6f, 0x74, 0xea, 0x92, 0x31, 0x8e, - 0x22, 0x08, 0xba, 0xeb, 0x7a, 0x2b, 0xbb, 0xfe, 0x0d, 0x3e, 0x07, 0x19, 0xec, 0xc1, 0xca, 0x9f, - 0xbe, 0xda, 0x9d, 0xfb, 0xf2, 0xab, 0xdd, 0x39, 0xeb, 0x1f, 0x06, 0xba, 0x3b, 0x28, 0x37, 0x1e, - 0xb2, 0x14, 0x07, 0x3f, 0xe6, 0x05, 0x3b, 0x42, 0x6d, 0x21, 0x59, 0x9c, 0x95, 0xf4, 0xc2, 0x1b, - 0x94, 0xf4, 0x8a, 0x32, 0x53, 0x0b, 0xd6, 0x5f, 0x0d, 0xb4, 0xf5, 0xd1, 0xf3, 0x84, 0xa6, 0x8c, - 0xe0, 0xff, 0x8b, 0x1e, 0x9c, 0xa2, 0x35, 0xa8, 0xf1, 0x89, 0x6e, 0x6b, 0xaf, 0xb5, 0xdf, 0x39, - 0xfc, 0xb9, 0x9d, 0x89, 0x93, 0x5d, 0x6a, 0x56, 0x2e, 0x50, 0x76, 0xdd, 0xbb, 0xd3, 0xb4, 0xb5, - 0xfe, 0x6e, 0xa0, 0x7b, 0xea, 0x94, 0x47, 0xe0, 0xc0, 0x0b, 0xcc, 0xfd, 0x87, 0x10, 0xb1, 0x50, - 0xfc, 0xe0, 0x18, 0x2d, 0xb4, 0xe6, 0x6b, 0x26, 0x57, 0x32, 0x17, 0xfb, 0xbe, 0x8e, 0x51, 0x63, - 0xd4, 0xe4, 0x39, 0x3b, 0xf2, 0x7d, 0x73, 0x1f, 0x6d, 0x56, 0x18, 0xae, 0x72, 0xa9, 0x8e, 0x58, - 0xc1, 0xd6, 0x0b, 0x98, 0xce, 0x30, 0x58, 0xff, 0x31, 0xd0, 0xe6, 0xc7, 0x01, 0xf3, 0x70, 0x70, - 0x16, 0x60, 0x31, 0x56, 0x15, 0x36, 0x51, 0xa9, 0xe1, 0x90, 0x5f, 0x6d, 0x1d, 0xde, 0xcc, 0xa9, - 0x51, 0x66, 0x5a, 0x6c, 0x3e, 0x44, 0xb7, 0xca, 0xcb, 0x56, 0x56, 0x80, 0xde, 0xcd, 0xf1, 0xed, - 0x57, 0xdf, 0xee, 0x6e, 0x14, 0x85, 0x36, 0xd0, 0xd5, 0xf0, 0xd0, 0xd9, 0x20, 0x8d, 0x09, 0xdf, - 0xec, 0xa1, 0x0e, 0xf5, 0x88, 0x2b, 0xe0, 0xb9, 0x1b, 0x25, 0xa1, 0x2e, 0x9e, 0x05, 0xa7, 0x4d, - 0x3d, 0x72, 0x06, 0xcf, 0x3f, 0x4b, 0x42, 0xf3, 0x7d, 0xf4, 0x56, 0xd1, 0x2d, 0xdd, 0x14, 0x07, - 0xae, 0xb2, 0x57, 0xc7, 0xc1, 0x75, 0x2d, 0xad, 0x3a, 0xb7, 0x8b, 0xd5, 0x0b, 0x1c, 0x28, 0x67, - 0x47, 0xbe, 0xcf, 0xad, 0x7f, 0x2f, 0xa2, 0xa5, 0x21, 0xe6, 0x38, 0x14, 0xe6, 0x39, 0xda, 0x90, - 0x10, 0xc6, 0x01, 0x96, 0xe0, 0x66, 0x42, 0x9e, 0xef, 0xf4, 0x5d, 0x2d, 0xf0, 0xf5, 0x06, 0x68, - 0xd7, 0x5a, 0x5e, 0x7a, 0x60, 0x0f, 0xf4, 0xec, 0x99, 0xc4, 0x12, 0x9c, 0xf5, 0x82, 0x23, 0x9b, - 0x34, 0x7f, 0x85, 0xba, 0x92, 0x27, 0x42, 0x56, 0x12, 0x5b, 0x69, 0x4b, 0x96, 0xcb, 0xb7, 0x8a, - 0xf5, 0x4c, 0x95, 0x4a, 0x4d, 0xb9, 0x5e, 0x4d, 0x5b, 0x3f, 0x44, 0x4d, 0xcf, 0xd0, 0x6d, 0xd5, - 0x8a, 0xa6, 0x39, 0x17, 0x66, 0xe7, 0xbc, 0xa5, 0xec, 0x9b, 0xa4, 0x9f, 0x23, 0x33, 0x15, 0x64, - 0x9a, 0x73, 0xf1, 0x0d, 0xe2, 0x4c, 0x05, 0x69, 0x52, 0xfa, 0x68, 0x47, 0xa8, 0xe2, 0x73, 0x43, - 0x90, 0x5a, 0x9b, 0xe3, 0x00, 0x22, 0x2a, 0xc6, 0x05, 0xf9, 0xd2, 0xec, 0xe4, 0xdb, 0x9a, 0xe8, - 0x53, 0xc5, 0xe3, 0x14, 0x34, 0xb9, 0x97, 0x01, 0xea, 0x5d, 0xef, 0xa5, 0x4c, 0xd0, 0xb2, 0x4e, - 0xd0, 0x4f, 0xae, 0xa1, 0x28, 0xb3, 0x74, 0x88, 0xee, 0x84, 0xf8, 0xa5, 0x2b, 0xc7, 0x9c, 0x49, - 0x19, 0x80, 0xef, 0xc6, 0x98, 0x5c, 0x82, 0x14, 0xba, 0x91, 0xb6, 0x9c, 0xdb, 0x21, 0x7e, 0x79, - 0x5e, 0xac, 0x0d, 0xb3, 0x25, 0x53, 0xa0, 0x77, 0x6a, 0x7d, 0x47, 0x29, 0x81, 0xab, 0x2f, 0xa1, - 0xcb, 0x61, 0xa4, 0xc4, 0x19, 0x67, 0x2d, 0x08, 0xa0, 0xec, 0x9d, 0xb9, 0xda, 0xa8, 0xa7, 0x50, - 0xa9, 0x34, 0x03, 0x46, 0xa3, 0xfc, 0x81, 0x61, 0x55, 0xed, 0xa9, 0xd4, 0x15, 0xa7, 0xc6, 0xf5, - 0x08, 0xc0, 0xf2, 0xd0, 0xad, 0xc7, 0x38, 0xf2, 0xc5, 0x18, 0x5f, 0xc2, 0xa7, 0x20, 0xb1, 0x8f, - 0x25, 0x6e, 0xdc, 0x99, 0x67, 0x00, 0x6e, 0xcc, 0x58, 0x90, 0xdd, 0x99, 0x4c, 0x83, 0xca, 0x3b, - 0xf3, 0x08, 0x60, 0xc8, 0x58, 0xa0, 0xee, 0x8c, 0xd9, 0x45, 0xcb, 0x29, 0x70, 0x51, 0x55, 0x70, - 0x31, 0xb4, 0x7e, 0x81, 0xda, 0x5a, 0x34, 0x8e, 0xc8, 0xa5, 0x30, 0x77, 0x50, 0x5b, 0x31, 0x81, - 0x10, 0x20, 0xba, 0x86, 0xd6, 0x9a, 0x6a, 0xc2, 0x92, 0x68, 0xfb, 0xa6, 0xc7, 0x9b, 0x30, 0x9f, - 0xa2, 0xe5, 0x18, 0xf4, 0xcb, 0x42, 0x1b, 0x76, 0x0e, 0x3f, 0xb0, 0x67, 0x78, 0x20, 0xdb, 0x37, - 0x11, 0x3a, 0x05, 0x9b, 0xc5, 0xab, 0x27, 0xe3, 0x54, 0x43, 0x13, 0xe6, 0xc5, 0xb4, 0xd3, 0x5f, - 0xbf, 0x91, 0xd3, 0x29, 0xbe, 0xca, 0xe7, 0xbb, 0xa8, 0x73, 0x94, 0x6d, 0xfb, 0x37, 0x54, 0xc8, - 0xab, 0xc7, 0xb2, 0x5a, 0x3f, 0x96, 0x4f, 0xd0, 0x7a, 0xde, 0x87, 0xcf, 0x99, 0x16, 0x3e, 0xf3, - 0xa7, 0x08, 0xe5, 0x0d, 0x5c, 0x09, 0x66, 0x96, 0x96, 0x76, 0x3e, 0x73, 0xe2, 0x37, 0xfa, 0xe9, - 0x7c, 0xa3, 0x9f, 0x5a, 0x0e, 0xda, 0xb8, 0x10, 0xe4, 0xb7, 0xc5, 0x23, 0xed, 0x49, 0x2c, 0xcc, - 0x3b, 0x68, 0x49, 0xdd, 0xd5, 0x9c, 0x68, 0xc1, 0x59, 0x4c, 0x05, 0x39, 0xd1, 0xdd, 0xa1, 0x7a, - 0x08, 0xb2, 0xd8, 0xa5, 0xbe, 0xe8, 0xce, 0xef, 0xb5, 0xf6, 0x17, 0x9c, 0xf5, 0xa4, 0x32, 0x3f, - 0xf1, 0x85, 0xf5, 0x3b, 0xd4, 0xa9, 0x11, 0x9a, 0xeb, 0x68, 0xbe, 0xe4, 0x9a, 0xa7, 0xbe, 0xf9, - 0x00, 0x6d, 0x57, 0x44, 0x4d, 0xb9, 0xcf, 0x18, 0xdb, 0xce, 0xdd, 0x12, 0xd0, 0x50, 0x7c, 0x61, - 0x3d, 0x41, 0x5b, 0x27, 0x95, 0xb8, 0x94, 0xcd, 0xa4, 0xb1, 0x43, 0xa3, 0xf9, 0x62, 0xd8, 0x41, - 0xed, 0xf2, 0xd7, 0x8e, 0xde, 0xfd, 0x82, 0x53, 0x4d, 0x58, 0x21, 0xda, 0xbc, 0x10, 0xe4, 0x0c, - 0x22, 0xbf, 0x22, 0xbb, 0xe1, 0x00, 0x8e, 0xa7, 0x89, 0x66, 0x7e, 0x4d, 0x57, 0xee, 0xfe, 0x6c, - 0xa0, 0xee, 0x29, 0x4c, 0x8e, 0x84, 0xa0, 0xa3, 0x28, 0x84, 0x48, 0x2a, 0xb1, 0xc0, 0x04, 0xd4, - 0xa7, 0xf9, 0x36, 0x5a, 0x2b, 0x2f, 0x5a, 0x79, 0xbf, 0x56, 0x9d, 0xd5, 0x62, 0x52, 0x5f, 0xac, - 0x07, 0x08, 0xc5, 0x1c, 0x52, 0x97, 0xb8, 0x97, 0x30, 0xc9, 0xc3, 0xd8, 0xa9, 0xf7, 0x9a, 0xec, - 0xc7, 0x94, 0x3d, 0x4c, 0xbc, 0x80, 0x92, 0x53, 0x98, 0x38, 0x2b, 0x0a, 0x3f, 0x38, 0x85, 0x89, - 0x7a, 0x3c, 0xc4, 0xec, 0x05, 0x70, 0xdd, 0x20, 0x5a, 0x4e, 0x36, 0xb0, 0xfe, 0x62, 0xa0, 0xbb, - 0x17, 0x38, 0xa0, 0x3e, 0x96, 0x8c, 0x17, 0xe7, 0x3d, 0x4c, 0x3c, 0x65, 0xf1, 0x3d, 0xe7, 0x7a, - 0x25, 0xda, 0xf9, 0x6b, 0xa2, 0xfd, 0x10, 0xad, 0x96, 0x19, 0x56, 0xf1, 0xb6, 0x66, 0x88, 0xb7, - 0x53, 0x58, 0x9c, 0xc2, 0xc4, 0xfa, 0x63, 0x2d, 0xb6, 0xe3, 0x49, 0xed, 0xf2, 0xf2, 0xff, 0x11, - 0x5b, 0xe9, 0xb6, 0x1e, 0x1b, 0xa9, 0xdb, 0x5f, 0xd9, 0x40, 0xeb, 0xea, 0x06, 0xac, 0xbf, 0x19, - 0x68, 0xab, 0xee, 0x55, 0x9c, 0xb3, 0x21, 0x4f, 0x22, 0xf8, 0x3e, 0xef, 0x55, 0xfd, 0xcc, 0xd7, - 0xeb, 0xe7, 0x29, 0x5a, 0x6f, 0x04, 0x25, 0xf2, 0xd3, 0xf8, 0xe5, 0x4c, 0x12, 0x52, 0x93, 0x07, - 0x67, 0xad, 0xbe, 0x0f, 0x71, 0xfc, 0xf4, 0xeb, 0x57, 0x3d, 0xe3, 0x9b, 0x57, 0x3d, 0xe3, 0x5f, - 0xaf, 0x7a, 0xc6, 0x17, 0xaf, 0x7b, 0x73, 0xdf, 0xbc, 0xee, 0xcd, 0xfd, 0xf3, 0x75, 0x6f, 0xee, - 0xf7, 0x1f, 0x8c, 0xa8, 0x1c, 0x27, 0x9e, 0x4d, 0x58, 0xd8, 0xcf, 0x7f, 0x29, 0x57, 0xbe, 0xde, - 0x2b, 0xff, 0x78, 0x48, 0x0f, 0xfb, 0x2f, 0x9b, 0xff, 0x3e, 0xc8, 0x49, 0x0c, 0xc2, 0x5b, 0xd2, - 0x65, 0xfd, 0xfe, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x09, 0x3c, 0x54, 0xc1, 0xae, 0x10, 0x00, - 0x00, + // 1595 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0x4f, 0x73, 0xdb, 0xc6, + 0x15, 0x17, 0x44, 0xfd, 0xe3, 0x92, 0xa2, 0x64, 0xd8, 0x8e, 0x29, 0x57, 0x25, 0x65, 0xa4, 0xd3, + 0x51, 0x27, 0x13, 0xa0, 0x52, 0x2e, 0x1d, 0x4f, 0x33, 0x19, 0x89, 0xae, 0x63, 0xc5, 0x4d, 0xcc, + 0x40, 0xac, 0x3c, 0x6d, 0x0f, 0x98, 0xc5, 0xe2, 0x99, 0xdc, 0x11, 0x80, 0x45, 0x76, 0x97, 0xb0, + 0x79, 0xe9, 0xb9, 0xc7, 0xf4, 0x96, 0xe9, 0x29, 0xed, 0x17, 0xe8, 0xd7, 0xc8, 0x31, 0xc7, 0x9e, + 0x92, 0x8e, 0x7d, 0xe8, 0xa1, 0x5f, 0xa2, 0xb3, 0x8b, 0xbf, 0xa4, 0xa4, 0x94, 0x9e, 0x4c, 0x6f, + 0xd8, 0xdd, 0xdf, 0xfb, 0xbd, 0x7f, 0xfb, 0xde, 0x5b, 0xa0, 0x63, 0x1a, 0x4b, 0xe0, 0x64, 0x82, + 0x69, 0xec, 0x09, 0x20, 0x53, 0x4e, 0xe5, 0xcc, 0x21, 0x24, 0x75, 0x12, 0xce, 0x52, 0x1a, 0x00, + 0x77, 0xd2, 0xa3, 0xf2, 0xdb, 0x4e, 0x38, 0x93, 0xcc, 0x7c, 0xf7, 0x1a, 0x19, 0x9b, 0x90, 0xd4, + 0x2e, 0x71, 0xe9, 0xd1, 0xfd, 0x3b, 0x63, 0x36, 0x66, 0x1a, 0xef, 0xa8, 0xaf, 0x4c, 0xf4, 0x7e, + 0x7f, 0xcc, 0xd8, 0x38, 0x04, 0x47, 0xaf, 0xfc, 0xe9, 0x0b, 0x47, 0xd2, 0x08, 0x84, 0xc4, 0x51, + 0x92, 0x03, 0x7a, 0x8b, 0x80, 0x60, 0xca, 0xb1, 0xa4, 0x2c, 0x2e, 0x08, 0xa8, 0x4f, 0x1c, 0xc2, + 0x38, 0x38, 0x24, 0xa4, 0x10, 0x4b, 0x65, 0x5e, 0xf6, 0x95, 0x03, 0x1c, 0x05, 0x08, 0xe9, 0x78, + 0x22, 0xb3, 0x6d, 0xe1, 0x48, 0x88, 0x03, 0xe0, 0x11, 0xcd, 0xc0, 0xd5, 0x2a, 0x17, 0xd8, 0xaf, + 0x9d, 0x13, 0x3e, 0x4b, 0x24, 0x73, 0x2e, 0x61, 0x26, 0x0a, 0x7b, 0x08, 0x13, 0x11, 0x13, 0x8e, + 0x8f, 0x05, 0x38, 0xe9, 0x91, 0x0f, 0x12, 0x1f, 0x39, 0x84, 0xd1, 0xdc, 0x1e, 0xeb, 0xfb, 0x0d, + 0xd4, 0x1d, 0xb0, 0x58, 0x4c, 0x23, 0xe0, 0x27, 0x41, 0x40, 0x95, 0xa9, 0x43, 0xce, 0x12, 0x26, + 0x70, 0x68, 0xde, 0x41, 0xeb, 0x92, 0xca, 0x10, 0xba, 0xc6, 0x81, 0x71, 0xd8, 0x74, 0xb3, 0x85, + 0x79, 0x80, 0x5a, 0x01, 0x08, 0xc2, 0x69, 0xa2, 0xc0, 0xdd, 0x55, 0x7d, 0x56, 0xdf, 0x32, 0xf7, + 0xd0, 0x56, 0x16, 0x5d, 0x1a, 0x74, 0x1b, 0xfa, 0x78, 0x53, 0xaf, 0xcf, 0x02, 0xf3, 0x63, 0xd4, + 0xa1, 0x31, 0x95, 0x14, 0x87, 0xde, 0x04, 0x94, 0x97, 0xdd, 0xb5, 0x03, 0xe3, 0xb0, 0x75, 0x7c, + 0xdf, 0xa6, 0x3e, 0xb1, 0x55, 0x60, 0xec, 0x3c, 0x1c, 0xe9, 0x91, 0xfd, 0x44, 0x23, 0x4e, 0xd7, + 0xbe, 0xf9, 0xae, 0xbf, 0xe2, 0x6e, 0xe7, 0x72, 0xd9, 0xa6, 0xf9, 0x00, 0xb5, 0xc7, 0x10, 0x83, + 0xa0, 0xc2, 0x9b, 0x60, 0x31, 0xe9, 0xae, 0x1f, 0x18, 0x87, 0x6d, 0xb7, 0x95, 0xef, 0x3d, 0xc1, + 0x62, 0x62, 0xf6, 0x51, 0xcb, 0xa7, 0x31, 0xe6, 0xb3, 0x0c, 0xb1, 0xa1, 0x11, 0x28, 0xdb, 0xd2, + 0x80, 0x01, 0x42, 0x22, 0xc1, 0x2f, 0x63, 0x4f, 0x65, 0xb1, 0xbb, 0x99, 0x1b, 0x92, 0x65, 0xd0, + 0x2e, 0x32, 0x68, 0x8f, 0x8a, 0x14, 0x9f, 0x6e, 0x29, 0x43, 0xbe, 0xfc, 0xbe, 0x6f, 0xb8, 0x4d, + 0x2d, 0xa7, 0x4e, 0xcc, 0xcf, 0xd0, 0xee, 0x34, 0xf6, 0x59, 0x1c, 0xd0, 0x78, 0xec, 0x25, 0xc0, + 0x29, 0x0b, 0xba, 0x5b, 0x9a, 0x6a, 0xef, 0x0a, 0xd5, 0xa3, 0xfc, 0x32, 0x64, 0x4c, 0x5f, 0x29, + 0xa6, 0x9d, 0x52, 0x78, 0xa8, 0x65, 0xcd, 0xcf, 0x91, 0x49, 0x48, 0xaa, 0x4d, 0x62, 0x53, 0x59, + 0x30, 0x36, 0x97, 0x67, 0xdc, 0x25, 0x24, 0x1d, 0x65, 0xd2, 0x39, 0xe5, 0x1f, 0xd1, 0x3d, 0xc9, + 0x71, 0x2c, 0x5e, 0x00, 0x5f, 0xe4, 0x45, 0xcb, 0xf3, 0xde, 0x2d, 0x38, 0xe6, 0xc9, 0x9f, 0xa0, + 0x03, 0x92, 0x5f, 0x20, 0x8f, 0x43, 0x40, 0x85, 0xe4, 0xd4, 0x9f, 0x2a, 0x59, 0xef, 0x05, 0xc7, + 0x44, 0xdf, 0x91, 0x96, 0xbe, 0x04, 0xbd, 0x02, 0xe7, 0xce, 0xc1, 0x1e, 0xe7, 0x28, 0xf3, 0x19, + 0xfa, 0x99, 0x1f, 0x32, 0x72, 0x29, 0x94, 0x71, 0xde, 0x1c, 0x93, 0x56, 0x1d, 0x51, 0x21, 0x14, + 0x5b, 0xfb, 0xc0, 0x38, 0x6c, 0xb8, 0x0f, 0x32, 0xec, 0x10, 0xf8, 0xa3, 0x1a, 0x72, 0x54, 0x03, + 0x9a, 0xef, 0x23, 0x73, 0x42, 0x85, 0x64, 0x9c, 0x12, 0x1c, 0x7a, 0x10, 0x4b, 0x4e, 0x41, 0x74, + 0xb7, 0xb5, 0xf8, 0xad, 0xea, 0xe4, 0x37, 0xd9, 0x81, 0xf9, 0x09, 0x7a, 0x70, 0xa3, 0x52, 0x8f, + 0x4c, 0x70, 0x1c, 0x43, 0xd8, 0xed, 0x68, 0x57, 0xfa, 0xc1, 0x0d, 0x3a, 0x07, 0x19, 0xec, 0xe1, + 0xd6, 0x9f, 0xbf, 0xee, 0xaf, 0x7c, 0xf5, 0x75, 0x7f, 0xc5, 0xfa, 0x87, 0x81, 0xee, 0x0d, 0x4a, + 0xc7, 0x23, 0x96, 0xe2, 0xf0, 0xff, 0x59, 0x60, 0x27, 0xa8, 0x29, 0x24, 0x4b, 0xb2, 0x2b, 0xbd, + 0xf6, 0x16, 0x57, 0x7a, 0x4b, 0x89, 0xa9, 0x03, 0xeb, 0xef, 0x06, 0xba, 0xaf, 0xfc, 0x18, 0x83, + 0x0b, 0x2f, 0x31, 0x0f, 0x1e, 0x41, 0xcc, 0x22, 0xf1, 0xa3, 0x8d, 0xb6, 0xd0, 0x76, 0xa0, 0x99, + 0x3c, 0xc9, 0x3c, 0x1c, 0x28, 0xcb, 0x1b, 0x19, 0x46, 0x6d, 0x8e, 0xd8, 0x49, 0x10, 0x98, 0x87, + 0x68, 0xb7, 0xc2, 0x70, 0x15, 0x2d, 0xe5, 0x84, 0x82, 0x75, 0x0a, 0x98, 0x8e, 0x21, 0x58, 0xff, + 0x31, 0xd0, 0xee, 0xc7, 0x21, 0xf3, 0x71, 0x78, 0x1e, 0x62, 0x31, 0x51, 0x39, 0x9c, 0x29, 0xe7, + 0x39, 0xe4, 0xc5, 0xa3, 0xcd, 0x5b, 0xda, 0x79, 0x25, 0xa6, 0xcb, 0xf9, 0x23, 0x74, 0xab, 0xbc, + 0xce, 0x65, 0x8c, 0xb5, 0x37, 0xa7, 0xb7, 0x5f, 0x7f, 0xd7, 0xdf, 0x29, 0x52, 0x39, 0xd0, 0xf1, + 0x7e, 0xe4, 0xee, 0x90, 0xb9, 0x8d, 0xc0, 0xec, 0xa1, 0x16, 0xf5, 0x89, 0x27, 0xe0, 0x0b, 0x2f, + 0x9e, 0x46, 0x3a, 0x3d, 0x6b, 0x6e, 0x93, 0xfa, 0xe4, 0x1c, 0xbe, 0xf8, 0x6c, 0x1a, 0x99, 0x1f, + 0xa0, 0x77, 0x8a, 0x39, 0xe3, 0xa5, 0x38, 0xf4, 0x94, 0xbc, 0x0a, 0x07, 0xd7, 0xd9, 0x6a, 0xbb, + 0xb7, 0x8b, 0xd3, 0x0b, 0x1c, 0x2a, 0x65, 0x27, 0x41, 0xc0, 0xad, 0x7f, 0xaf, 0xa3, 0x8d, 0x21, + 0xe6, 0x38, 0x12, 0xe6, 0x08, 0xed, 0x48, 0x88, 0x92, 0x10, 0x4b, 0xf0, 0xb2, 0x56, 0x99, 0x7b, + 0xfa, 0x9e, 0x6e, 0xa1, 0xf5, 0xd1, 0x61, 0xd7, 0x86, 0x45, 0x7a, 0x64, 0x0f, 0xf4, 0xee, 0xb9, + 0xc4, 0x12, 0xdc, 0x4e, 0xc1, 0x91, 0x6d, 0x9a, 0xbf, 0x42, 0x5d, 0xc9, 0xa7, 0x42, 0x56, 0x4d, + 0xac, 0xaa, 0xde, 0x2c, 0x97, 0xef, 0x14, 0xe7, 0x59, 0xdd, 0x97, 0x55, 0x7b, 0x7d, 0xbf, 0x6a, + 0xfc, 0x98, 0x7e, 0x75, 0x8e, 0x6e, 0xab, 0x66, 0xbf, 0xc8, 0xb9, 0xb6, 0x3c, 0xe7, 0x2d, 0x25, + 0x3f, 0x4f, 0xfa, 0x39, 0x32, 0x53, 0x41, 0x16, 0x39, 0xd7, 0xdf, 0xc2, 0xce, 0x54, 0x90, 0x79, + 0xca, 0x00, 0xed, 0x0b, 0x75, 0xf9, 0xbc, 0x08, 0xa4, 0xee, 0x7e, 0x49, 0x08, 0x31, 0x15, 0x93, + 0x82, 0x7c, 0x63, 0x79, 0xf2, 0x3d, 0x4d, 0xf4, 0xa9, 0xe2, 0x71, 0x0b, 0x9a, 0x5c, 0xcb, 0x00, + 0xf5, 0xae, 0xd7, 0x52, 0x26, 0x68, 0x53, 0x27, 0xe8, 0x27, 0xd7, 0x50, 0x94, 0x59, 0x3a, 0x46, + 0x77, 0x23, 0xfc, 0xca, 0x93, 0x13, 0xce, 0xa4, 0x0c, 0x21, 0xf0, 0x12, 0x4c, 0x2e, 0x41, 0x0a, + 0x3d, 0xaa, 0x1a, 0xee, 0xed, 0x08, 0xbf, 0x1a, 0x15, 0x67, 0xc3, 0xec, 0xc8, 0x14, 0xe8, 0xe7, + 0xb5, 0xce, 0xae, 0x3a, 0x81, 0xa7, 0x8b, 0xd0, 0xe3, 0x30, 0x56, 0xed, 0x0f, 0x67, 0x4d, 0x1e, + 0xa0, 0x9c, 0x4e, 0xd9, 0x63, 0xc3, 0x56, 0x8f, 0x0d, 0x3b, 0x7f, 0x6c, 0xd8, 0x03, 0x46, 0xe3, + 0x7c, 0x84, 0x5b, 0xd5, 0x00, 0x28, 0xfb, 0x8a, 0x5b, 0xe3, 0x7a, 0x0c, 0x60, 0xf9, 0xe8, 0xd6, + 0x13, 0x1c, 0x07, 0x62, 0x82, 0x2f, 0xe1, 0x53, 0x90, 0x38, 0xc0, 0x12, 0xcf, 0xd5, 0xcc, 0x0b, + 0x00, 0x2f, 0x61, 0x2c, 0xcc, 0x6a, 0x26, 0xeb, 0x41, 0x65, 0xcd, 0x3c, 0x06, 0x18, 0x32, 0x16, + 0xaa, 0x9a, 0x31, 0xbb, 0x68, 0x33, 0x05, 0x2e, 0xaa, 0x1b, 0x5c, 0x2c, 0xad, 0x5f, 0xa0, 0xa6, + 0x6e, 0x1a, 0x27, 0xe4, 0x52, 0x98, 0xfb, 0xa8, 0xa9, 0x98, 0x40, 0x08, 0x10, 0x5d, 0x43, 0xf7, + 0x9a, 0x6a, 0xc3, 0x92, 0x68, 0xef, 0xa6, 0xe7, 0x91, 0x30, 0x9f, 0xa3, 0xcd, 0x04, 0xf4, 0xec, + 0xd6, 0x82, 0xad, 0xe3, 0x0f, 0xed, 0x25, 0x9e, 0x96, 0xf6, 0x4d, 0x84, 0x6e, 0xc1, 0x66, 0xf1, + 0xea, 0x51, 0xb6, 0x30, 0x32, 0x84, 0x79, 0xb1, 0xa8, 0xf4, 0xd7, 0x6f, 0xa5, 0x74, 0x81, 0xaf, + 0xd2, 0xf9, 0x1e, 0x6a, 0x9d, 0x64, 0x6e, 0xff, 0x96, 0x0a, 0x79, 0x35, 0x2c, 0xed, 0x7a, 0x58, + 0x3e, 0x41, 0x9d, 0x7c, 0xd2, 0x8d, 0x98, 0x6e, 0x7c, 0xe6, 0x4f, 0x11, 0xca, 0x47, 0xa4, 0x6a, + 0x98, 0x59, 0x5a, 0x9a, 0xf9, 0xce, 0x59, 0x30, 0x37, 0xb1, 0x56, 0xe7, 0x26, 0x96, 0xe5, 0xa2, + 0x9d, 0x0b, 0x41, 0x7e, 0x57, 0x3c, 0x83, 0x9e, 0x25, 0xc2, 0xbc, 0x8b, 0x36, 0x54, 0xad, 0xe6, + 0x44, 0x6b, 0xee, 0x7a, 0x2a, 0xc8, 0x99, 0x9e, 0x0e, 0xd5, 0x53, 0x8b, 0x25, 0x1e, 0x0d, 0x44, + 0x77, 0xf5, 0xa0, 0x71, 0xb8, 0xe6, 0x76, 0xa6, 0x95, 0xf8, 0x59, 0x20, 0xac, 0xdf, 0xa3, 0x56, + 0x8d, 0xd0, 0xec, 0xa0, 0xd5, 0x92, 0x6b, 0x95, 0x06, 0xe6, 0x43, 0xb4, 0x57, 0x11, 0xcd, 0xb7, + 0xfb, 0x8c, 0xb1, 0xe9, 0xde, 0x2b, 0x01, 0x73, 0x1d, 0x5f, 0x58, 0xcf, 0xd0, 0x9d, 0xb3, 0xaa, + 0xb9, 0x94, 0xc3, 0x64, 0xce, 0x43, 0x63, 0x7e, 0x26, 0xef, 0xa3, 0x66, 0xf9, 0x9f, 0xa0, 0xbd, + 0x5f, 0x73, 0xab, 0x0d, 0x2b, 0x42, 0xbb, 0x17, 0x82, 0x9c, 0x43, 0x1c, 0x54, 0x64, 0x37, 0x04, + 0xe0, 0x74, 0x91, 0x68, 0xe9, 0xf7, 0x6a, 0xa5, 0xee, 0x2f, 0x06, 0xea, 0x3e, 0x85, 0xd9, 0x89, + 0x10, 0x74, 0x1c, 0x47, 0x10, 0x4b, 0xd5, 0x2c, 0x30, 0x01, 0xf5, 0x69, 0xbe, 0x8b, 0xb6, 0xcb, + 0x42, 0x2b, 0xeb, 0xab, 0xed, 0xb6, 0x8b, 0x4d, 0x5d, 0x58, 0x0f, 0x11, 0x4a, 0x38, 0xa4, 0x1e, + 0xf1, 0x2e, 0x61, 0x96, 0x9b, 0xb1, 0x5f, 0x9f, 0x35, 0xd9, 0x6f, 0x88, 0x3d, 0x9c, 0xfa, 0x21, + 0x25, 0x4f, 0x61, 0xe6, 0x6e, 0x29, 0xfc, 0xe0, 0x29, 0xcc, 0xd4, 0xe3, 0x21, 0x61, 0x2f, 0x81, + 0xeb, 0x01, 0xd1, 0x70, 0xb3, 0x85, 0xf5, 0x57, 0x03, 0xdd, 0xbb, 0xc0, 0x21, 0x0d, 0xb0, 0x64, + 0xbc, 0x88, 0xf7, 0x70, 0xea, 0x2b, 0x89, 0x1f, 0x88, 0xeb, 0x15, 0x6b, 0x57, 0xaf, 0xb1, 0xf6, + 0x23, 0xd4, 0x2e, 0x33, 0xac, 0xec, 0x6d, 0x2c, 0x61, 0x6f, 0xab, 0x90, 0x78, 0x0a, 0x33, 0xeb, + 0x4f, 0x35, 0xdb, 0x4e, 0x67, 0xb5, 0xe2, 0xe5, 0xff, 0xc3, 0xb6, 0x52, 0x6d, 0xdd, 0x36, 0x52, + 0x97, 0xbf, 0xe2, 0x40, 0xe3, 0xaa, 0x03, 0xd6, 0xdf, 0x0c, 0x74, 0xa7, 0xae, 0x55, 0x8c, 0xd8, + 0x90, 0x4f, 0x63, 0xf8, 0x21, 0xed, 0xd5, 0xfd, 0x59, 0xad, 0xdf, 0x9f, 0xe7, 0xa8, 0x33, 0x67, + 0x94, 0xc8, 0xa3, 0xf1, 0xcb, 0xa5, 0x5a, 0x48, 0xad, 0x3d, 0xb8, 0xdb, 0x75, 0x3f, 0xc4, 0xe9, + 0xf3, 0x6f, 0x5e, 0xf7, 0x8c, 0x6f, 0x5f, 0xf7, 0x8c, 0x7f, 0xbd, 0xee, 0x19, 0x5f, 0xbe, 0xe9, + 0xad, 0x7c, 0xfb, 0xa6, 0xb7, 0xf2, 0xcf, 0x37, 0xbd, 0x95, 0x3f, 0x7c, 0x38, 0xa6, 0x72, 0x32, + 0xf5, 0x6d, 0xc2, 0x22, 0x27, 0xff, 0x17, 0xad, 0x74, 0xbd, 0x5f, 0xfe, 0xb2, 0xa7, 0xc7, 0xce, + 0xab, 0xf9, 0xff, 0x76, 0x39, 0x4b, 0x40, 0xf8, 0x1b, 0xfa, 0x5a, 0x7f, 0xf0, 0xdf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x3c, 0x4e, 0x8d, 0x14, 0xe8, 0x0f, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -1585,57 +1516,6 @@ func (m *ConsumerRemovalProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } -func (m *EquivocationProposal) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *EquivocationProposal) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *EquivocationProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Equivocations) > 0 { - for iNdEx := len(m.Equivocations) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Equivocations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintProvider(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - } - if len(m.Description) > 0 { - i -= len(m.Description) - copy(dAtA[i:], m.Description) - i = encodeVarintProvider(dAtA, i, uint64(len(m.Description))) - i-- - dAtA[i] = 0x12 - } - if len(m.Title) > 0 { - i -= len(m.Title) - copy(dAtA[i:], m.Title) - i = encodeVarintProvider(dAtA, i, uint64(len(m.Title))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *ChangeRewardDenomsProposal) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -2477,29 +2357,6 @@ func (m *ConsumerRemovalProposal) Size() (n int) { return n } -func (m *EquivocationProposal) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Title) - if l > 0 { - n += 1 + l + sovProvider(uint64(l)) - } - l = len(m.Description) - if l > 0 { - n += 1 + l + sovProvider(uint64(l)) - } - if len(m.Equivocations) > 0 { - for _, e := range m.Equivocations { - l = e.Size() - n += 1 + l + sovProvider(uint64(l)) - } - } - return n -} - func (m *ChangeRewardDenomsProposal) Size() (n int) { if m == nil { return 0 @@ -3494,154 +3351,6 @@ func (m *ConsumerRemovalProposal) Unmarshal(dAtA []byte) error { } return nil } -func (m *EquivocationProposal) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProvider - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: EquivocationProposal: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: EquivocationProposal: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProvider - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthProvider - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthProvider - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Title = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProvider - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthProvider - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthProvider - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Description = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Equivocations", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProvider - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthProvider - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthProvider - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Equivocations = append(m.Equivocations, &types1.Equivocation{}) - if err := m.Equivocations[len(m.Equivocations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipProvider(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthProvider - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *ChangeRewardDenomsProposal) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -4047,7 +3756,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.TemplateClient == nil { - m.TemplateClient = &types2.ClientState{} + m.TemplateClient = &types1.ClientState{} } if err := m.TemplateClient.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 1aa2b6dc8f..68a5c2c003 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -7,7 +7,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" @@ -51,10 +50,6 @@ type StakingKeeper interface { GetUnbondingType(ctx sdk.Context, id uint64) (unbondingType stakingtypes.UnbondingType, found bool) } -type EvidenceKeeper interface { - HandleEquivocationEvidence(ctx sdk.Context, evidence *evidencetypes.Equivocation) -} - // SlashingKeeper defines the contract expected to perform ccv slashing type SlashingKeeper interface { JailUntil(sdk.Context, sdk.ConsAddress, time.Time) // called from provider keeper only From f7a411a04e93c89f20a448eced5d3998e2a28445 Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Wed, 13 Sep 2023 16:41:50 +0200 Subject: [PATCH 16/40] bring back evidencekeeper --- app/provider/app.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/provider/app.go b/app/provider/app.go index 6e65d20a96..1b4d9f75e1 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -2,6 +2,7 @@ package app import ( "fmt" + evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper" "io" stdlog "log" "net/http" @@ -205,6 +206,7 @@ type App struct { // nolint: golint UpgradeKeeper upgradekeeper.Keeper ParamsKeeper paramskeeper.Keeper IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + EvidenceKeeper evidencekeeper.Keeper TransferKeeper ibctransferkeeper.Keeper ProviderKeeper ibcproviderkeeper.Keeper @@ -469,6 +471,7 @@ func New( distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper), upgrade.NewAppModule(app.UpgradeKeeper), + evidence.NewAppModule(app.EvidenceKeeper), ibc.NewAppModule(app.IBCKeeper), params.NewAppModule(app.ParamsKeeper), transferModule, @@ -572,6 +575,7 @@ func New( distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), params.NewAppModule(app.ParamsKeeper), + evidence.NewAppModule(app.EvidenceKeeper), ibc.NewAppModule(app.IBCKeeper), transferModule, ) From 4f8b0a7579eb69cbebe404041d0611faa1389328 Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Wed, 13 Sep 2023 16:53:20 +0200 Subject: [PATCH 17/40] go.sum added --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 0378b823b2..3d7480161f 100644 --- a/go.sum +++ b/go.sum @@ -1128,7 +1128,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1381,7 +1380,6 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From e8a6408ab842ba077ad7af0fbaa2d2b2baf54805 Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Thu, 28 Sep 2023 10:15:33 +0200 Subject: [PATCH 18/40] rebase and fix lint issues --- Dockerfile | 2 +- app/provider/app.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7f58c49632..e8ad65a81c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,7 @@ FROM informalofftermatt/cometmock:latest as cometmock-builder # Get GoRelayer FROM informalofftermatt/gorelayer:nogas AS gorelayer-builder -FROM --platform=linux/amd64 fedora:36 +FROM --platform=linux/arm64 fedora:36 RUN dnf update -y RUN dnf install -y which iproute iputils procps-ng vim-minimal tmux net-tools htop jq USER root diff --git a/app/provider/app.go b/app/provider/app.go index 1b4d9f75e1..d4dbf94211 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -2,13 +2,14 @@ package app import ( "fmt" - evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper" "io" stdlog "log" "net/http" "os" "path/filepath" + evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper" + appparams "github.com/cosmos/interchain-security/v2/app/params" "github.com/cosmos/cosmos-sdk/baseapp" From ec76a5244dea3d6c962f7969f0eedecbe0a349fc Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Thu, 28 Sep 2023 10:18:26 +0200 Subject: [PATCH 19/40] fix mocks --- go.sum | 2 ++ testutil/keeper/mocks.go | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/go.sum b/go.sum index 3d7480161f..0378b823b2 100644 --- a/go.sum +++ b/go.sum @@ -1128,6 +1128,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1380,6 +1381,7 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 26606eed40..bc3b872a2b 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -116,10 +116,10 @@ func (mr *MockStakingKeeperMockRecorder) GetLastValidators(ctx interface{}) *gom } // GetRedelegationsFromSrcValidator mocks base method. -func (m *MockStakingKeeper) GetRedelegationsFromSrcValidator(ctx types.Context, valAddr types.ValAddress) []types4.Redelegation { +func (m *MockStakingKeeper) GetRedelegationsFromSrcValidator(ctx types.Context, valAddr types.ValAddress) []types3.Redelegation { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRedelegationsFromSrcValidator", ctx, valAddr) - ret0, _ := ret[0].([]types4.Redelegation) + ret0, _ := ret[0].([]types3.Redelegation) return ret0 } @@ -130,10 +130,10 @@ func (mr *MockStakingKeeperMockRecorder) GetRedelegationsFromSrcValidator(ctx, v } // GetUnbondingDelegationsFromValidator mocks base method. -func (m *MockStakingKeeper) GetUnbondingDelegationsFromValidator(ctx types.Context, valAddr types.ValAddress) []types4.UnbondingDelegation { +func (m *MockStakingKeeper) GetUnbondingDelegationsFromValidator(ctx types.Context, valAddr types.ValAddress) []types3.UnbondingDelegation { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetUnbondingDelegationsFromValidator", ctx, valAddr) - ret0, _ := ret[0].([]types4.UnbondingDelegation) + ret0, _ := ret[0].([]types3.UnbondingDelegation) return ret0 } @@ -307,7 +307,7 @@ func (mr *MockStakingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4, arg } // SlashRedelegation mocks base method. -func (m *MockStakingKeeper) SlashRedelegation(arg0 types.Context, arg1 types4.Validator, arg2 types4.Redelegation, arg3 int64, arg4 types.Dec) types.Int { +func (m *MockStakingKeeper) SlashRedelegation(arg0 types.Context, arg1 types3.Validator, arg2 types3.Redelegation, arg3 int64, arg4 types.Dec) types.Int { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SlashRedelegation", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(types.Int) @@ -321,7 +321,7 @@ func (mr *MockStakingKeeperMockRecorder) SlashRedelegation(arg0, arg1, arg2, arg } // SlashUnbondingDelegation mocks base method. -func (m *MockStakingKeeper) SlashUnbondingDelegation(arg0 types.Context, arg1 types4.UnbondingDelegation, arg2 int64, arg3 types.Dec) types.Int { +func (m *MockStakingKeeper) SlashUnbondingDelegation(arg0 types.Context, arg1 types3.UnbondingDelegation, arg2 int64, arg3 types.Dec) types.Int { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SlashUnbondingDelegation", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(types.Int) From c6ed9ab6b9044ed8cbc9e0f58fe29ecd22d932d5 Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Thu, 28 Sep 2023 10:27:09 +0200 Subject: [PATCH 20/40] clean up protos & delete unecessary file --- go.sum | 2 -- .../cosmos/evidence/v1beta1/evidence.proto | 21 ------------------- 2 files changed, 23 deletions(-) delete mode 100644 third_party/proto/cosmos/evidence/v1beta1/evidence.proto diff --git a/go.sum b/go.sum index 0378b823b2..3d7480161f 100644 --- a/go.sum +++ b/go.sum @@ -1128,7 +1128,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1381,7 +1380,6 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/third_party/proto/cosmos/evidence/v1beta1/evidence.proto b/third_party/proto/cosmos/evidence/v1beta1/evidence.proto deleted file mode 100644 index 14612c314f..0000000000 --- a/third_party/proto/cosmos/evidence/v1beta1/evidence.proto +++ /dev/null @@ -1,21 +0,0 @@ -syntax = "proto3"; -package cosmos.evidence.v1beta1; - -option go_package = "github.com/cosmos/cosmos-sdk/x/evidence/types"; -option (gogoproto.equal_all) = true; - -import "gogoproto/gogo.proto"; -import "google/protobuf/timestamp.proto"; - -// Equivocation implements the Evidence interface and defines evidence of double -// signing misbehavior. -message Equivocation { - option (gogoproto.goproto_stringer) = false; - option (gogoproto.goproto_getters) = false; - option (gogoproto.equal) = false; - - int64 height = 1; - google.protobuf.Timestamp time = 2 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - int64 power = 3; - string consensus_address = 4 [(gogoproto.moretags) = "yaml:\"consensus_address\""]; -} \ No newline at end of file From 23a25f84c8a50033ca01f0a2d665d887a5dbe646 Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Thu, 28 Sep 2023 12:23:05 +0200 Subject: [PATCH 21/40] fix E2E test --- app/provider/app.go | 10 ++++++++++ tests/e2e/steps.go | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/provider/app.go b/app/provider/app.go index d4dbf94211..dc08e6a482 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -450,6 +450,16 @@ func New( ibcRouter.AddRoute(providertypes.ModuleName, providerModule) app.IBCKeeper.SetRouter(ibcRouter) + // create evidence keeper with router + evidenceKeeper := evidencekeeper.NewKeeper( + appCodec, + keys[evidencetypes.StoreKey], + app.StakingKeeper, + app.SlashingKeeper, + ) + + app.EvidenceKeeper = *evidenceKeeper + skipGenesisInvariants := cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants)) // NOTE: Any module instantiated in the module manager that is later modified diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index b11d5d932c..3608131652 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -25,8 +25,8 @@ var happyPathSteps = concatSteps( stepsDowntime("consu"), stepsDoubleSignOnProviderAndConsumer("consu"), // carol double signs on provider, bob double signs on consumer stepsStartRelayer(), - stepsConsumerRemovalPropNotPassing("consu", 3), // submit removal prop but vote no on it - chain should stay - stepsStopChain("consu", 4), // stop chain + stepsConsumerRemovalPropNotPassing("consu", 2), // submit removal prop but vote no on it - chain should stay + stepsStopChain("consu", 3), // stop chain ) var shortHappyPathSteps = concatSteps( From 650ccb5254ea054ad74866d46a5f83598be94b9f Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Thu, 28 Sep 2023 12:24:17 +0200 Subject: [PATCH 22/40] fix Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e8ad65a81c..7f58c49632 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,7 @@ FROM informalofftermatt/cometmock:latest as cometmock-builder # Get GoRelayer FROM informalofftermatt/gorelayer:nogas AS gorelayer-builder -FROM --platform=linux/arm64 fedora:36 +FROM --platform=linux/amd64 fedora:36 RUN dnf update -y RUN dnf install -y which iproute iputils procps-ng vim-minimal tmux net-tools htop jq USER root From a654ebe408b48a9fd40ad1f88fe31aa68a8f536a Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Thu, 28 Sep 2023 14:00:57 +0200 Subject: [PATCH 23/40] more fixes --- .../adrs/adr-013-equivocation-slashing.md | 147 ++++++++++++++++++ docs/docs/features/slashing.md | 35 +---- docs/docs/validators/overview.md | 2 +- tests/e2e/steps.go | 2 +- tests/e2e/steps_double_sign.go | 78 +--------- 5 files changed, 157 insertions(+), 107 deletions(-) create mode 100644 docs/docs/adrs/adr-013-equivocation-slashing.md diff --git a/docs/docs/adrs/adr-013-equivocation-slashing.md b/docs/docs/adrs/adr-013-equivocation-slashing.md new file mode 100644 index 0000000000..1351cf5234 --- /dev/null +++ b/docs/docs/adrs/adr-013-equivocation-slashing.md @@ -0,0 +1,147 @@ +--- +sidebar_position: 14 +title: Slashing on the provider for consumer equivocation +--- +# ADR 013: Slashing on the provider for consumer equivocation + +## Changelog +* 1st Sept. 2023: Initial draft + +## Status +Proposed + +## Context +This ADR presents some approaches on how to slash on the provider chain validators that performed equivocations on consumer chains. +Currently, the provider chain can [receive and verify evidence of equivocation](https://github.com/cosmos/interchain-security/pull/1232), but it cannot slash the misbehaving validator. + +In the remainder of this section, we explain how slashing is performed on a single chain and show why slashing on the provider for equivocation on the consumer is challenging. + +Note that future versions of the Cosmos SDK, CometBFT, and ibc-go could modify the way we slash, etc. Therefore, a future reader of this ADR, should note that when we refer to Cosmos SDK, CometBFT, and ibc-go we specifically refer to their [v0.47](https://docs.cosmos.network/v0.47/intro/overview), [v0.37](https://docs.cometbft.com/v0.37/) and [v7.3.0](https://github.com/cosmos/ibc-go/blob/v7.3.0) versions respectively. + +### Single-chain slashing +Slashing is implemented across the [slashing](https://docs.cosmos.network/v0.47/modules/slashing) +and [staking](https://docs.cosmos.network/v0.47/modules/staking) modules. +The slashing module's keeper calls the staking module's `Slash()` method, passing among others, the `infractionHeight` (i.e., the height when the equivocation occurred), the validator's `power` at the infraction height, and the `slashFactor` (currently set to `5%` in case of equivocation on the Cosmos Hub). + +#### Slashing undelegations and redelegations +To slash undelegations, `Slash` goes through all undelegations and checks whether they started before or after the infraction occurred. If an undelegation started before the `infractionHeight`, then it is **not** slashed, otherwise it is slashed by `slashFactor`. + +The slashing of redelegations happens in a similar way, meaning that `Slash` goes through all redelegations and checks whether the redelegations started before or after the `infractionHeight`. + +#### Slashing delegations +Besides undelegations and redelegations, the validator's delegations need to also be slashed. +This is performed by deducting the appropriate amount of tokens from the validator. Note that this deduction is computed based on the voting `power` the misbehaving validator had at the height of the equivocation. As a result of the tokens deduction, +the [tokens per share](https://docs.cosmos.network/v0.47/modules/staking#delegator-shares) +reduce and hence later on, when delegators undelegate or redelegate, the delegators retrieve back less +tokens, effectively having their tokens slashed. The rationale behind this slashing mechanism, as mentioned in the [Cosmos SDK documentation](https://docs.cosmos.network/v0.47/modules/staking#delegator-shares) +> [...] is to simplify the accounting around slashing. Rather than iteratively slashing the tokens of every delegation entry, instead the Validators total bonded tokens can be slashed, effectively reducing the value of each issued delegator share. + +This approach of slashing delegations does not utilize the +`infractionHeight` in any way and hence the following scenario could occur: + 1. a validator `V` performs an equivocation at a height `Hi` + 2. a new delegator `D` delegates to `V` after height `Hi` + 3. evidence of the equivocation by validator `V` is received + 4. the tokens of delegator `D` are slashed + +In the above scenario, delegator `D` is slashed, even though `D`'s voting power did not contribute to the infraction. + + +#### Old evidence +In the single-chain case, old evidence (e.g., from 3 years ago) is ignored. This is achieved through +[CometBFT](https://docs.cometbft.com/v0.37/spec/consensus/evidence) that ignores old evidence based on the parameters `MaxAgeNumBlocks` and `MaxAgeDuration` (see [here](https://github.com/cometbft/cometbft/blob/v0.37.0/evidence/pool.go#271)). +Additionally, note that when the evidence is sent by CometBFT to the application, the evidence is rechecked in the [evidence module](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/evidence/keeper/infraction.go#L54) of Cosmos SDK and if it is old, the evidence is ignored. +In Cosmos Hub, the `MaxAgeNumBlocks` is set to 1000000 (i.e., ~70 days if we assume we need ~6 sec per block) and `MaxAgeDuration` is set to 172800000000000 ns (i.e., 2 days). Because of this check, we can easily exclude old evidence. + +### Slashing for equivocation on the consumer +In the single-chain case, slashing requires both the `infractionHeight` and the voting `power`. +In order to slash on the provider for an equivocation on a consumer, we need to have both the provider's `infractionHeight` and voting `power`. +Note that the `infractionHeight` on the consumer chain must be mapped to a height on the provider chain. +Unless we have a way to find the corresponding `infractionHeight` and `power` on the provider chain, we cannot slash for equivocation on the consumer in the same way as we would slash in the single-chain case. + + +The challenge of figuring out the corresponding `infractionHeight` and `power` values on the provider chain is due to the following trust assumption: + +- We trust the consensus layer and validator set of the consumer chains, _but we do not trust the application layer_. + +As a result, we cannot trust anything that stems from the _application state_ of a consumer chain. + +Note that when a relayer or a user sends evidence through a [MsgSubmitConsumerDoubleVoting](https://github.com/cosmos/interchain-security/pull/1232) message, the provider gets access to [DuplicateVoteEvidence](https://github.com/cometbft/cometbft/blob/v0.37.0/types/evidence.go#L35): +```protobuf +type DuplicateVoteEvidence struct { + VoteA *Vote `json:"vote_a"` + VoteB *Vote `json:"vote_b"` + + // abci specific information + TotalVotingPower int64 + ValidatorPower int64 + Timestamp time.Time +} +``` +The "abci specific information" fields cannot be trusted because they are not signed. Therefore, +we can use neither `ValidatorPower` for slashing on the provider chain, nor the `Timestamp` to check the evidence age. We can get the `infractionHeight` from the votes, but this `infractionHeight` corresponds to the infraction height on the consumer and **not** on the provider chain. +Similarly, when a relayer or a user sends evidence through a [MsgSubmitConsumerMisbehaviour](https://github.com/cosmos/interchain-security/pull/826) message, the provider gets access to [Misbehaviour](https://github.com/cosmos/ibc-go/blob/v7.3.0/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L79) that we cannot use to extract the infraction height, power, or the time on the provider chain. + +## Proposed solution +As a first iteration, we propose the following approach. At the moment the provider receives evidence of equivocation on a consumer: +1. slash all the undelegations and redelegations using `slashFactor`; +2. slash all delegations using as voting `power` the sum of the voting power of the misbehaving validator and the power of all the ongoing undelegations and redelegations. + +**Evidence expiration:** Additionally, because we cannot infer the actual time of the evidence (i.e., the timestamp of the evidence cannot be trusted), we do not consider _evidence expiration_ and hence old evidence is never ignored (e.g., the provider would act on 3 year-old evidence of equivocation on a consumer). +Additionally, we do not need to store equivocation evidence to avoid slashing a validator more than once, because we [do not slash](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/evidence/keeper/infraction.go#L94) tombstoned validators and we [tombstone](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/evidence/keeper/infraction.go#L138) a validator when slashed. + +We do not act on evidence that was signed by a validator [consensus key](https://tutorials.cosmos.network/tutorials/9-path-to-prod/3-keys.html#what-validator-keys) that is _pruned_ when we receive the evidence. We prune a validator's consensus key if the validator has assigned a new consumer key (using `MsgAssignConsumerKey`) and an unbonding period on the consumer chain has elapsed (see [key assignment ADR](https://github.com/cosmos/interchain-security/blob/main/docs/docs/adrs/adr-001-key-assignment.md)). Note that the provider chain is informed that the unbonding period has elapsed on the consumer when the provider receives a `VSCMaturedPacket` and because of this, if the consumer delays the sending of a `VSCMaturedPacket`, we would delay the pruning of the key as well. + +### Implementation +The following logic needs to be added to the [HandleConsumerDoubleVoting](https://github.com/cosmos/interchain-security/pull/1232) and [HandleConsumerMisbehaviour](https://github.com/cosmos/interchain-security/pull/826) methods: +```go +undelegationsInTokens := sdk.NewInt(0) +for _, v := range k.stakingKeeper.GetUnbondingDelegationsFromValidator(ctx, validatorAddress) { + for _, entry := range v.Entries { + if entry.IsMature(now) && !entry.OnHold() { + // undelegation no longer eligible for slashing, skip it + continue + } + undelegationsInTokens = undelegationsInTokens.Add(entry.InitialBalance) + } +} + +redelegationsInTokens := sdk.NewInt(0) +for _, v := range k.stakingKeeper.GetRedelegationsFromSrcValidator(ctx, validatorAddress) { + for _, entry := range v.Entries { + if entry.IsMature(now) && !entry.OnHold() { + // redelegation no longer eligible for slashing, skip it + continue + } + redelegationsInTokens = redelegationsInTokens.Add(entry.InitialBalance) + } +} + +infractionHeight := 0 +undelegationsAndRedelegationsInPower = sdk.TokensToConsensusPower(undelegationsInTokens.Add(redelegationsInTokens)) +totalPower := validator's voting power + undelegationsAndRedelegationsInPower +slashFraction := k.slashingKeeper.SlashFractionDoubleSign(ctx) + +k.stakingKeeper.Slash(ctx, validatorConsAddress, infractionHeight, totalPower, slashFraction, DoubleSign) +``` + +**Infraction height:** We provide a zero `infractionHeight` to the [Slash](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/staking/keeper/slash.go#L33) method in order to slash all ongoing undelegations and redelegations (see checks in [Slash](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/staking/keeper/slash.go#L92), [SlashUnbondingDelegation](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/staking/keeper/slash.go#L195), and [SlashRedelegation](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/staking/keeper/slash.go#L249)). + +**Power:** We pass the sum of the voting power of the misbehaving validator when the evidence was received (i.e., at evidence height) and the power of all the ongoing undelegations and redelegations. +If we assume that the `slashFactor` is `5%`, then the voting power we pass is `power + totalPower(undelegations) + totalPower(redelegations)`. +Hence, when the `Slash` method slashes all the undelegations and redelegations it would end up with `0.05 * power + 0.05 * totalPower(undelegations) + 0.05 * totalPower(redelegations) - 0.05 * totalPower(undelegations) - 0.05 * totalPower(redelegations) = 0.05 * power` and hence it would slash `5%` of the validator's power when the evidence is received. + +### Positive +With the proposed approach we can quickly implement slashing functionality on the provider chain for consumer chain equivocations. +This approach does not need to change the staking module and therefore does not change in any way how slashing is performed today for a single chain. + +### Negative + +- We _definitely_ slash more when it comes to undelegations and redelegations because we slash for all of them without considering an `infractionHeight`. +- We _potentially_ slash more than what we would have slashed if we knew the voting `power` at the corresponding `infractionHeight` in the provider chain. +- We slash on old evidence of equivocation on a consumer. + + +## References +* [ADR 005: Cryptographic verification of equivocation evidence](https://github.com/cosmos/interchain-security/blob/main/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md) +* [EPIC tracking cryptographic equivocation feature](https://github.com/cosmos/interchain-security/issues/732) +* [Cosmos Hub Forum discussion on cryptographic equivocation slashing](https://forum.cosmos.network/t/cryptographic-equivocation-slashing-design/11400) diff --git a/docs/docs/features/slashing.md b/docs/docs/features/slashing.md index e1a51bf8b2..09ceeefd0b 100644 --- a/docs/docs/features/slashing.md +++ b/docs/docs/features/slashing.md @@ -3,7 +3,7 @@ sidebar_position: 4 --- # Consumer Initiated Slashing -A consumer chain is essentially a regular Cosmos-SDK based chain that uses the interchain security module to achieve economic security by stake deposited on the provider chain, instead of it's own chain. +A consumer chain is essentially a regular Cosmos-SDK based chain that uses the interchain security module to achieve economic security by stake deposited on the provider chain, instead of its own chain. In essence, provider chain and consumer chains are different networks (different infrastructures) that are bound together by the provider's validator set. By being bound to the provider's validator set, a consumer chain inherits the economic security guarantees of the provider chain (in terms of total stake). To maintain the proof of stake model, the consumer chain is able to send evidence of infractions (double signing and downtime) to the provider chain so the offending validators can be penalized. @@ -17,36 +17,13 @@ reported by consumer chains are acted upon on the provider as soon as the provid Instead of slashing, the provider will only jail offending validator for the duration of time established in the chain parameters. :::info -Slash throttling (sometimes called jail throttling) mechanism insures that only a fraction of the validator set can be jailed at any one time to prevent malicious consumer chains from harming the provider. +Slash throttling (sometimes called jail throttling) mechanism ensures that only a fraction of the validator set can be jailed at any one time to prevent malicious consumer chains from harming the provider. ::: -## Double-signing (equivocation) -infractions are not acted upon immediately. +# Cryptographic verification of equivocation and slashing +The Cryptographic verification of equivocation allows external agents to submit evidences of light client and double signing attack observed on a consumer chain. When a valid evidence is received, the malicious validators will be slashed, jailed, and tombstoned on the provider. -Upon receiving double signing evidence, the provider chain will take note of the evidence and allow for `EquivocationProposal` to be submitted to slash the offending validator. -Any `EquivocationProposal`s to slash a validator that has not double signed on any of the consumer chains will be automatically rejected by the provider chain. - -:::info -The offending validator will only be slashed (and tombstoned) if an `EquivocationProposal` is accepted and passed through governance. - -The offending validator will effectively get slashed and tombstoned on all consumer chains. -::: - - -You can find instructions on creating `EquivocationProposal`s [here](./proposals#equivocationproposal). - -# Cryptographic verification of equivocation -The Cryptographic verification of equivocation allows external agents to submit evidences of light client and double signing attack observed on a consumer chain. When a valid evidence is received, the malicious validators will be permanently jailed on the provider. - -The feature is outlined in this [ADR-005](../adrs/adr-005-cryptographic-equivocation-verification.md) +The feature is outlined in [ADR-005](../adrs/adr-005-cryptographic-equivocation-verification.md) and [ADR-013](../adrs/adr-013-equivocation-slashing.md). By sending a `MsgSubmitConsumerMisbehaviour` or a `MsgSubmitConsumerDoubleVoting` transaction, the provider will - verify the reported equivocation and, if successful, jail the malicious validator. - -:::info -Note that this feature can only lead to the jailing of the validators responsible for an attack on a consumer chain. However, an [equivocation proposal](#double-signing-equivocation) can still be submitted to execute the slashing and the tombstoning of the a malicious validator afterwards. -::: - - - - + verify the reported equivocation and, if successful, slash, jail, and tombstone the malicious validator. \ No newline at end of file diff --git a/docs/docs/validators/overview.md b/docs/docs/validators/overview.md index 5a670f0780..136d601192 100644 --- a/docs/docs/validators/overview.md +++ b/docs/docs/validators/overview.md @@ -91,7 +91,7 @@ More information is available in [Downtime Slashing documentation](../features/s ::: ## Double-signing Infractions -To learn more about equivocation handling in replicated security check out the [Slashing](../features/slashing.md#double-signing-equivocation) and [EquivocationProposal](../features/proposals.md#equivocationproposal) documentation sections +To learn more about equivocation handling in replicated security check out the [Slashing](../features/slashing.md) documentation section. ## Key assignment Validators can use different consensus keys on the provider and each of the consumer chains. The consumer chain consensus key must be registered on the provider before use. diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 3608131652..2cf310f157 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -23,7 +23,7 @@ var happyPathSteps = concatSteps( stepsDowntimeWithOptOut("consu"), stepsRedelegate("consu"), stepsDowntime("consu"), - stepsDoubleSignOnProviderAndConsumer("consu"), // carol double signs on provider, bob double signs on consumer + stepsDoubleSignOnProvider("consu"), // carol double signs on provider stepsStartRelayer(), stepsConsumerRemovalPropNotPassing("consu", 2), // submit removal prop but vote no on it - chain should stay stepsStopChain("consu", 3), // stop chain diff --git a/tests/e2e/steps_double_sign.go b/tests/e2e/steps_double_sign.go index 63e187237a..eeafac3c96 100644 --- a/tests/e2e/steps_double_sign.go +++ b/tests/e2e/steps_double_sign.go @@ -1,7 +1,7 @@ package main -// Steps that make carol double sign on the provider, and bob double sign on a single consumer -func stepsDoubleSignOnProviderAndConsumer(consumerName string) []Step { +// Steps that make carol double sign on the provider, and this power change propagates to consumer chain `consumerName` +func stepsDoubleSignOnProvider(consumerName string) []Step { return []Step{ { // provider double sign @@ -52,80 +52,6 @@ func stepsDoubleSignOnProviderAndConsumer(consumerName string) []Step { }, }, }, - { - // consumer double sign - // provider will only log the double sign slash - // stepsSubmitEquivocationProposal will cause the double sign slash to be executed - action: doublesignSlashAction{ - chain: chainID("consu"), - validator: validatorID("bob"), - }, - state: State{ - chainID("provi"): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 500, - validatorID("carol"): 0, - }, - }, - chainID(consumerName): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 500, - validatorID("carol"): 0, - }, - }, - }, - }, - { - action: relayPacketsAction{ - chainA: chainID("provi"), - chainB: chainID(consumerName), - port: "provider", - channel: 0, - }, - state: State{ - chainID("provi"): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 500, // not tombstoned - validatorID("carol"): 0, - }, - }, - chainID(consumerName): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 500, // not tombstoned - validatorID("carol"): 0, - }, - }, - }, - }, - { - // consumer learns about the double sign - action: relayPacketsAction{ - chainA: chainID("provi"), - chainB: chainID(consumerName), - port: "provider", - channel: 0, - }, - state: State{ - chainID("provi"): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 500, - validatorID("carol"): 0, - }, - }, - chainID(consumerName): ChainState{ - ValPowers: &map[validatorID]uint{ - validatorID("alice"): 509, - validatorID("bob"): 500, // not tombstoned - validatorID("carol"): 0, - }, - }, - }, - }, } } From 236153f0c17cc19c546a79e87e650bfbd14fd97f Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Thu, 28 Sep 2023 14:17:27 +0200 Subject: [PATCH 24/40] increase wait attempt --- Dockerfile | 2 +- tests/e2e/actions.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7f58c49632..e8ad65a81c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,7 @@ FROM informalofftermatt/cometmock:latest as cometmock-builder # Get GoRelayer FROM informalofftermatt/gorelayer:nogas AS gorelayer-builder -FROM --platform=linux/amd64 fedora:36 +FROM --platform=linux/arm64 fedora:36 RUN dnf update -y RUN dnf install -y which iproute iputils procps-ng vim-minimal tmux net-tools htop jq USER root diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 3da9e0d394..a5c2a95c62 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -455,7 +455,7 @@ func (tr TestRun) voteGovProposal( } wg.Wait() - time.Sleep(time.Duration(tr.chainConfigs[action.chain].votingWaitTime) * time.Second) + time.Sleep(time.Duration(tr.chainConfigs[action.chain].votingWaitTime)*time.Second + 10*time.Second) } type startConsumerChainAction struct { From 6fc6721979a4aec30a83716a9cb2d87e7a686a7b Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Thu, 28 Sep 2023 14:17:42 +0200 Subject: [PATCH 25/40] increase wait attempt --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e8ad65a81c..7f58c49632 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,7 @@ FROM informalofftermatt/cometmock:latest as cometmock-builder # Get GoRelayer FROM informalofftermatt/gorelayer:nogas AS gorelayer-builder -FROM --platform=linux/arm64 fedora:36 +FROM --platform=linux/amd64 fedora:36 RUN dnf update -y RUN dnf install -y which iproute iputils procps-ng vim-minimal tmux net-tools htop jq USER root From c9702c2dbf58301de2f8b3ae54ae28e0c468247b Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Thu, 28 Sep 2023 15:06:38 +0200 Subject: [PATCH 26/40] wait 1 block in gov proposal --- tests/e2e/actions.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index a5c2a95c62..321507f96d 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -455,7 +455,8 @@ func (tr TestRun) voteGovProposal( } wg.Wait() - time.Sleep(time.Duration(tr.chainConfigs[action.chain].votingWaitTime)*time.Second + 10*time.Second) + time.Sleep(time.Duration(tr.chainConfigs[action.chain].votingWaitTime) * time.Second) + tr.waitBlocks(action.chain, 1, 5*time.Second) } type startConsumerChainAction struct { From 34aa49083d15ecf6c38ec8ff39feed0a06dd1d4c Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Thu, 28 Sep 2023 15:25:56 +0200 Subject: [PATCH 27/40] fix numbers --- x/ccv/provider/keeper/keeper.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 919996c2e9..c0a5dc5ea8 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -105,9 +105,9 @@ func (k Keeper) mustValidateFields() { ccv.PanicIfZeroOrNil(k.clientKeeper, "clientKeeper") // 9 ccv.PanicIfZeroOrNil(k.stakingKeeper, "stakingKeeper") // 10 ccv.PanicIfZeroOrNil(k.slashingKeeper, "slashingKeeper") // 11 - ccv.PanicIfZeroOrNil(k.distributionKeeper, "distributionKeeper") // 13 - ccv.PanicIfZeroOrNil(k.bankKeeper, "bankKeeper") // 14 - ccv.PanicIfZeroOrNil(k.feeCollectorName, "feeCollectorName") // 15 + ccv.PanicIfZeroOrNil(k.distributionKeeper, "distributionKeeper") // 12 + ccv.PanicIfZeroOrNil(k.bankKeeper, "bankKeeper") // 13 + ccv.PanicIfZeroOrNil(k.feeCollectorName, "feeCollectorName") // 14 } // Logger returns a module-specific logger. From 48a4b5117e0dbc600eb6f519f554971e3ff79289 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 13 Oct 2023 17:42:48 +0200 Subject: [PATCH 28/40] fix double voting security hole --- testutil/crypto/evidence.go | 35 +++++++++++++++++++++++ x/ccv/provider/keeper/double_vote.go | 10 +++++++ x/ccv/provider/keeper/double_vote_test.go | 24 ++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/testutil/crypto/evidence.go b/testutil/crypto/evidence.go index 050c17331a..d49358e892 100644 --- a/testutil/crypto/evidence.go +++ b/testutil/crypto/evidence.go @@ -54,3 +54,38 @@ func MakeAndSignVote( vote.Signature = v.Signature return vote } + +// MakeAndSignVoteWithForgedValAddress makes and signs a vote using two different keys: +// one to derive the validator address in the vote and a second to sign it. +func MakeAndSignVoteWithForgedValAddress( + blockID tmtypes.BlockID, + blockHeight int64, + blockTime time.Time, + valSet *tmtypes.ValidatorSet, + signer tmtypes.PrivValidator, + chainID string, +) *tmtypes.Vote { + // create the vote using a different key than the signing key + forgedSigner := tmtypes.NewMockPV() + vote, err := tmtypes.MakeVote( + blockHeight, + blockID, + valSet, + forgedSigner, + chainID, + blockTime, + ) + if err != nil { + panic(err) + } + + // sign vote using the given private key + v := vote.ToProto() + err = signer.SignVote(chainID, v) + if err != nil { + panic(err) + } + + vote.Signature = v.Signature + return vote +} diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index d3813f8da4..597b2037dc 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" + errorsmod "cosmossdk.io/errors" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -59,6 +60,15 @@ func (k Keeper) VerifyDoubleVotingEvidence( return fmt.Errorf("validator public key cannot be empty") } + // check that the validator address in the evidence is derived from the provided public key + if !bytes.Equal(pubkey.Address(), evidence.VoteA.ValidatorAddress) { + return errorsmod.Wrapf( + ccvtypes.ErrInvalidDoubleVotingEvidence, + "public key %s doesn't correspond to the validator address %s in double vote evidence", + pubkey.String(), evidence.VoteA.ValidatorAddress.String(), + ) + } + // Note that since we're only jailing validators for double voting on a consumer chain, // the age of the evidence is irrelevant and therefore isn't checked. diff --git a/x/ccv/provider/keeper/double_vote_test.go b/x/ccv/provider/keeper/double_vote_test.go index cc8280b14d..617e56fb0e 100644 --- a/x/ccv/provider/keeper/double_vote_test.go +++ b/x/ccv/provider/keeper/double_vote_test.go @@ -44,6 +44,30 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { pubkey cryptotypes.PubKey expPass bool }{ + { + "verifying public key doesn't correspond to validator address", + []*tmtypes.Vote{ + testutil.MakeAndSignVoteWithForgedValAddress( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + testutil.MakeAndSignVoteWithForgedValAddress( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, { "evidence has votes with different block height - shouldn't pass", []*tmtypes.Vote{ From 6a355fe6a0183335af4fbef93850ed91b308f9d2 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 13 Oct 2023 18:07:21 +0200 Subject: [PATCH 29/40] fix error logging --- x/ccv/provider/keeper/misbehaviour.go | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index c53e11b450..30de615a50 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -1,8 +1,6 @@ package keeper import ( - "fmt" - "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -36,7 +34,6 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty provAddrs := make([]types.ProviderConsAddress, len(byzantineValidators)) - var errors []error // slash, jail, and tombstone the Byzantine validators for _, v := range byzantineValidators { providerAddr := k.GetProviderAddrFromConsumerAddr( @@ -46,12 +43,13 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty ) err := k.SlashValidator(ctx, providerAddr) if err != nil { - errors = append(errors, err) + logger.Error("failed to slash validator: %s", err) } err = k.JailAndTombstoneValidator(ctx, providerAddr) if err != nil { - errors = append(errors, err) + logger.Error("failed to jail or tombstone validator: %s", err) } + provAddrs = append(provAddrs, providerAddr) } @@ -60,16 +58,6 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty "byzantine validators", provAddrs, ) - // If we fail to slash all validators we return an error. However, if we only fail to slash some validators - // we just log an error to avoid having the whole `MsgSubmitMisbehaviour` failing and reverting the partial slashing. - if len(errors) == len(byzantineValidators) { - return fmt.Errorf("failed to slash, jail, or tombstone all validators: %v", errors) - } - - if len(errors) > 0 { - logger.Error("failed to slash, jail, or tombstone validators: %v", errors) - } - return nil } From 79b126bbda9d2d48d491679603fe5dd475c394e5 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 13 Oct 2023 19:07:41 +0200 Subject: [PATCH 30/40] fix nit --- x/ccv/provider/proposal_handler_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/ccv/provider/proposal_handler_test.go b/x/ccv/provider/proposal_handler_test.go index f15f989f19..fd0df502a1 100644 --- a/x/ccv/provider/proposal_handler_test.go +++ b/x/ccv/provider/proposal_handler_test.go @@ -111,7 +111,6 @@ func TestProviderProposalHandler(t *testing.T) { if tc.expValidConsumerAddition || tc.expValidConsumerRemoval || tc.expValidChangeRewardDenom { require.NoError(t, err) - require.Error(t, err) } else { require.Error(t, err) } From bcdd2c42a1d2744424b1d822c82c5efc8ed39941 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 16 Oct 2023 09:28:14 +0200 Subject: [PATCH 31/40] fix first comments --- x/ccv/provider/client/cli/tx.go | 2 +- x/ccv/provider/keeper/double_vote.go | 2 +- x/ccv/provider/keeper/misbehaviour.go | 5 ++++- x/ccv/provider/keeper/punish_validator.go | 1 - 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index f32bc304df..6d975bc3e4 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -127,7 +127,7 @@ func NewSubmitConsumerDoubleVotingCmd() *cobra.Command { , see cometbft/proto/tendermint/types/evidence.proto and the IBC header definition can be found in the IBC messages, see ibc-go/proto/ibc/lightclients/tendermint/v1/tendermint.proto. -Examples: +Example: %s tx provider submit-consumer-double-voting [path/to/evidence.json] [path/to/infraction_header.json] --from node0 --home ../node0 --chain-id $CID `, version.AppName)), Args: cobra.ExactArgs(2), diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 597b2037dc..9ad4610e54 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -72,7 +72,7 @@ func (k Keeper) VerifyDoubleVotingEvidence( // Note that since we're only jailing validators for double voting on a consumer chain, // the age of the evidence is irrelevant and therefore isn't checked. - // H/R/S must be the same + // Height/Round/Type must be the same if evidence.VoteA.Height != evidence.VoteB.Height || evidence.VoteA.Round != evidence.VoteB.Round || evidence.VoteA.Type != evidence.VoteB.Type { diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 30de615a50..b5519b4cab 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -44,10 +44,12 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty err := k.SlashValidator(ctx, providerAddr) if err != nil { logger.Error("failed to slash validator: %s", err) + continue } err = k.JailAndTombstoneValidator(ctx, providerAddr) if err != nil { logger.Error("failed to jail or tombstone validator: %s", err) + continue } provAddrs = append(provAddrs, providerAddr) @@ -55,7 +57,8 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty logger.Info( "confirmed equivocation light client attack", - "byzantine validators", provAddrs, + "byzantine validators", byzantineValidators, + "byzantine validators that got slashes, jailed, and tombstoned", provAddrs, ) return nil diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go index 6c9e88f260..5e1cc07ee7 100644 --- a/x/ccv/provider/keeper/punish_validator.go +++ b/x/ccv/provider/keeper/punish_validator.go @@ -37,7 +37,6 @@ func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.Pr k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime) // Tombstone the validator so that we cannot slash the validator more than once - // (see cosmos/cosmos-sdk/blob/v0.45.16-ics-lsm/x/evidence/keeper/infraction.go#L81). // Note that we cannot simply use the fact that a validator is jailed to avoid slashing more than once // because then a validator could i) perform an equivocation, ii) get jailed (e.g., through downtime) // and in such a case the validator would not get slashed when we call `SlashValidator`. From 7b5248a257a31cfe0eb8df2f2590bc68f32372b3 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 16 Oct 2023 09:44:04 +0200 Subject: [PATCH 32/40] add hermes comment --- tests/e2e/testnet-scripts/hermes-config.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/e2e/testnet-scripts/hermes-config.toml b/tests/e2e/testnet-scripts/hermes-config.toml index 89c1f0a0bb..0f8c838e99 100644 --- a/tests/e2e/testnet-scripts/hermes-config.toml +++ b/tests/e2e/testnet-scripts/hermes-config.toml @@ -8,9 +8,13 @@ enabled = true refresh = true misbehaviour = true +# mode disabled since connections are created manually in the tests +# see addIbcChannelHermes in /tests/e2e/actions.go#L1113 [mode.connections] enabled = false +# mode disabled since channels are created manually in the tests +# see addIbcConnection in /tests/e2e/actions.go#L879 [mode.channels] enabled = false From 082386eaa19da30bce754f42e280100d8ce827b1 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 16 Oct 2023 09:47:45 +0200 Subject: [PATCH 33/40] update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0be65b94f..a5c82babcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,11 @@ ## [Unreleased] Add an entry to the unreleased section whenever merging a PR to main that is not targeted at a specific release. These entries will eventually be included in a release. + ### Cryptographic verification of equivocation * New feature enabling the provider chain to verify equivocation evidence on its own instead of trusting consumer chains, see [EPIC](https://github.com/cosmos/interchain-security/issues/732). -## v2.1.0-lsm-provider +## v2.1.0-provider-lsm * (feature!) [#1280](https://github.com/cosmos/interchain-security/pull/1280) provider proposal for changing reward denoms From 882e2894271bb8841cd2253f3eb1582be4defe8d Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 16 Oct 2023 14:34:41 +0200 Subject: [PATCH 34/40] changes misbehavour logging --- x/ccv/provider/keeper/double_vote_test.go | 48 +++++++++++------------ x/ccv/provider/keeper/misbehaviour.go | 7 ---- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/x/ccv/provider/keeper/double_vote_test.go b/x/ccv/provider/keeper/double_vote_test.go index 617e56fb0e..84156e4e6a 100644 --- a/x/ccv/provider/keeper/double_vote_test.go +++ b/x/ccv/provider/keeper/double_vote_test.go @@ -44,6 +44,30 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { pubkey cryptotypes.PubKey expPass bool }{ + { + "invalid verifying public key - shouldn't pass", + []*tmtypes.Vote{ + testutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + testutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + nil, + false, + }, { "verifying public key doesn't correspond to validator address", []*tmtypes.Vote{ @@ -212,30 +236,6 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { valPubkey1, false, }, - { - "invalid public key - shouldn't pass", - []*tmtypes.Vote{ - testutil.MakeAndSignVote( - blockID1, - ctx.BlockHeight(), - ctx.BlockTime(), - valSet, - signer1, - chainID, - ), - testutil.MakeAndSignVote( - blockID2, - ctx.BlockHeight(), - ctx.BlockTime(), - valSet, - signer1, - chainID, - ), - }, - chainID, - nil, - false, - }, { "wrong public key - shouldn't pass", []*tmtypes.Vote{ diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index b5519b4cab..286228a4be 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -32,8 +32,6 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty return err } - provAddrs := make([]types.ProviderConsAddress, len(byzantineValidators)) - // slash, jail, and tombstone the Byzantine validators for _, v := range byzantineValidators { providerAddr := k.GetProviderAddrFromConsumerAddr( @@ -44,21 +42,16 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty err := k.SlashValidator(ctx, providerAddr) if err != nil { logger.Error("failed to slash validator: %s", err) - continue } err = k.JailAndTombstoneValidator(ctx, providerAddr) if err != nil { logger.Error("failed to jail or tombstone validator: %s", err) - continue } - - provAddrs = append(provAddrs, providerAddr) } logger.Info( "confirmed equivocation light client attack", "byzantine validators", byzantineValidators, - "byzantine validators that got slashes, jailed, and tombstoned", provAddrs, ) return nil From 79c0492db08a60d239760a7b393d1d0ed25f43cf Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 16 Oct 2023 14:46:42 +0200 Subject: [PATCH 35/40] update double vote and mem test --- testutil/crypto/evidence.go | 4 ++-- x/ccv/provider/keeper/double_vote.go | 3 +-- x/ccv/provider/keeper/double_vote_test.go | 5 ++++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/testutil/crypto/evidence.go b/testutil/crypto/evidence.go index d49358e892..3f75010fa1 100644 --- a/testutil/crypto/evidence.go +++ b/testutil/crypto/evidence.go @@ -63,15 +63,15 @@ func MakeAndSignVoteWithForgedValAddress( blockTime time.Time, valSet *tmtypes.ValidatorSet, signer tmtypes.PrivValidator, + valAddressSigner tmtypes.PrivValidator, chainID string, ) *tmtypes.Vote { // create the vote using a different key than the signing key - forgedSigner := tmtypes.NewMockPV() vote, err := tmtypes.MakeVote( blockHeight, blockID, valSet, - forgedSigner, + valAddressSigner, chainID, blockTime, ) diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 9ad4610e54..88b6a06f4b 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -22,7 +22,7 @@ func (k Keeper) HandleConsumerDoubleVoting( pubkey cryptotypes.PubKey, ) error { // verifies the double voting evidence using the consumer chain public key - if err := k.VerifyDoubleVotingEvidence(ctx, *evidence, chainID, pubkey); err != nil { + if err := k.VerifyDoubleVotingEvidence(*evidence, chainID, pubkey); err != nil { return err } @@ -51,7 +51,6 @@ func (k Keeper) HandleConsumerDoubleVoting( // VerifyDoubleVotingEvidence verifies a double voting evidence // for a given chain id and a validator public key func (k Keeper) VerifyDoubleVotingEvidence( - ctx sdk.Context, evidence tmtypes.DuplicateVoteEvidence, chainID string, pubkey cryptotypes.PubKey, diff --git a/x/ccv/provider/keeper/double_vote_test.go b/x/ccv/provider/keeper/double_vote_test.go index 84156e4e6a..0b21211fca 100644 --- a/x/ccv/provider/keeper/double_vote_test.go +++ b/x/ccv/provider/keeper/double_vote_test.go @@ -69,6 +69,8 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { false, }, { + // Note that the signer1 key is used to sign the vote and + // signer2 is used to derive the validator addresss of the same vote "verifying public key doesn't correspond to validator address", []*tmtypes.Vote{ testutil.MakeAndSignVoteWithForgedValAddress( @@ -77,6 +79,7 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { ctx.BlockTime(), valSet, signer1, + signer2, chainID, ), testutil.MakeAndSignVoteWithForgedValAddress( @@ -85,6 +88,7 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { ctx.BlockTime(), valSet, signer1, + signer2, chainID, ), }, @@ -288,7 +292,6 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { for _, tc := range testCases { err = keeper.VerifyDoubleVotingEvidence( - ctx, tmtypes.DuplicateVoteEvidence{ VoteA: tc.votes[0], VoteB: tc.votes[1], From 88978cffb6b9f80f002cbc8acaf2c4ceb2602662 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 16 Oct 2023 17:03:54 +0200 Subject: [PATCH 36/40] change misb error flow --- x/ccv/provider/keeper/misbehaviour.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 286228a4be..ccf547dd56 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -32,6 +34,8 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty return err } + provAddrs := make([]types.ProviderConsAddress, len(byzantineValidators)) + // slash, jail, and tombstone the Byzantine validators for _, v := range byzantineValidators { providerAddr := k.GetProviderAddrFromConsumerAddr( @@ -42,16 +46,27 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty err := k.SlashValidator(ctx, providerAddr) if err != nil { logger.Error("failed to slash validator: %s", err) + continue } err = k.JailAndTombstoneValidator(ctx, providerAddr) + // JailAndTombstoneValidator should never return an error if + // SlashValidator succeeded because both methods fails if the malicious + // validator is either or both !found, unbonded and tombstoned. if err != nil { - logger.Error("failed to jail or tombstone validator: %s", err) + panic(err) } + + provAddrs = append(provAddrs, providerAddr) + } + + // Return an error if no validators were punished + if len(provAddrs) == 0 { + return fmt.Errorf("failed to slash, jail, or tombstone all validators: %v", byzantineValidators) } logger.Info( "confirmed equivocation light client attack", - "byzantine validators", byzantineValidators, + "byzantine validators slashed, jailed and tombstoned", provAddrs, ) return nil From c41f526d44d492a922e49b2cc3a5c0779574d830 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 17 Oct 2023 11:25:08 +0200 Subject: [PATCH 37/40] add debug call --- tests/integration/double_vote.go | 9 ++++++++- testutil/integration/debug_test.go | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index d2a00e583b..e834b73ad4 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -1,6 +1,8 @@ package integration import ( + "fmt" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" testutil "github.com/cosmos/interchain-security/v2/testutil/crypto" @@ -294,6 +296,9 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegations() { _, shares, _ = delegateByIdx(s, delAddr, sdk.NewInt(50000000), idx) _ = undelegate(s, delAddr, valAddr, shares) + ubds, _ := s.providerApp.GetTestStakingKeeper().GetUnbondingDelegation(s.providerCtx(), delAddr, validator.GetOperator()) + fmt.Println(ubds.String()) + err = s.providerApp.GetProviderKeeper().HandleConsumerDoubleVoting( s.providerCtx(), evidence, @@ -305,7 +310,9 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegations() { slashFraction := s.providerApp.GetTestSlashingKeeper().SlashFractionDoubleSign(s.providerCtx()) // check undelegations are slashed - ubds, _ := s.providerApp.GetTestStakingKeeper().GetUnbondingDelegation(s.providerCtx(), delAddr, validator.GetOperator()) + ubds, _ = s.providerApp.GetTestStakingKeeper().GetUnbondingDelegation(s.providerCtx(), delAddr, validator.GetOperator()) + fmt.Println(ubds.String()) + s.Require().True(len(ubds.Entries) > 0) for _, unb := range ubds.Entries { initialBalance := unb.InitialBalance.ToDec() diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index fcb19d5255..3244c18443 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -280,3 +280,7 @@ func TestCheckMisbehaviour(t *testing.T) { func TestHandleConsumerDoubleVoting(t *testing.T) { runCCVTestByName(t, "TestHandleConsumerDoubleVoting") } + +func TestHandleConsumerDoubleVotingSlashesUndelegations(t *testing.T) { + runCCVTestByName(t, "TestHandleConsumerDoubleVotingSlashesUndelegations") +} From e633e53411131d166cb6b16a05547b64adbec10f Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 17 Oct 2023 13:54:14 +0200 Subject: [PATCH 38/40] add redelegations testings --- tests/integration/double_vote.go | 47 ++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index e834b73ad4..d108abbdee 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -1,8 +1,6 @@ package integration import ( - "fmt" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" testutil "github.com/cosmos/interchain-security/v2/testutil/crypto" @@ -212,7 +210,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { // TestHandleConsumerDoubleVotingSlashesUndelegations verifies that handling a successful double voting // evidence of a consumer chain results in the expected slashing of the misbehave validator undelegations -func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegations() { +func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegationsAndRelegations() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 s.SendEmptyVSCPacket() @@ -225,6 +223,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegations() { consuValSet, err := tmtypes.ValidatorSetFromProto(s.consumerChain.LastHeader.ValidatorSet) s.Require().NoError(err) consuVal := consuValSet.Validators[0] + consuVal2 := consuValSet.Validators[1] consuSigner := s.consumerChain.Signers[consuVal.Address.String()] blockID1 := testutil.MakeBlockID([]byte("blockhash"), 1000, []byte("partshash")) @@ -267,10 +266,16 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegations() { consuAddr := types.NewConsumerConsAddress(sdk.ConsAddress(consuVal.Address.Bytes())) provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) + consuAddr2 := types.NewConsumerConsAddress(sdk.ConsAddress(consuVal2.Address.Bytes())) + provAddr2 := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr2) + validator, found := s.providerApp.GetTestStakingKeeper().GetValidator(s.providerCtx(), provAddr.ToSdkConsAddr().Bytes()) s.Require().True(found) - s.Run("slash undelegations when getting double voting evidence", func() { + validator2, found := s.providerApp.GetTestStakingKeeper().GetValidator(s.providerCtx(), provAddr2.ToSdkConsAddr().Bytes()) + s.Require().True(found) + + s.Run("slash undelegations and redelegations when getting double voting evidence", func() { // convert validator public key pk, err := cryptocodec.FromTmPubKeyInterface(pubKey) s.Require().NoError(err) @@ -290,15 +295,33 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegations() { } } - _, shares, valAddr := delegateByIdx(s, delAddr, bondAmt, idx) - _ = undelegate(s, delAddr, valAddr, shares) + // delegate bond amount + _, shares, _ := delegateByIdx(s, delAddr, bondAmt, idx) + s.Require().NotZero(shares) - _, shares, _ = delegateByIdx(s, delAddr, sdk.NewInt(50000000), idx) - _ = undelegate(s, delAddr, valAddr, shares) + // undelegate 1/2 of the bound amount + undelegate(s, delAddr, validator.GetOperator(), shares.Quo(sdk.NewDec(4))) + undelegate(s, delAddr, validator.GetOperator(), shares.Quo(sdk.NewDec(4))) + // check that undelegations was successful ubds, _ := s.providerApp.GetTestStakingKeeper().GetUnbondingDelegation(s.providerCtx(), delAddr, validator.GetOperator()) - fmt.Println(ubds.String()) + s.Require().Len(ubds.Entries, 2) + + // save the delegation shares of the validator to redelegate to + // Note this shares should not be slashed! + delShares := s.providerApp.GetTestStakingKeeper().Delegation(s.providerCtx(), delAddr, validator2.GetOperator()).GetShares() + // redelegate 1/2 of the bound amount + redelegate(s, delAddr, validator.GetOperator(), validator2.GetOperator(), shares.Quo(sdk.NewDec(4))) + redelegate(s, delAddr, validator.GetOperator(), validator2.GetOperator(), shares.Quo(sdk.NewDec(4))) + + // check that redelegation was successful + rdel := s.providerApp.GetTestStakingKeeper().GetRedelegations(s.providerCtx(), delAddr, uint16(10)) + s.Require().Len(rdel[0].Entries, 2) + + redelShares := rdel[0].Entries[0].SharesDst.Add(rdel[0].Entries[1].SharesDst) + + // cause double voting err = s.providerApp.GetProviderKeeper().HandleConsumerDoubleVoting( s.providerCtx(), evidence, @@ -309,9 +332,8 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegations() { slashFraction := s.providerApp.GetTestSlashingKeeper().SlashFractionDoubleSign(s.providerCtx()) - // check undelegations are slashed + // check undelegations and redelegations are slashed ubds, _ = s.providerApp.GetTestStakingKeeper().GetUnbondingDelegation(s.providerCtx(), delAddr, validator.GetOperator()) - fmt.Println(ubds.String()) s.Require().True(len(ubds.Entries) > 0) for _, unb := range ubds.Entries { @@ -319,5 +341,8 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegations() { currentBalance := unb.Balance.ToDec() s.Require().True(initialBalance.Sub(initialBalance.Mul(slashFraction)).Equal(currentBalance)) } + + delegations := s.providerApp.GetTestStakingKeeper().Delegation(s.providerCtx(), delAddr, validator2.GetOperator()) + s.Require().Equal(delegations.GetShares(), delShares.Add(redelShares).Sub(redelShares.Mul(slashFraction))) }) } From 28e6cccfb772c2293638b6a4f4616ec5a419dce9 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 17 Oct 2023 17:33:13 +0200 Subject: [PATCH 39/40] fix nit in mem test --- tests/integration/double_vote.go | 5 ++++- testutil/integration/debug_test.go | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index d108abbdee..4b862fbaaa 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -1,6 +1,8 @@ package integration import ( + "fmt" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" testutil "github.com/cosmos/interchain-security/v2/testutil/crypto" @@ -305,7 +307,8 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegationsAndRele // check that undelegations was successful ubds, _ := s.providerApp.GetTestStakingKeeper().GetUnbondingDelegation(s.providerCtx(), delAddr, validator.GetOperator()) - s.Require().Len(ubds.Entries, 2) + s.Require().Len(ubds.Entries, 1) + fmt.Println(ubds.String()) // save the delegation shares of the validator to redelegate to // Note this shares should not be slashed! diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index 3244c18443..227542d154 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -281,6 +281,6 @@ func TestHandleConsumerDoubleVoting(t *testing.T) { runCCVTestByName(t, "TestHandleConsumerDoubleVoting") } -func TestHandleConsumerDoubleVotingSlashesUndelegations(t *testing.T) { - runCCVTestByName(t, "TestHandleConsumerDoubleVotingSlashesUndelegations") +func TestHandleConsumerDoubleVotingSlashesUndelegationsAndRelegations(t *testing.T) { + runCCVTestByName(t, "TestHandleConsumerDoubleVotingSlashesUndelegationsAndRelegations") } From 21d156ba5b67f925d85f6ca5ff76af88bea2e232 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 17 Oct 2023 17:34:53 +0200 Subject: [PATCH 40/40] add comment --- tests/integration/double_vote.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index 4b862fbaaa..60fc0ce635 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -1,8 +1,6 @@ package integration import ( - "fmt" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" testutil "github.com/cosmos/interchain-security/v2/testutil/crypto" @@ -307,8 +305,8 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegationsAndRele // check that undelegations was successful ubds, _ := s.providerApp.GetTestStakingKeeper().GetUnbondingDelegation(s.providerCtx(), delAddr, validator.GetOperator()) + // should have a single entry since undelegations are merged s.Require().Len(ubds.Entries, 1) - fmt.Println(ubds.String()) // save the delegation shares of the validator to redelegate to // Note this shares should not be slashed!