From 42279c8c85dfa20ff25c62c5947466a2ec5ea12f Mon Sep 17 00:00:00 2001 From: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> Date: Thu, 1 Jun 2023 15:29:04 -0700 Subject: [PATCH 01/82] docs: cleanup changelog for v2.0.0 on release/v2.0.x (#987) * Update CHANGELOG.md * Update CHANGELOG.md * comment --- CHANGELOG.md | 71 +++--------------------------- x/ccv/provider/keeper/migration.go | 2 +- 2 files changed, 7 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 526d82baae..5efd479791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,14 @@ # CHANGELOG -## [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. - ## v.2.0.0 -Date: May 12th, 2023 +Date: June 1st, 2023 -Unlike prior releases, the ICS v2.0.0 release will be based on the main branch. v2.0.0 will contain all the accumulated PRs from the various releases below, along with other PRs that were merged, but not released to production. After v2.0.0, we plan to revamp release practices, and how we modularize the repo for consumer/provider. +Unlike prior releases, the ICS `v2.0.0` release will be based on the main branch. `v2.0.0` will contain all the accumulated PRs from the various releases below, along with other PRs that were merged, but not released to production. After `v2.0.0`, we plan to revamp release practices, and how we modularize the repo for consumer/provider. -Some PRs from v2.0.0 may reappear from other releases below. This is due to the fact that ICS v1.1.0 deviates from the commit ordering of the main branch, and other releases thereafter are based on v1.1.0. +Upgrading a provider from `v1.1.0-multiden` to `v2.0.0` will require state migrations. See [migration.go](./x/ccv/provider/keeper/migration.go). See the provider module's `ConsensusVersion` in [module](./x/ccv/provider/module.go) + +Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state migrations. See the consumer module's `ConsensusVersion` in [module](./x/ccv/consumer/module.go) ### High level changes included in v2.0.0 @@ -19,7 +17,7 @@ Some PRs from v2.0.0 may reappear from other releases below. This is due to the * Various fixes, critical and non-critical * Docs updates which should not affect production code -## PRs included in v2.0.0 +## Notable PRs included in v2.0.0 * (feat) v1->v2 migrations to accommodate a bugfix having to do with store keys, introduce new params, and deal with consumer genesis state schema changes [#975](https://github.com/cosmos/interchain-security/pull/975) * (deps) Bump github.com/cosmos/ibc-go/v4 from 4.4.0 to 4.4.2 [#982](https://github.com/cosmos/interchain-security/pull/982) @@ -52,63 +50,6 @@ Some PRs from v2.0.0 may reappear from other releases below. This is due to the * (api) Add consumer QueryParams [#746](https://github.com/cosmos/interchain-security/pull/746) * (feature) New validation for keeper fields [#740](https://github.com/cosmos/interchain-security/pull/740) -## v1.2.0-multiden - -The first release candidate for a fix built on top of v1.2.0, intended for consumers. This release adds a list of denoms on the consumer that are allowed to be sent to the provider as rewards. This prevents a potential DOS attack that was discovered during the audit of Replicated Security performed by Oak Security and funded by the Cosmos Hub community through Proposal 687. In an effort to move quickly, this release also includes a multisig fix that is effective only for provider. It shouldn't affect the consumer module. - -Note PRs were made in a private security repo. - -[full diff](https://github.com/cosmos/interchain-security/compare/v1.2.0...v1.2.0-multiden-rc0) - -## v1.1.0-multiden - -This release combines two fixes on top of v1.1.0, that we judged were urgent to get onto the Cosmos Hub before the launch of the first ICS consumer chain. This is an emergency release intended for providers. - -The first fix is to enable the use of multisigs and Ledger devices when assigning keys for consumer chains. The second is to prevent a possible DOS vector involving the reward distribution system. - -Note PRs were made in a private security repo. - -[full diff](https://github.com/cosmos/interchain-security/compare/v1.1.0...release/v1.1.0-multiden) - -### Multisig fix - -On April 25th (a week and a half ago), we began receiving reports that validators using multisigs and Ledger devices were getting errors reading Error: unable to resolve type URL /interchain_security.ccv.provider.v1.MsgAssignConsumerKey: tx parse error when attempting to assign consensus keys for consumer chains. - -We quickly narrowed the problem down to issues having to do with using the PubKey type directly in the MsgAssignConsumerKey transaction, and Amino (a deprecated serialization library still used in Ledger devices and multisigs) not being able to handle this. We attempted to fix this with the assistance of the Cosmos-SDK team, but after making no headway for a few days, we decided to simply use a JSON representation of the PubKey in the transaction. This is how it is usually represented anyway. We have verified that this fixes the problem. - -### Distribution fix - -The ICS distribution system works by allowing consumer chains to send rewards to a module address on the provider called the FeePoolAddress. From here they are automatically distributed to all validators and delegators through the distribution system that already exists to distribute staking rewards. The FeePoolAddress is usually blocked so that no tokens can be sent to it, but to enable ICS distribution we had to unblock it. - -We recently realized that unblocking the FeePoolAddress could enable an attacker to send a huge number of different denoms into the distribution system. The distribution system would then attempt to distribute them all, leading to out of memory errors. Fixing a similar attack vector that existed in the distribution system before ICS led us to this realization. - -To fix this problem, we have re-blocked the FeePoolAddress and created a new address called the ConsumerRewardsPool. Consumer chains now send rewards to this new address. There is also a new transaction type called RegisterConsumerRewardDenom. This transaction allows people to register denoms to be used as rewards from consumer chains. It costs 10 Atoms to run this transaction.The Atoms are transferred to the community pool. Only denoms registered with this command are then transferred to the FeePoolAddress and distributed out to delegators and validators. - -## v1.2.1 - -* (fix) Remove SPM [#812](https://github.com/cosmos/interchain-security/pull/812) -* (refactor) Key assignment type safety [#725](https://github.com/cosmos/interchain-security/pull/725) - -## v1.2.0 - -Date: April 13th, 2023 - -* (feat) Soft opt-out [#833](https://github.com/cosmos/interchain-security/pull/833) -* (fix) Correctly handle VSC packet with duplicate val updates on consumer [#846](https://github.com/cosmos/interchain-security/pull/846) -* (chore) bump: sdk v0.45.15-ics [#805](https://github.com/cosmos/interchain-security/pull/805) -* (api) add interchain security consumer QueryParams [#746](https://github.com/cosmos/interchain-security/pull/746) - -## v1.1.1 - -* (fix) Remove SPM [#812](https://github.com/cosmos/interchain-security/pull/812) -* (refactor) Key assignment type safety [#725](https://github.com/cosmos/interchain-security/pull/725) - -## v1.1.0 - -Date: March 24th, 2023 - -* (fix) StopConsumerChain not running in cachedContext [#802](https://github.com/cosmos/interchain-security/pull/802) - ## v1.0.0 Date: February 6th, 2023 diff --git a/x/ccv/provider/keeper/migration.go b/x/ccv/provider/keeper/migration.go index 1254faf463..4e899bcce2 100644 --- a/x/ccv/provider/keeper/migration.go +++ b/x/ccv/provider/keeper/migration.go @@ -22,7 +22,7 @@ func NewMigrator(ccvProviderKeeper Keeper, ccvProviderParamSpace paramtypes.Subs return Migrator{ccvProviderKeeper: ccvProviderKeeper, ccvProviderParamSpace: ccvProviderParamSpace} } -// Migratev1Tov2 migrates a provider from v1.0.0 to v2.0.0. +// Migratev1Tov2 migrates a provider from v1.0.0 to v2.0.0, and/or consensus version 1 -> 2. func (m Migrator) Migratev1Tov2(ctx sdk.Context) error { // Migrate params MigrateParamsv1Tov2(ctx, From 8ce740fa90c6c9a6c1fef48090c1eea77eb06040 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:37:07 -0700 Subject: [PATCH 02/82] chore: Hardcode golangci-lint version (backport #990) (#1013) chore: Hardcode golangci-lint version (#990) * Hardcode golangci-lint version * Hardcode version in CI config (cherry picked from commit 9920121274605ee5bf79984a687d7b4586fdec71) Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> --- .github/workflows/golangci-lint.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 7dcf88c9c0..28eba10b41 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -25,7 +25,7 @@ jobs: uses: golangci/golangci-lint-action@v3 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: latest + version: v1.52.2 # Optional: working directory, useful for monorepos # working-directory: somedir diff --git a/Makefile b/Makefile index 600c617d70..b84eca6524 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ test-no-cache: ### Linting ### ############################################################################### -golangci_version=latest +golangci_version=v1.52.2 lint: @echo "--> Running linter" From e8230bcabd3b334e41e8e3d7200e0e6e9209f8e6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:53:28 -0700 Subject: [PATCH 03/82] fix: proper consumer key prefix ordering (backport #991) (#1011) fix: proper consumer key prefix ordering (#991) * Update keys.go * tests * fix another bug * fix comments (cherry picked from commit a1e18d0cfe4cfc105e6bbb0730471075ee1edc28) Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> --- x/ccv/consumer/types/keys.go | 39 +++++++++++++++++-------------- x/ccv/consumer/types/keys_test.go | 20 +++++++++------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/x/ccv/consumer/types/keys.go b/x/ccv/consumer/types/keys.go index d52c9c551e..093f78b450 100644 --- a/x/ccv/consumer/types/keys.go +++ b/x/ccv/consumer/types/keys.go @@ -49,6 +49,23 @@ const ( // received over CCV channel but not yet flushed over ABCI PendingChangesByteKey + // NOTE: This prefix is depreciated, but left in place to avoid consumer state migrations + // [DEPRECATED] + PendingDataPacketsByteKey + + // PreCCVByteKey is the byte to store the consumer is running on democracy staking module without consumer + PreCCVByteKey + + // InitialValSetByteKey is the byte to store the initial validator set for a consumer + InitialValSetByteKey + + // NOTE: This prefix is depreciated, but left in place to avoid consumer state migrations + // [DEPRECATED] + LastStandaloneHeightByteKey + + // SmallestNonOptOutPowerByteKey is the byte that will store the smallest val power that cannot opt out + SmallestNonOptOutPowerByteKey + // HistoricalInfoKey is the byte prefix that will store the historical info for a given height HistoricalInfoBytePrefix @@ -61,30 +78,18 @@ const ( // OutstandingDowntimePrefix is the byte prefix that will store the validators outstanding downtime by consensus address OutstandingDowntimeBytePrefix - // NOTE: This prefix is depreciated, but left in place to avoid consumer state migrations - PendingDataPacketsBytePrefix - - // CrossChainValidatorPrefix is the byte prefix that will store cross-chain validators by consensus address - CrossChainValidatorBytePrefix - - // PendingDataPacketsByteKey is the byte key for storing + // PendingDataPacketsBytePrefix is the byte prefix for storing // a list of data packets that cannot be sent yet to the provider // chain either because the CCV channel is not established or // because the client is expired - PendingDataPacketsByteKey - - // PreCCVByteKey is the byte to store the consumer is running on democracy staking module without consumer - PreCCVByteKey + PendingDataPacketsBytePrefix - // InitialValSetByteKey is the byte to store the initial validator set for a consumer - InitialValSetByteKey + // CrossChainValidatorPrefix is the byte prefix that will store cross-chain validators by consensus address + CrossChainValidatorBytePrefix // InitGenesisHeightByteKey is the byte that will store the init genesis height InitGenesisHeightByteKey - // SmallestNonOptOutPowerByteKey is the byte that will store the smallest val power that cannot opt out - SmallestNonOptOutPowerByteKey - // StandaloneTransferChannelIDByteKey is the byte storing the channelID of transfer channel // that existed from a standalone chain changing over to a consumer StandaloneTransferChannelIDByteKey @@ -170,7 +175,7 @@ func CrossChainValidatorKey(addr []byte) []byte { // that cannot be sent yet to the provider chain either because the CCV channel // is not established or because the client is expired. func PendingDataPacketsKey() []byte { - return []byte{PendingDataPacketsByteKey} + return []byte{PendingDataPacketsBytePrefix} } func PreCCVKey() []byte { diff --git a/x/ccv/consumer/types/keys_test.go b/x/ccv/consumer/types/keys_test.go index c0ab13391a..a63da6f326 100644 --- a/x/ccv/consumer/types/keys_test.go +++ b/x/ccv/consumer/types/keys_test.go @@ -27,16 +27,18 @@ func getAllKeyPrefixes() []byte { ProviderClientByteKey, ProviderChannelByteKey, PendingChangesByteKey, + PendingDataPacketsByteKey, + PreCCVByteKey, + InitialValSetByteKey, + LastStandaloneHeightByteKey, + SmallestNonOptOutPowerByteKey, HistoricalInfoBytePrefix, PacketMaturityTimeBytePrefix, HeightValsetUpdateIDBytePrefix, OutstandingDowntimeBytePrefix, + PendingDataPacketsBytePrefix, CrossChainValidatorBytePrefix, - PendingDataPacketsByteKey, - PreCCVByteKey, - InitialValSetByteKey, InitGenesisHeightByteKey, - SmallestNonOptOutPowerByteKey, StandaloneTransferChannelIDByteKey, PrevStandaloneChainByteKey, } @@ -61,16 +63,18 @@ func getAllFullyDefinedKeys() [][]byte { ProviderClientIDKey(), ProviderChannelKey(), PendingChangesKey(), + // PendingDataPacketsKey() does not use duplicated prefix with value of 0x06 + PreCCVKey(), + InitialValSetKey(), + // LastStandaloneHeightKey() is depreciated + SmallestNonOptOutPowerKey(), HistoricalInfoKey(0), PacketMaturityTimeKey(0, time.Time{}), HeightValsetUpdateIDKey(0), OutstandingDowntimeKey([]byte{}), - CrossChainValidatorKey([]byte{}), PendingDataPacketsKey(), - PreCCVKey(), - InitialValSetKey(), + CrossChainValidatorKey([]byte{}), InitGenesisHeightKey(), - SmallestNonOptOutPowerKey(), StandaloneTransferChannelIDKey(), PrevStandaloneChainKey(), } From 42f916eba9293e4f87087f274d46151422c79ad0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 11:16:54 -0700 Subject: [PATCH 04/82] feat: Remove consumer genesis migration on provider (backport #997) (#1012) feat: Remove consumer genesis migration on provider (#997) * Update keys.go * tests * fix another bug * remove consumer genesis deletion, link to test * remove unused bond denom method * Revert "remove unused bond denom method" This reverts commit f930eca428bade49a05368fe5dae2d96842659cc. * remove test too * update changelog (cherry picked from commit e2ac9743e9a309b29e6e92ab64be2e4e79503f75) Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> --- CHANGELOG.md | 4 ++-- x/ccv/provider/keeper/migration.go | 14 ++++---------- x/ccv/provider/keeper/migration_test.go | 19 ------------------- 3 files changed, 6 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5efd479791..00e56c59d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,11 @@ Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state m ## Notable PRs included in v2.0.0 -* (feat) v1->v2 migrations to accommodate a bugfix having to do with store keys, introduce new params, and deal with consumer genesis state schema changes [#975](https://github.com/cosmos/interchain-security/pull/975) +* (fix) cosumer key prefix order to avoid complex migrations [#963](https://github.com/cosmos/interchain-security/pull/963) and [#991](https://github.com/cosmos/interchain-security/pull/991). The latter PR is the proper fix. +* (feat) v1->v2 migrations to accommodate a bugfix having to do with store keys, introduce new params, and deal with consumer genesis state schema changes [#975](https://github.com/cosmos/interchain-security/pull/975) and [#997](https://github.com/cosmos/interchain-security/pull/997) * (deps) Bump github.com/cosmos/ibc-go/v4 from 4.4.0 to 4.4.2 [#982](https://github.com/cosmos/interchain-security/pull/982) * (fix) partially revert key assignment type safety PR [#980](https://github.com/cosmos/interchain-security/pull/980) * (fix) Remove panics on failure to send IBC packets [#876](https://github.com/cosmos/interchain-security/pull/876) -* (fix) consumer key prefix order to avoid complex migrations [#963](https://github.com/cosmos/interchain-security/pull/963) * (fix) Prevent denom DOS [#931](https://github.com/cosmos/interchain-security/pull/931) * (fix) multisig for assigning consumer key, use json [#916](https://github.com/cosmos/interchain-security/pull/916) * (deps) Bump github.com/cosmos/ibc-go/v4 from 4.3.0 to 4.4.0 [#902](https://github.com/cosmos/interchain-security/pull/902) diff --git a/x/ccv/provider/keeper/migration.go b/x/ccv/provider/keeper/migration.go index 4e899bcce2..d33fcb7968 100644 --- a/x/ccv/provider/keeper/migration.go +++ b/x/ccv/provider/keeper/migration.go @@ -33,8 +33,10 @@ func (m Migrator) Migratev1Tov2(ctx sdk.Context) error { sdk.NewCoin(m.ccvProviderKeeper.BondDenom(ctx), sdk.NewInt(10000000)), ) - // Delete select consumer genesis states for consumers that're launched - MigrateConsumerGenesisStatesv1Tov2(ctx, m.ccvProviderKeeper) + // Consumer genesis states persisted on the provider do not need to be migrated, + // as protobuf serialization is able to gracefully handle unpopulated fields when deserializing. + // See https://github.com/smarshall-spitzbart/ics-migration-tests/commit/b589e3982c26783ed66e954051f7da1ead16de68 + // which passes, proving the addition of preCCV will not break things. // Migrate keys to accommodate fix from https://github.com/cosmos/interchain-security/pull/786 MigrateKeysv1Tov2(ctx, m.ccvProviderKeeper) @@ -80,14 +82,6 @@ func MigrateParamsv1Tov2(ctx sdk.Context, paramsSubspace paramtypes.Subspace, co paramsSubspace.SetParamSet(ctx, &newParams) } -func MigrateConsumerGenesisStatesv1Tov2(ctx sdk.Context, providerKeeper Keeper) { - // We could try to migrate existing ConsumerGenesisStates, but they're not needed after consumer launch. - // Hence we delete them strategically. - providerKeeper.DeleteConsumerGenesis(ctx, "neutron-1") // See https://github.com/neutron-org/mainnet-assets#parameters - - // TODO: determine if any other ConsumerGenesisStates need to be deleted, or actually migrated! -} - // Due to https://github.com/cosmos/interchain-security/pull/786, // validators' slash logs are stored under the key prefix for slash acks. // This method will extract "slash logs" from the slash acks part of the store, and put the slash logs diff --git a/x/ccv/provider/keeper/migration_test.go b/x/ccv/provider/keeper/migration_test.go index 858aa193e6..e488dd2d36 100644 --- a/x/ccv/provider/keeper/migration_test.go +++ b/x/ccv/provider/keeper/migration_test.go @@ -13,7 +13,6 @@ import ( types2 "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" "github.com/cosmos/interchain-security/v2/testutil/crypto" testutil "github.com/cosmos/interchain-security/v2/testutil/keeper" - consumertypes "github.com/cosmos/interchain-security/v2/x/ccv/consumer/types" providerkeeper "github.com/cosmos/interchain-security/v2/x/ccv/provider/keeper" providertypes "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" ccvtypes "github.com/cosmos/interchain-security/v2/x/ccv/types" @@ -124,24 +123,6 @@ type v1Params struct { MaxThrottledPackets int64 `protobuf:"varint,8,opt,name=max_throttled_packets,json=maxThrottledPackets,proto3" json:"max_throttled_packets,omitempty"` } -func TestMigrateConsumerGenesisv1Tov2(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testutil.GetProviderKeeperAndCtx(t, testutil.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - _, found := providerKeeper.GetConsumerGenesis(ctx, "neutron-1") - require.False(t, found) - - providerKeeper.SetConsumerGenesis(ctx, "neutron-1", consumertypes.GenesisState{}) - - _, found = providerKeeper.GetConsumerGenesis(ctx, "neutron-1") - require.True(t, found) - - providerkeeper.MigrateConsumerGenesisStatesv1Tov2(ctx, providerKeeper) - - _, found = providerKeeper.GetConsumerGenesis(ctx, "neutron-1") - require.False(t, found) -} - func TestMigrateKeysv1Tov2(t *testing.T) { providerKeeper, ctx, ctrl, _ := testutil.GetProviderKeeperAndCtx(t, testutil.NewInMemKeeperParams(t)) defer ctrl.Finish() From 2395b6f951101970e26e509613f7a63ca9ded65d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 16:09:29 -0700 Subject: [PATCH 05/82] fix: limit vsc matured packets handled per endblocker (backport #1004) (#1015) fix: limit vsc matured packets handled per endblocker (#1004) * initial implementation, still need tests * UTs * integration test * linter * Update CHANGELOG.md * make vsc matured handled this block a var * comment (cherry picked from commit 8c2fc562dedecee7799ac69bd72734c37a356638) Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> --- CHANGELOG.md | 1 + tests/integration/throttle.go | 85 ++++++++++++++++++++++++-- testutil/integration/debug_test.go | 4 ++ x/ccv/provider/keeper/relay.go | 22 +++++-- x/ccv/provider/keeper/throttle.go | 30 +++++++-- x/ccv/provider/keeper/throttle_test.go | 2 +- x/ccv/provider/types/keys.go | 8 +++ x/ccv/provider/types/keys_test.go | 2 + 8 files changed, 140 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00e56c59d6..e74438f99c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state m ## Notable PRs included in v2.0.0 +* (feat/fix) limit vsc matured packets handled per endblocker [#1004](https://github.com/cosmos/interchain-security/pull/1004) * (fix) cosumer key prefix order to avoid complex migrations [#963](https://github.com/cosmos/interchain-security/pull/963) and [#991](https://github.com/cosmos/interchain-security/pull/991). The latter PR is the proper fix. * (feat) v1->v2 migrations to accommodate a bugfix having to do with store keys, introduce new params, and deal with consumer genesis state schema changes [#975](https://github.com/cosmos/interchain-security/pull/975) and [#997](https://github.com/cosmos/interchain-security/pull/997) * (deps) Bump github.com/cosmos/ibc-go/v4 from 4.4.0 to 4.4.2 [#982](https://github.com/cosmos/interchain-security/pull/982) diff --git a/tests/integration/throttle.go b/tests/integration/throttle.go index c2ef98a4ae..b2dc390ea1 100644 --- a/tests/integration/throttle.go +++ b/tests/integration/throttle.go @@ -12,6 +12,8 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) +const fullSlashMeterString = "1.0" + // TestBasicSlashPacketThrottling tests slash packet throttling with a single consumer, // two slash packets, and no VSC matured packets. The most basic scenario. func (s *CCVTestSuite) TestBasicSlashPacketThrottling() { @@ -651,7 +653,7 @@ func (s *CCVTestSuite) TestSlashSameValidator() { // Set replenish fraction to 1.0 so that all sent packets should handled immediately (no throttling) params := providerKeeper.GetParams(s.providerCtx()) - params.SlashMeterReplenishFraction = "1.0" + params.SlashMeterReplenishFraction = fullSlashMeterString // needs to be const for linter providerKeeper.SetParams(s.providerCtx(), params) providerKeeper.InitializeSlashMeter(s.providerCtx()) @@ -706,7 +708,7 @@ func (s CCVTestSuite) TestSlashAllValidators() { //nolint:govet // this is a tes // Set replenish fraction to 1.0 so that all sent packets should be handled immediately (no throttling) params := providerKeeper.GetParams(s.providerCtx()) - params.SlashMeterReplenishFraction = "1.0" + params.SlashMeterReplenishFraction = fullSlashMeterString // needs to be const for linter providerKeeper.SetParams(s.providerCtx(), params) providerKeeper.InitializeSlashMeter(s.providerCtx()) @@ -779,7 +781,7 @@ func (s *CCVTestSuite) TestLeadingVSCMaturedAreDequeued() { // Queue up 50 slash packets for each consumer for _, bundle := range s.consumerBundles { - for i := 0; i < 50; i++ { + for i := 50; i < 100; i++ { ibcSeqNum := uint64(i) packet := s.constructSlashPacketFromConsumer(*bundle, *s.providerChain.Vals.Validators[0], stakingtypes.Downtime, ibcSeqNum) @@ -792,7 +794,7 @@ func (s *CCVTestSuite) TestLeadingVSCMaturedAreDequeued() { // Queue up another 50 vsc matured packets for each consumer for _, bundle := range s.consumerBundles { - for i := 0; i < 50; i++ { + for i := 100; i < 150; i++ { ibcSeqNum := uint64(i) packet := s.constructVSCMaturedPacketFromConsumer(*bundle, ibcSeqNum) packetData := ccvtypes.ConsumerPacketData{} @@ -818,6 +820,10 @@ func (s *CCVTestSuite) TestLeadingVSCMaturedAreDequeued() { providerKeeper.SetSlashMeterReplenishTimeCandidate(s.providerCtx()) // Execute end blocker to dequeue only the leading vsc matured packets. + // Note we must call the end blocker three times, since only 100 vsc matured packets can be handled + // each block, and we have 5*50=250 total. + s.providerChain.NextBlock() + s.providerChain.NextBlock() s.providerChain.NextBlock() // Confirm queue size is 100 for each consumer-specific queue (50 leading vsc matured are dequeued). @@ -827,9 +833,80 @@ func (s *CCVTestSuite) TestLeadingVSCMaturedAreDequeued() { } // No slash packets handled, global slash queue is same size as last block. + globalEntries = providerKeeper.GetAllGlobalSlashEntries(s.providerCtx()) + s.Require().Equal(len(globalEntries), 50*5) + + // No slash packets handled even if we call end blocker a couple more times. + s.providerChain.NextBlock() + s.providerChain.NextBlock() + globalEntries = providerKeeper.GetAllGlobalSlashEntries(s.providerCtx()) s.Require().Equal(len(globalEntries), 50*5) } +// TestVscMaturedHandledPerBlockLimit tests that only 100 vsc matured packets are handled per block, +// specifically from HandleThrottleQueues(). +// +// Note the vsc matured per block limit is also tested in, TestLeadingVSCMaturedAreDequeued, +// specifically in the context of HandleLeadingVSCMaturedPackets(). +func (s *CCVTestSuite) TestVscMaturedHandledPerBlockLimit() { + s.SetupAllCCVChannels() + providerKeeper := s.providerApp.GetProviderKeeper() + + // Set replenish fraction to 1.0 so that all sent packets should be handled immediately (no jail throttling) + params := providerKeeper.GetParams(s.providerCtx()) + params.SlashMeterReplenishFraction = fullSlashMeterString // needs to be const for linter + providerKeeper.SetParams(s.providerCtx(), params) + providerKeeper.InitializeSlashMeter(s.providerCtx()) + + // Queue up 100 vsc matured packets for each consumer + for _, bundle := range s.consumerBundles { + for i := 0; i < 100; i++ { + ibcSeqNum := uint64(i) + packet := s.constructVSCMaturedPacketFromConsumer(*bundle, ibcSeqNum) + packetData := ccvtypes.ConsumerPacketData{} + ccvtypes.ModuleCdc.MustUnmarshalJSON(packet.GetData(), &packetData) + providerKeeper.OnRecvVSCMaturedPacket(s.providerCtx(), + packet, *packetData.GetVscMaturedPacketData()) + } + } + + // Queue up 50 slash packets for each consumer, with new IBC sequence numbers + for _, bundle := range s.consumerBundles { + for i := 100; i < 150; i++ { + ibcSeqNum := uint64(i) + packet := s.constructSlashPacketFromConsumer(*bundle, + *s.providerChain.Vals.Validators[0], stakingtypes.Downtime, ibcSeqNum) + packetData := ccvtypes.ConsumerPacketData{} + ccvtypes.ModuleCdc.MustUnmarshalJSON(packet.GetData(), &packetData) + providerKeeper.OnRecvSlashPacket(s.providerCtx(), + packet, *packetData.GetSlashPacketData()) + } + } + + // Confirm queue size is 150 for each consumer-specific queue. + for _, bundle := range s.consumerBundles { + s.Require().Equal(uint64(150), + providerKeeper.GetThrottledPacketDataSize(s.providerCtx(), bundle.Chain.ChainID)) + } + // Confirm global queue size is 50 * 5 (50 slash packets for each of 5 consumers) + globalEntries := providerKeeper.GetAllGlobalSlashEntries(s.providerCtx()) + s.Require().Equal(len(globalEntries), 50*5) + + // Note even though there is no jail throttling active, slash packets will not be handled until + // all of the leading vsc matured packets are handled first. This should take 5 blocks. + for i := 0; i < 5; i++ { + s.providerChain.NextBlock() + s.Require().Len(providerKeeper.GetAllGlobalSlashEntries(s.providerCtx()), 250) // global entries remain same size + } + + // Set signing info for val to be jailed, preventing panic + s.setDefaultValSigningInfo(*s.providerChain.Vals.Validators[0]) + + // Now we execute one more block and all 250 of the slash packets should be handled. + s.providerChain.NextBlock() + s.Require().Empty(providerKeeper.GetAllGlobalSlashEntries(s.providerCtx())) // empty global entries = all slash packets handled +} + func (s *CCVTestSuite) confirmValidatorJailed(tmVal tmtypes.Validator, checkPower bool) { sdkVal, found := s.providerApp.GetTestStakingKeeper().GetValidator( s.providerCtx(), sdk.ValAddress(tmVal.Address)) diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index 95190ba8a0..b6456663a9 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -205,6 +205,10 @@ func TestLeadingVSCMaturedAreDequeued(t *testing.T) { runCCVTestByName(t, "TestLeadingVSCMaturedAreDequeued") } +func TestVscMaturedHandledPerBlockLimit(t *testing.T) { + runCCVTestByName(t, "TestVscMaturedHandledPerBlockLimit") +} + // // Unbonding tests // diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 1d93b2fdd9..ea07e0e9af 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -53,14 +53,25 @@ func (k Keeper) OnRecvVSCMaturedPacket( // the "VSC Maturity and Slashing Order" CCV property. If VSC matured packet data DOES NOT // trail slash packet data for that consumer, it will be handled in this method, // bypassing HandleThrottleQueues. -func (k Keeper) HandleLeadingVSCMaturedPackets(ctx sdk.Context) { +func (k Keeper) HandleLeadingVSCMaturedPackets(ctx sdk.Context) (vscMaturedHandledThisBlock int) { + vscMaturedHandledThisBlock = 0 for _, chain := range k.GetAllConsumerChains(ctx) { + // Note: it's assumed the order of the vsc matured slice matches the order of the ibc seq nums slice, + // in that a vsc matured packet data at index i is associated with the ibc seq num at index i. leadingVscMatured, ibcSeqNums := k.GetLeadingVSCMaturedData(ctx, chain.ChainId) - for _, data := range leadingVscMatured { + ibcSeqNumsHandled := []uint64{} + for idx, data := range leadingVscMatured { + if vscMaturedHandledThisBlock >= vscMaturedHandledPerBlockLimit { + // Break from inner for-loop, DeleteThrottledPacketData will still be called for this consumer + break + } k.HandleVSCMaturedPacket(ctx, chain.ChainId, data) + vscMaturedHandledThisBlock++ + ibcSeqNumsHandled = append(ibcSeqNumsHandled, ibcSeqNums[idx]) } - k.DeleteThrottledPacketData(ctx, chain.ChainId, ibcSeqNums...) + k.DeleteThrottledPacketData(ctx, chain.ChainId, ibcSeqNumsHandled...) } + return vscMaturedHandledThisBlock } // HandleVSCMaturedPacket handles a VSCMatured packet. @@ -267,13 +278,14 @@ func (k Keeper) EndBlockCIS(ctx sdk.Context) { // - Marshaling and/or store corruption errors. // - Setting invalid slash meter values (see SetSlashMeter). k.CheckForSlashMeterReplenishment(ctx) + // Handle leading vsc matured packets before throttling logic. // // Note: HandleLeadingVSCMaturedPackets contains panics for the following scenarios, any of which should never occur // if the protocol is correct and external data is properly validated: // // - Marshaling and/or store corruption errors. - k.HandleLeadingVSCMaturedPackets(ctx) + vscMaturedHandledThisBlock := k.HandleLeadingVSCMaturedPackets(ctx) // Handle queue entries considering throttling logic. // // Note: HandleThrottleQueues contains panics for the following scenarios, any of which should never occur @@ -282,7 +294,7 @@ func (k Keeper) EndBlockCIS(ctx sdk.Context) { // - SlashMeter has not been set (which should be set in InitGenesis, see InitializeSlashMeter). // - Marshaling and/or store corruption errors. // - Setting invalid slash meter values (see SetSlashMeter). - k.HandleThrottleQueues(ctx) + k.HandleThrottleQueues(ctx, vscMaturedHandledThisBlock) } // OnRecvSlashPacket delivers a received slash packet, validates it and diff --git a/x/ccv/provider/keeper/throttle.go b/x/ccv/provider/keeper/throttle.go index 704a2f6d19..d832125bfb 100644 --- a/x/ccv/provider/keeper/throttle.go +++ b/x/ccv/provider/keeper/throttle.go @@ -12,16 +12,25 @@ import ( // This file contains functionality relevant to the throttling of slash and vsc matured packets, aka circuit breaker logic. +const vscMaturedHandledPerBlockLimit = 100 + // HandleThrottleQueues iterates over the global slash entry queue, and // handles all or some portion of throttled (slash and/or VSC matured) packet data received from // consumer chains. The slash meter is decremented appropriately in this method. -func (k Keeper) HandleThrottleQueues(ctx sdktypes.Context) { +func (k Keeper) HandleThrottleQueues(ctx sdktypes.Context, vscMaturedHandledThisBlock int) { meter := k.GetSlashMeter(ctx) // Return if meter is negative in value if meter.IsNegative() { return } + // Return if vsc matured handle limit was already reached this block, during HandleLeadingVSCMaturedPackets. + // It makes no practical difference for throttling logic to execute next block. + // By doing this, we assure that all leading vsc matured packets were handled before any slash packets. + if vscMaturedHandledThisBlock >= vscMaturedHandledPerBlockLimit { + return + } + // Obtain all global slash entries, where only some of them may be handled in this method, // depending on the value of the slash meter. allEntries := k.GetAllGlobalSlashEntries(ctx) @@ -35,7 +44,7 @@ func (k Keeper) HandleThrottleQueues(ctx sdktypes.Context) { // Handle one slash and any trailing vsc matured packet data instances by passing in // chainID and appropriate callbacks, relevant packet data is deleted in this method. - k.HandlePacketDataForChain(ctx, globalEntry.ConsumerChainID, k.HandleSlashPacket, k.HandleVSCMaturedPacket) + k.HandlePacketDataForChain(ctx, globalEntry.ConsumerChainID, k.HandleSlashPacket, k.HandleVSCMaturedPacket, vscMaturedHandledThisBlock) handledEntries = append(handledEntries, globalEntry) // don't handle any more global entries if meter becomes negative in value @@ -82,18 +91,31 @@ func (k Keeper) GetEffectiveValPower(ctx sdktypes.Context, func (k Keeper) HandlePacketDataForChain(ctx sdktypes.Context, consumerChainID string, slashPacketHandler func(sdktypes.Context, string, ccvtypes.SlashPacketData), vscMaturedPacketHandler func(sdktypes.Context, string, ccvtypes.VSCMaturedPacketData), + vscMaturedHandledThisBlock int, ) { // Get slash packet data and trailing vsc matured packet data, handle it all. slashFound, slashData, vscMaturedData, seqNums := k.GetSlashAndTrailingData(ctx, consumerChainID) + seqNumsHandled := []uint64{} if slashFound { slashPacketHandler(ctx, consumerChainID, slashData) + // Due to HandleLeadingVSCMaturedPackets() running before HandleThrottleQueues(), and the fact that + // HandleThrottleQueues() will return until all leading vsc matured have been handled, a slash packet + // should always be the first packet in the queue. So we can safely append the first seqNum here. + seqNumsHandled = append(seqNumsHandled, seqNums[0]) } - for _, vscMData := range vscMaturedData { + for idx, vscMData := range vscMaturedData { + if vscMaturedHandledThisBlock >= vscMaturedHandledPerBlockLimit { + // Break from for-loop, DeleteThrottledPacketData will still be called for this consumer + break + } vscMaturedPacketHandler(ctx, consumerChainID, vscMData) + vscMaturedHandledThisBlock++ + // Append seq num for this vsc matured packet + seqNumsHandled = append(seqNumsHandled, seqNums[idx+1]) // Note idx+1, since slash packet is at index 0 } // Delete handled data after it has all been handled. - k.DeleteThrottledPacketData(ctx, consumerChainID, seqNums...) + k.DeleteThrottledPacketData(ctx, consumerChainID, seqNumsHandled...) } // InitializeSlashMeter initializes the slash meter to it's max value (also its allowance), diff --git a/x/ccv/provider/keeper/throttle_test.go b/x/ccv/provider/keeper/throttle_test.go index 3be597e28a..3b93523d4b 100644 --- a/x/ccv/provider/keeper/throttle_test.go +++ b/x/ccv/provider/keeper/throttle_test.go @@ -134,7 +134,7 @@ func TestHandlePacketDataForChain(t *testing.T) { handledData = append(handledData, data) } - providerKeeper.HandlePacketDataForChain(ctx, tc.chainID, slashHandleCounter, vscMaturedHandleCounter) + providerKeeper.HandlePacketDataForChain(ctx, tc.chainID, slashHandleCounter, vscMaturedHandleCounter, 0) // Assert number of handled data instances matches expected number require.Equal(t, len(tc.expectedHandledIndexes), len(handledData)) diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index bb2ffcc881..4e15f566ef 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -134,6 +134,10 @@ const ( // ConsumerRewardDenomsBytePrefix is the byte prefix that will store a list of consumer reward denoms ConsumerRewardDenomsBytePrefix + // VSCMaturedHandledThisBlockBytePrefix is the byte prefix storing the number of vsc matured packets + // handled in the current block + VSCMaturedHandledThisBlockBytePrefix + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -476,6 +480,10 @@ func ParseChainIdAndConsAddrKey(prefix byte, bz []byte) (string, sdk.ConsAddress return chainID, addr, nil } +func VSCMaturedHandledThisBlockKey() []byte { + return []byte{VSCMaturedHandledThisBlockBytePrefix} +} + // // End of generic helpers section // diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index d28232c6a2..03493c1138 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -51,6 +51,7 @@ func getAllKeyPrefixes() []byte { providertypes.KeyAssignmentReplacementsBytePrefix, providertypes.ConsumerAddrsToPruneBytePrefix, providertypes.SlashLogBytePrefix, + providertypes.VSCMaturedHandledThisBlockBytePrefix, } } @@ -94,6 +95,7 @@ func getAllFullyDefinedKeys() [][]byte { providertypes.KeyAssignmentReplacementsKey("chainID", providertypes.NewProviderConsAddress([]byte{0x05})), providertypes.ConsumerAddrsToPruneKey("chainID", 88), providertypes.SlashLogKey(providertypes.NewProviderConsAddress([]byte{0x05})), + providertypes.VSCMaturedHandledThisBlockKey(), } } From 163da4e32518f995498c6cef6d11a5c9189aec0c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 16 Jun 2023 11:51:18 -0700 Subject: [PATCH 06/82] feat: integrate cometmock (backport #989) (#1030) feat: integrate cometmock (#989) * Add gorelayer and CometMock to Dockerfile * Add option to start with cometmock in start-chain script * Start adding support for rly * Adjust relayer start action * Add entrypoint for short happy path steps * Add . nosec G204 and waiting for blocks * Adjust rly config: Gas is free * Remove optout steps from short happy path * Use separate redelegate step for short happy path * Wait for blocks after unbonding * Make naming more descriptive and add comments * Add comment to chain name sorting and improve comments * Update start-chain.sh Address comments form joint review session with @MSalopek * Fix typo (cherry picked from commit 07be71aa678a0ad204c62a6eb6033a526c0bef27) Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> --- Dockerfile | 8 + tests/e2e/actions.go | 286 +++++++++++++++++- tests/e2e/config.go | 10 + tests/e2e/main.go | 24 +- tests/e2e/state.go | 5 + tests/e2e/step_delegation.go | 65 +++- tests/e2e/steps.go | 12 +- tests/e2e/steps_democracy.go | 9 +- tests/e2e/steps_double_sign.go | 9 +- tests/e2e/steps_downtime.go | 24 +- tests/e2e/steps_multi_consumer_delegation.go | 18 +- tests/e2e/steps_multi_consumer_double_sign.go | 15 +- tests/e2e/steps_multi_consumer_downtime.go | 27 +- tests/e2e/steps_reward_denom.go | 9 +- tests/e2e/steps_start_chains.go | 3 +- tests/e2e/steps_stop_chain.go | 6 +- .../e2e/steps_submit_equivocation_proposal.go | 3 +- tests/e2e/testnet-scripts/start-chain.sh | 65 +++- 18 files changed, 534 insertions(+), 64 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0da9141620..ac528fa8fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,12 +30,20 @@ RUN make install # Get Hermes build FROM ghcr.io/informalsystems/hermes:1.4.1 AS hermes-builder +# Get CometMock +FROM informalofftermatt/cometmock:latest as cometmock-builder + +# Get GoRelayer +FROM informalofftermatt/gorelayer:nogas AS gorelayer-builder + 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 COPY --from=hermes-builder /usr/bin/hermes /usr/local/bin/ +COPY --from=cometmock-builder /usr/local/bin/cometmock /usr/local/bin/cometmock +COPY --from=gorelayer-builder /bin/rly /usr/local/bin/ COPY --from=is-builder /go/bin/interchain-security-pd /usr/local/bin/interchain-security-pd COPY --from=is-builder /go/bin/interchain-security-cd /usr/local/bin/interchain-security-cd diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 4a4c471f79..b1e6863b7a 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -122,6 +122,13 @@ func (tr TestRun) startChain( genesisChanges = chainConfig.genesisChanges } + var cometmockArg string + if tr.useCometmock { + cometmockArg = "true" + } else { + cometmockArg = "false" + } + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash", "/testnet-scripts/start-chain.sh", chainConfig.binaryName, string(vals), @@ -132,6 +139,7 @@ func (tr TestRun) startChain( // lower timeout_commit means the blocks are produced faster making the test run shorter // with short timeout_commit (eg. timeout_commit = 1s) some nodes may miss blocks causing the test run to fail tr.tendermintConfigOverride, + cometmockArg, ) cmdReader, err := cmd.StdoutPipe() @@ -587,9 +595,80 @@ websocket_addr = "%s" numerator = "1" ` +// Set up the config for a new chain for gorelayer. +// This config is added to the container as a file. +// We then add the chain to the relayer, using this config as the chain config with `rly chains add --file` +// This is functionally similar to the config used by Hermes for chains, e.g. gas is free. +const gorelayerChainConfigTemplate = ` +{ + "type": "cosmos", + "value": { + "key": "default", + "chain-id": "%s", + "rpc-addr": "%s", + "account-prefix": "cosmos", + "keyring-backend": "test", + "gas-adjustment": 1.2, + "gas-prices": "0.00stake", + "debug": true, + "timeout": "20s", + "output-format": "json", + "sign-mode": "direct" + } +}` + func (tr TestRun) addChainToRelayer( action addChainToRelayerAction, verbose bool, +) { + if !tr.useGorelayer { + tr.addChainToHermes(action, verbose) + } else { + tr.addChainToGorelayer(action, verbose) + } +} + +func (tr TestRun) addChainToGorelayer( + action addChainToRelayerAction, + verbose bool, +) { + queryNodeIP := tr.getQueryNodeIP(action.chain) + chainId := tr.chainConfigs[action.chain].chainId + rpcAddr := "http://" + queryNodeIP + ":26658" + + chainConfig := fmt.Sprintf(gorelayerChainConfigTemplate, + chainId, + rpcAddr, + ) + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + bz, err := exec.Command("docker", "exec", tr.containerConfig.instanceName, "rly", "config", "init").CombinedOutput() + if err != nil && !strings.Contains(string(bz), "config already exists") { + log.Fatal(err, "\n", string(bz)) + } + + chainConfigFileName := fmt.Sprintf("/root/%s_config.json", chainId) + + bashCommand := fmt.Sprintf(`echo '%s' >> %s`, chainConfig, chainConfigFileName) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + bz, err = exec.Command("docker", "exec", tr.containerConfig.instanceName, "bash", "-c", + bashCommand).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + addChainCommand := exec.Command("docker", "exec", tr.containerConfig.instanceName, "rly", "chains", "add", "--file", chainConfigFileName, string(chainId)) + executeCommand(addChainCommand, "add chain") + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + keyRestoreCommand := exec.Command("docker", "exec", tr.containerConfig.instanceName, "rly", "keys", "restore", string(chainId), "default", tr.validatorConfigs[action.validator].mnemonic) + executeCommand(keyRestoreCommand, "restore keys") +} + +func (tr TestRun) addChainToHermes( + action addChainToRelayerAction, + verbose bool, ) { queryNodeIP := tr.getQueryNodeIP(action.chain) chainId := tr.chainConfigs[action.chain].chainId @@ -638,6 +717,26 @@ func (tr TestRun) addChainToRelayer( } } +// This config file is used by gorelayer to create a path between chains. +// Since the tests assume we use a certain client-id for certain paths, +// in the config we specify the client id, e.g. 07-tendermint-5. +// The src-channel-filter is empty because we want to relay all channels. +const gorelayerPathConfigTemplate = `{ + "src": { + "chain-id": "%s", + "client-id": "07-tendermint-%v" + }, + "dst": { + "chain-id": "%s", + "client-id": "07-tendermint-%v" + }, + "src-channel-filter": { + "rule": "", + "channel-list": [] + } +} +` + type addIbcConnectionAction struct { chainA chainID chainB chainID @@ -648,6 +747,67 @@ type addIbcConnectionAction struct { func (tr TestRun) addIbcConnection( action addIbcConnectionAction, verbose bool, +) { + if !tr.useGorelayer { + tr.addIbcConnectionHermes(action, verbose) + } else { + tr.addIbcConnectionGorelayer(action, verbose) + } +} + +func (tr TestRun) addIbcConnectionGorelayer( + action addIbcConnectionAction, + verbose bool, +) { + pathName := tr.GetPathNameForGorelayer(action.chainA, action.chainB) + + pathConfig := fmt.Sprintf(gorelayerPathConfigTemplate, action.chainA, action.clientA, action.chainB, action.clientB) + + pathConfigFileName := fmt.Sprintf("/root/%s_config.json", pathName) + + bashCommand := fmt.Sprintf(`echo '%s' >> %s`, pathConfig, pathConfigFileName) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + pathConfigCommand := exec.Command("docker", "exec", tr.containerConfig.instanceName, "bash", "-c", + bashCommand) + executeCommand(pathConfigCommand, "add path config") + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + newPathCommand := exec.Command("docker", "exec", tr.containerConfig.instanceName, "rly", + "paths", "add", + string(tr.chainConfigs[action.chainA].chainId), + string(tr.chainConfigs[action.chainB].chainId), + pathName, + "--file", pathConfigFileName, + ) + + executeCommand(newPathCommand, "new path") + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + newClientsCommand := exec.Command("docker", "exec", tr.containerConfig.instanceName, "rly", + "transact", "clients", + pathName, + ) + + executeCommand(newClientsCommand, "new clients") + + tr.waitBlocks(action.chainA, 1, 10*time.Second) + tr.waitBlocks(action.chainB, 1, 10*time.Second) + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + newConnectionCommand := exec.Command("docker", "exec", tr.containerConfig.instanceName, "rly", + "transact", "connection", + pathName, + ) + + executeCommand(newConnectionCommand, "new connection") + + tr.waitBlocks(action.chainA, 1, 10*time.Second) + tr.waitBlocks(action.chainB, 1, 10*time.Second) +} + +func (tr TestRun) addIbcConnectionHermes( + action addIbcConnectionAction, + verbose bool, ) { //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", @@ -692,10 +852,40 @@ type addIbcChannelAction struct { order string } -type startHermesAction struct{} +type startRelayerAction struct{} + +func (tr TestRun) startRelayer( + action startRelayerAction, + verbose bool, +) { + if tr.useGorelayer { + tr.startGorelayer(action, verbose) + } else { + tr.startHermes(action, verbose) + } +} + +func (tr TestRun) startGorelayer( + action startRelayerAction, + verbose bool, +) { + // gorelayer start is running in detached mode + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", "-d", tr.containerConfig.instanceName, "rly", + "start", + ) + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + if verbose { + fmt.Println("started gorelayer") + } +} func (tr TestRun) startHermes( - action startHermesAction, + action startRelayerAction, verbose bool, ) { // hermes start is running in detached mode @@ -716,6 +906,35 @@ func (tr TestRun) startHermes( func (tr TestRun) addIbcChannel( action addIbcChannelAction, verbose bool, +) { + if tr.useGorelayer { + tr.addIbcChannelGorelayer(action, verbose) + } else { + tr.addIbcChannelHermes(action, verbose) + } +} + +func (tr TestRun) addIbcChannelGorelayer( + action addIbcChannelAction, + verbose bool, +) { + pathName := tr.GetPathNameForGorelayer(action.chainA, action.chainB) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "rly", + "transact", "channel", + pathName, + "--src-port", action.portA, + "--dst-port", action.portB, + "--version", tr.containerConfig.ccvVersion, + "--order", action.order, + "--debug", + ) + executeCommand(cmd, "addChannel") +} + +func (tr TestRun) addIbcChannelHermes( + action addIbcChannelAction, + verbose bool, ) { //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", @@ -773,6 +992,10 @@ func (tr TestRun) transferChannelComplete( action transferChannelCompleteAction, verbose bool, ) { + if tr.useGorelayer { + log.Fatal("transferChannelComplete is not implemented for rly") + } + //#nosec G204 -- Bypass linter warning for spawning subprocess with chanOpenTryCmd arguments. chanOpenTryCmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", "tx", "chan-open-try", @@ -841,7 +1064,8 @@ func executeCommand(cmd *exec.Cmd, cmdName string) { } type relayPacketsAction struct { - chain chainID + chainA chainID + chainB chainID port string channel uint } @@ -849,11 +1073,43 @@ type relayPacketsAction struct { func (tr TestRun) relayPackets( action relayPacketsAction, verbose bool, +) { + if tr.useGorelayer { + tr.relayPacketsGorelayer(action, verbose) + } else { + tr.relayPacketsHermes(action, verbose) + } +} + +func (tr TestRun) relayPacketsGorelayer( + action relayPacketsAction, + verbose bool, +) { + pathName := tr.GetPathNameForGorelayer(action.chainA, action.chainB) + + // rly transact relay-packets [path-name] --channel [channel-id] + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "rly", "transact", "flush", + pathName, + "channel-"+fmt.Sprint(action.channel), + ) + if verbose { + log.Println("relayPackets cmd:", cmd.String()) + } + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } +} + +func (tr TestRun) relayPacketsHermes( + action relayPacketsAction, + 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", "clear", "packets", - "--chain", string(tr.chainConfigs[action.chain].chainId), + "--chain", string(tr.chainConfigs[action.chainA].chainId), "--port", action.port, "--channel", "channel-"+fmt.Sprint(action.channel), ) @@ -866,7 +1122,7 @@ func (tr TestRun) relayPackets( log.Fatal(err, "\n", string(bz)) } - tr.waitBlocks(action.chain, 1, 30*time.Second) + tr.waitBlocks(action.chainA, 1, 30*time.Second) } type relayRewardPacketsToProviderAction struct { @@ -886,7 +1142,7 @@ func (tr TestRun) relayRewardPacketsToProvider( tr.waitBlocks(action.consumerChain, uint(blockPerDistribution-currentBlock+1), 60*time.Second) } - tr.relayPackets(relayPacketsAction{chain: action.consumerChain, port: action.port, channel: action.channel}, verbose) + tr.relayPackets(relayPacketsAction{chainA: action.consumerChain, chainB: action.providerChain, port: action.port, channel: action.channel}, verbose) tr.waitBlocks(action.providerChain, 1, 10*time.Second) } @@ -929,6 +1185,8 @@ func (tr TestRun) delegateTokens( if err != nil { log.Fatal(err, "\n", string(bz)) } + + tr.waitBlocks(action.chain, 1, 10*time.Second) } type unbondTokensAction struct { @@ -971,6 +1229,8 @@ func (tr TestRun) unbondTokens( if err != nil { log.Fatal(err, "\n", string(bz)) } + + tr.waitBlocks(action.chain, 1, 10*time.Second) } type redelegateTokensAction struct { @@ -1370,3 +1630,17 @@ func (tr TestRun) waitForSlashThrottleDequeue( func uintPointer(i uint) *uint { return &i } + +// GetPathNameForGorelayer returns the name of the path between two given chains used by Gorelayer. +// Since paths are bidirectional, we need either chain to be able to be provided as first or second argument +// and still return the same name, so we sort the chain names alphabetically. +func (tr TestRun) GetPathNameForGorelayer(chainA, chainB chainID) string { + var pathName string + if string(chainA) < string(chainB) { + pathName = string(chainA) + "-" + string(chainB) + } else { + pathName = string(chainB) + "-" + string(chainA) + } + + return pathName +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 6d7e36d637..cc755cb97a 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -70,6 +70,8 @@ type TestRun struct { tendermintConfigOverride string localSdkPath string useGaia bool + useCometmock bool // if false, nodes run CometBFT + useGorelayer bool // if false, Hermes is used as the relayer gaiaTag string name string @@ -339,6 +341,14 @@ func (s *TestRun) SetDockerConfig(localSdkPath string, useGaia bool, gaiaTag str s.localSdkPath = localSdkPath } +func (s *TestRun) SetCometMockConfig(useCometmock bool) { + s.useCometmock = useCometmock +} + +func (s *TestRun) SetRelayerConfig(useRly bool) { + s.useGorelayer = useRly +} + // validateStringLiterals enforces that configs follow the constraints // necessary to to execute the tests // diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 8ef9d93b7a..141f8a8cd8 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -16,12 +16,19 @@ import ( ) var ( - verbose = flag.Bool("verbose", false, "turn verbose logging on/off") - happyPathOnly = flag.Bool("happy-path-only", false, "run happy path tests only") + verbose = flag.Bool("verbose", false, "turn verbose logging on/off") + happyPathOnly = flag.Bool("happy-path-only", false, "run happy path tests only") + shortHappyPathOnly = flag.Bool("short-happy-path", false, `run abridged happy path tests only. +This is like the happy path, but skips steps +that involve starting or stopping nodes for the same chain outside of the chain setup or teardown. +In particular, this skips steps related to downtime and double signing. +This is suited for CometMock+Gorelayer testing`) includeMultiConsumer = flag.Bool("include-multi-consumer", false, "include multiconsumer tests in run") parallel = flag.Bool("parallel", false, "run all tests in parallel") localSdkPath = flag.String("local-sdk-path", "", "path of a local sdk version to build and reference in integration tests") + useCometmock = flag.Bool("use-cometmock", false, "use cometmock instead of CometBFT") + useGorelayer = flag.Bool("use-gorelayer", false, "use go relayer instead of Hermes") ) var ( @@ -35,6 +42,13 @@ var ( func main() { flag.Parse() + if shortHappyPathOnly != nil && *shortHappyPathOnly { + fmt.Println("=============== running short happy path only ===============") + tr := DefaultTestRun() + tr.Run(shortHappyPathSteps, *localSdkPath, *useGaia, *gaiaTag) + return + } + if happyPathOnly != nil && *happyPathOnly { fmt.Println("=============== running happy path only ===============") tr := DefaultTestRun() @@ -80,6 +94,8 @@ func main() { // Docker containers are torn down after the test run is complete. func (tr *TestRun) Run(steps []Step, localSdkPath string, useGaia bool, gaiaTag string) { tr.SetDockerConfig(localSdkPath, useGaia, gaiaTag) + tr.SetCometMockConfig(*useCometmock) + tr.SetRelayerConfig(*useGorelayer) tr.validateStringLiterals() tr.startDocker() @@ -142,8 +158,8 @@ func (tr *TestRun) runStep(step Step, verbose bool) { tr.assignConsumerPubKey(action, verbose) case slashThrottleDequeue: tr.waitForSlashThrottleDequeue(action, verbose) - case startHermesAction: - tr.startHermes(action, verbose) + case startRelayerAction: + tr.startRelayer(action, verbose) case registerConsumerRewardDenomAction: tr.registerConsumerRewardDenom(action, verbose) default: diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 98483ffb7e..9127c65df8 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -669,6 +669,11 @@ func (tr TestRun) getRegisteredConsumerRewardDenoms(chain chainID) []string { } func (tr TestRun) getValidatorNode(chain chainID, validator validatorID) string { + // for CometMock, validatorNodes are all the same address as the query node (which is CometMocks address) + if tr.useCometmock { + return tr.getQueryNode(chain) + } + return "tcp://" + tr.getValidatorIP(chain, validator) + ":26658" } diff --git a/tests/e2e/step_delegation.go b/tests/e2e/step_delegation.go index 8e9f090736..f33fc136d4 100644 --- a/tests/e2e/step_delegation.go +++ b/tests/e2e/step_delegation.go @@ -46,7 +46,8 @@ func stepsDelegate(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -110,7 +111,8 @@ func stepsUnbond(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -160,7 +162,8 @@ func stepsRedelegateForOptOut(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -212,7 +215,61 @@ func stepsRedelegate(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), + port: "provider", + channel: 0, + }, + state: State{ + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + // Now power changes are seen by consumer + validatorID("alice"): 509, + validatorID("bob"): 500, + validatorID("carol"): 501, + }, + }, + }, + }, + } +} + +// stepsRedelegate tests redelegation and resulting validator power changes. +func stepsRedelegateShort(consumerName string) []Step { + return []Step{ + { + action: redelegateTokensAction{ + chain: chainID("provi"), + src: validatorID("alice"), + dst: validatorID("carol"), + txSender: validatorID("alice"), + // Leave alice with majority stake so non-faulty validators maintain more than + // 2/3 voting power during downtime tests below, avoiding chain halt + amount: 1000000, + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 509, + validatorID("bob"): 500, + // carol always uses a consumer assigned key + validatorID("carol"): 501, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + // Voting power changes not seen by consumer yet + validatorID("alice"): 510, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: relayPacketsAction{ + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 234672546b..8c0b4b2661 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -25,11 +25,21 @@ var happyPathSteps = concatSteps( 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 - stepsStartHermes(), + stepsStartRelayer(), stepsConsumerRemovalPropNotPassing("consu", 3), // submit removal prop but vote no on it - chain should stay stepsStopChain("consu", 4), // stop chain ) +var shortHappyPathSteps = concatSteps( + stepsStartChains([]string{"consu"}, false), + stepsDelegate("consu"), + stepsUnbond("consu"), + stepsRedelegateShort("consu"), + stepsStartRelayer(), + stepsConsumerRemovalPropNotPassing("consu", 2), // submit removal prop but vote no on it - chain should stay + stepsStopChain("consu", 3), // stop chain +) + var slashThrottleSteps = concatSteps( stepsStartChains([]string{"consu"}, false), stepsDelegate("consu"), diff --git a/tests/e2e/steps_democracy.go b/tests/e2e/steps_democracy.go index 9f5915f71d..686fbf1c49 100644 --- a/tests/e2e/steps_democracy.go +++ b/tests/e2e/steps_democracy.go @@ -195,7 +195,8 @@ func stepsDemocracy(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -221,7 +222,8 @@ func stepsDemocracy(consumerName string) []Step { // and can now be relayed as packet to consumer { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -260,7 +262,8 @@ func stepsDemocracy(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, diff --git a/tests/e2e/steps_double_sign.go b/tests/e2e/steps_double_sign.go index e4efca4bb5..c007fa5c1c 100644 --- a/tests/e2e/steps_double_sign.go +++ b/tests/e2e/steps_double_sign.go @@ -30,7 +30,8 @@ func stepsDoubleSignOnProviderAndConsumer(consumerName string) []Step { { // relay power change to consumerName action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, // consumerName channel }, @@ -78,7 +79,8 @@ func stepsDoubleSignOnProviderAndConsumer(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -102,7 +104,8 @@ func stepsDoubleSignOnProviderAndConsumer(consumerName string) []Step { { // consumer learns about the double sign action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, diff --git a/tests/e2e/steps_downtime.go b/tests/e2e/steps_downtime.go index af2d2f237e..74cb349000 100644 --- a/tests/e2e/steps_downtime.go +++ b/tests/e2e/steps_downtime.go @@ -39,7 +39,8 @@ func stepsDowntime(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -65,7 +66,8 @@ func stepsDowntime(consumerName string) []Step { // and can now be relayed as packet to consumer { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -106,7 +108,8 @@ func stepsDowntime(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -150,7 +153,8 @@ func stepsDowntime(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -188,7 +192,8 @@ func stepsDowntime(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -236,7 +241,8 @@ func stepsDowntimeWithOptOut(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -314,7 +320,8 @@ func stepsThrottledDowntime(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -376,7 +383,8 @@ func stepsThrottledDowntime(consumerName string) []Step { // and can now be relayed as packet to consumer { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, diff --git a/tests/e2e/steps_multi_consumer_delegation.go b/tests/e2e/steps_multi_consumer_delegation.go index 106307629d..45536c0679 100644 --- a/tests/e2e/steps_multi_consumer_delegation.go +++ b/tests/e2e/steps_multi_consumer_delegation.go @@ -38,7 +38,8 @@ func stepsMultiConsumerDelegate(consumer1, consumer2 string) []Step { { // relay changes to consumer1 action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer1), port: "provider", channel: 0, // consumer1 channel }, @@ -69,7 +70,8 @@ func stepsMultiConsumerDelegate(consumer1, consumer2 string) []Step { { // relay changes to consumer2 action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer2), port: "provider", channel: 1, // consumer2 channel }, @@ -138,7 +140,8 @@ func stepsMultiConsumerUnbond(consumer1, consumer2 string) []Step { { // relay to consumer1 action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer1), port: "provider", channel: 0, // consumer1 channel }, @@ -169,7 +172,8 @@ func stepsMultiConsumerUnbond(consumer1, consumer2 string) []Step { { // relay to consumer2 action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer2), port: "provider", channel: 1, // consumer2 channel }, @@ -242,7 +246,8 @@ func stepsMultiConsumerRedelegate(consumer1, consumer2 string) []Step { { // relay to consumer1 action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer1), port: "provider", channel: 0, // consumer1 channel }, @@ -273,7 +278,8 @@ func stepsMultiConsumerRedelegate(consumer1, consumer2 string) []Step { { // relay to consumer2 action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer2), port: "provider", channel: 1, // consumer1 channel }, diff --git a/tests/e2e/steps_multi_consumer_double_sign.go b/tests/e2e/steps_multi_consumer_double_sign.go index a50f21e11f..d12eb37eff 100644 --- a/tests/e2e/steps_multi_consumer_double_sign.go +++ b/tests/e2e/steps_multi_consumer_double_sign.go @@ -45,7 +45,8 @@ func stepsMultiConsumerDoubleSign(consumer1, consumer2 string) []Step { { // relay power change to consumer1 action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer1), port: "provider", channel: 0, // consumer1 channel }, @@ -76,7 +77,8 @@ func stepsMultiConsumerDoubleSign(consumer1, consumer2 string) []Step { { // relay power change to consumer2 action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer2), port: "provider", channel: 1, // consumer2 channel }, @@ -137,7 +139,8 @@ func stepsMultiConsumerDoubleSign(consumer1, consumer2 string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer1), port: "provider", channel: 0, // consumer1 channel }, @@ -168,7 +171,8 @@ func stepsMultiConsumerDoubleSign(consumer1, consumer2 string) []Step { { // consumer1 learns about the double sign action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer1), port: "provider", channel: 0, // consumer1 channel }, @@ -199,7 +203,8 @@ func stepsMultiConsumerDoubleSign(consumer1, consumer2 string) []Step { { // consumer2 learns about the double sign action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer2), port: "provider", channel: 1, // consumer2 channel }, diff --git a/tests/e2e/steps_multi_consumer_downtime.go b/tests/e2e/steps_multi_consumer_downtime.go index 181ced029f..ce6cbdff59 100644 --- a/tests/e2e/steps_multi_consumer_downtime.go +++ b/tests/e2e/steps_multi_consumer_downtime.go @@ -40,7 +40,8 @@ func stepsMultiConsumerDowntimeFromConsumer(consumer1, consumer2 string) []Step // Downtime jailing and corresponding voting power change are processed by provider // Validator powers are unchanged on consumer chains action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer1), port: "provider", channel: 0, }, @@ -73,7 +74,8 @@ func stepsMultiConsumerDowntimeFromConsumer(consumer1, consumer2 string) []Step // and can now be relayed as packet to consumer // consumer1 will now see the validator power changes - consumer2 will not (had not been relayed) action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer1), port: "provider", channel: 0, // consumer1 chan }, @@ -100,7 +102,8 @@ func stepsMultiConsumerDowntimeFromConsumer(consumer1, consumer2 string) []Step { // both consumer1 and consumer will now see the validator power changes action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer2), port: "provider", channel: 1, // consumer2 chan }, @@ -156,7 +159,8 @@ func stepsMultiConsumerDowntimeFromConsumer(consumer1, consumer2 string) []Step { // relay to consumer 1 action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer1), port: "provider", channel: 0, }, @@ -187,7 +191,8 @@ func stepsMultiConsumerDowntimeFromConsumer(consumer1, consumer2 string) []Step { // relay to consumer2 action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer2), port: "provider", channel: 1, // consumer2 chan }, @@ -255,7 +260,8 @@ func stepsMultiConsumerDowntimeFromProvider(consumer1, consumer2 string) []Step }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer1), port: "provider", channel: 0, // consumer 1 channel }, @@ -288,7 +294,8 @@ func stepsMultiConsumerDowntimeFromProvider(consumer1, consumer2 string) []Step }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer2), port: "provider", channel: 1, // consumer2 channel }, @@ -349,7 +356,8 @@ func stepsMultiConsumerDowntimeFromProvider(consumer1, consumer2 string) []Step }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer1), port: "provider", channel: 0, // consumer1 channel }, @@ -380,7 +388,8 @@ func stepsMultiConsumerDowntimeFromProvider(consumer1, consumer2 string) []Step }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumer2), port: "provider", channel: 1, // consumer2 channel }, diff --git a/tests/e2e/steps_reward_denom.go b/tests/e2e/steps_reward_denom.go index bc26f38d27..dc10ccb7c6 100644 --- a/tests/e2e/steps_reward_denom.go +++ b/tests/e2e/steps_reward_denom.go @@ -193,7 +193,8 @@ func stepsRewardDenomConsumer(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -219,7 +220,8 @@ func stepsRewardDenomConsumer(consumerName string) []Step { // and can now be relayed as packet to consumer { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, @@ -258,7 +260,8 @@ func stepsRewardDenomConsumer(consumerName string) []Step { }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, diff --git a/tests/e2e/steps_start_chains.go b/tests/e2e/steps_start_chains.go index e3ba9d9ad8..c51042e8c8 100644 --- a/tests/e2e/steps_start_chains.go +++ b/tests/e2e/steps_start_chains.go @@ -265,7 +265,8 @@ func stepsAssignConsumerKeyOnStartedChain(consumerName, validator string) []Step }, { action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, diff --git a/tests/e2e/steps_stop_chain.go b/tests/e2e/steps_stop_chain.go index 229b40b215..9cef49a9ea 100644 --- a/tests/e2e/steps_stop_chain.go +++ b/tests/e2e/steps_stop_chain.go @@ -2,11 +2,11 @@ package main import "time" -// start hermes so that all messages are relayed -func stepsStartHermes() []Step { +// start relayer so that all messages are relayed +func stepsStartRelayer() []Step { return []Step{ { - action: startHermesAction{}, + action: startRelayerAction{}, state: State{}, }, } diff --git a/tests/e2e/steps_submit_equivocation_proposal.go b/tests/e2e/steps_submit_equivocation_proposal.go index f8a91d5362..8af1d2464d 100644 --- a/tests/e2e/steps_submit_equivocation_proposal.go +++ b/tests/e2e/steps_submit_equivocation_proposal.go @@ -122,7 +122,8 @@ func stepsSubmitEquivocationProposal(consumerName string, propNumber uint) []Ste { // relay power change to consumer1 action: relayPacketsAction{ - chain: chainID("provi"), + chainA: chainID("provi"), + chainB: chainID(consumerName), port: "provider", channel: 0, }, diff --git a/tests/e2e/testnet-scripts/start-chain.sh b/tests/e2e/testnet-scripts/start-chain.sh index b4d139ec27..9d6e73fdbb 100644 --- a/tests/e2e/testnet-scripts/start-chain.sh +++ b/tests/e2e/testnet-scripts/start-chain.sh @@ -33,6 +33,18 @@ SKIP_GENTX=$6 # A sed string modifying the tendermint config TENDERMINT_CONFIG_TRANSFORM=$7 +# whether to use CometMock +USE_COMETMOCK=$8 + +# stores a comma separated list of nodes addresses +# needed for CometMock - these are the addresses that the ABCI servers of the apps are listening on +NODE_LISTEN_ADDR_STR="" # example value: 7.7.8.6:26655,7.7.8.4:26655,7.7.8.5:26655 + +# stores a comma separated list of nodes home folders +# needed for CometMock - validator nodes have their private keys in their home directories, and CometMock +# reads the keys to sign with from there +NODE_HOMES="" # example value: /consu/validatorcarol,/consu/validatoralice,/consu/validatorbob + # CREATE VALIDATORS AND DO GENESIS CEREMONY @@ -219,10 +231,13 @@ do VAL_IP_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[$i].ip_suffix") NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" - GAIA_HOME="--home /$CHAIN_ID/validator$VAL_ID" + NODE_HOME="/$CHAIN_ID/validator$VAL_ID" + GAIA_HOME="--home $NODE_HOME" + NODE_HOMES="$NODE_HOME,$NODE_HOMES" RPC_ADDRESS="--rpc.laddr tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26658" GRPC_ADDRESS="--grpc.address $CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:9091" LISTEN_ADDRESS="--address tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26655" + NODE_LISTEN_ADDR_STR="$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26655,$NODE_LISTEN_ADDR_STR" P2P_ADDRESS="--p2p.laddr tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26656" # LOG_LEVEL="--log_level trace" # switch to trace to see panic messages and rich and all debug msgs LOG_LEVEL="--log_level info" @@ -246,7 +261,13 @@ do PERSISTENT_PEERS="--p2p.persistent_peers ${PERSISTENT_PEERS:1}" ARGS="$GAIA_HOME $LISTEN_ADDRESS $RPC_ADDRESS $GRPC_ADDRESS $LOG_LEVEL $P2P_ADDRESS $ENABLE_WEBGRPC $PERSISTENT_PEERS" - ip netns exec $NET_NAMESPACE_NAME $BIN $ARGS start &> /$CHAIN_ID/validator$VAL_ID/logs & + if [[ "$USE_COMETMOCK" == "true" ]]; then + # to start with CometMock, ensure ABCI server uses grpc (--transport=grpc) and the app is started without in-process CometBFT (--with-tendermint=false) + ip netns exec $NET_NAMESPACE_NAME $BIN $ARGS start --transport=grpc --with-tendermint=false &> /$CHAIN_ID/validator$VAL_ID/logs & + else + ip netns exec $NET_NAMESPACE_NAME $BIN $ARGS start &> /$CHAIN_ID/validator$VAL_ID/logs & + fi + done ## SETUP DOUBLE SIGNING NODE NETWORK NAMESPACE @@ -321,16 +342,46 @@ QUERY_PERSISTENT_PEERS="--p2p.persistent_peers ${QUERY_PERSISTENT_PEERS:1}" ## START NODE ARGS="$QUERY_GAIA_HOME $QUERY_LISTEN_ADDRESS $QUERY_RPC_ADDRESS $QUERY_GRPC_ADDRESS $QUERY_LOG_LEVEL $QUERY_P2P_ADDRESS $QUERY_ENABLE_WEBGRPC $QUERY_PERSISTENT_PEERS" -ip netns exec $QUERY_NET_NAMESPACE_NAME $BIN $ARGS start &> /$CHAIN_ID/$QUERY_NODE_ID/logs & + +# Query node is only started if CometMock is not used - with CometMock, it takes the role of the query node +if [[ "$USE_COMETMOCK" != "true" ]]; then + ip netns exec $QUERY_NET_NAMESPACE_NAME $BIN $ARGS start &> /$CHAIN_ID/$QUERY_NODE_ID/logs & +fi ## DONE START NODE +echo "Node addresses:" +echo $NODE_LISTEN_ADDR_STR + +echo "Node homes:" +echo $NODE_HOMES + +NODE_LISTEN_ADDR_STR=${NODE_LISTEN_ADDR_STR%?} +NODE_HOMES=${NODE_HOMES%?} + + +# CometMock takes the role of the query node +if [[ "$USE_COMETMOCK" == "true" ]]; then + sleep 2 + ip netns exec $QUERY_NET_NAMESPACE_NAME cometmock $NODE_LISTEN_ADDR_STR /$CHAIN_ID/genesis.json tcp://$CHAIN_IP_PREFIX.$QUERY_IP_SUFFIX:26658 $NODE_HOMES &> cometmock_${CHAIN_ID}_out.log & + sleep 3 +fi + +# exit + # poll for chain start -set +e -until $BIN query block --node "tcp://$CHAIN_IP_PREFIX.$QUERY_IP_SUFFIX:26658" | grep -q -v '{"block_id":{"hash":"","parts":{"total":0,"hash":""}},"block":null}'; do sleep 0.3 ; done -set -e +if [[ "$USE_COMETMOCK" == "true" ]]; then + set +e + until $BIN query block --node "tcp://$CHAIN_IP_PREFIX.$QUERY_IP_SUFFIX:26658"; do sleep 0.3 ; done + set -e +else + set +e + until $BIN query block --node "tcp://$CHAIN_IP_PREFIX.$QUERY_IP_SUFFIX:26658" | grep -q -v '{"block_id":{"hash":"","parts":{"total":0,"hash": + ""}},"block":null}'; do sleep 0.3 ; done + set -e +fi echo "done!!!!!!!!" -read -p "Press Return to Close..." \ No newline at end of file +read -p "Press Return to Close..." From afa541ff70dc978682c5697cb3ea6b20f314eee9 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> Date: Fri, 16 Jun 2023 15:22:14 -0700 Subject: [PATCH 07/82] feat!: Add DistributionTransmissionChannel to ConsumerAdditionProposal (manual backport #965) (#1031) feat!: Add DistributionTransmissionChannel to ConsumerAdditionProposal (#965) * update proto * remove transfer_channel_id from consumer genesis * ConsumerAdditionProposal: transfer_channel_id -> distribution_transmission_channel * send updated ConsumerAdditionProposal * validate consumer genesis param * remove StandaloneTransferChannelID from store * fix TestOnChanOpenAck * remove state breaking change * finalize merge and fix issues * chore: update docs and changelog * chore: regenerate protos * re-add integrationt tests around changeover * mv entry in changelog * test: add sovereign to consumer changeover e2e (#1025) * tests: add sovereign to consumer e2e test * rm unused bash scripts * partially address review comments * apply remaining review comments * chore: apply formatting rules --------- Co-authored-by: Marius Poke Co-authored-by: MSalopek --- CHANGELOG.md | 1 + Dockerfile | 2 +- Dockerfile.gaia | 2 +- Makefile | 1 + app/consumer-democracy/app.go | 8 +- app/sovereign/ante_handler.go | 55 ++ app/sovereign/app.go | 838 ++++++++++++++++++ app/sovereign/export.go | 187 ++++ app/sovereign/genesis.go | 21 + cmd/interchain-security-sd/cmd/genaccounts.go | 180 ++++ cmd/interchain-security-sd/cmd/root.go | 305 +++++++ cmd/interchain-security-sd/main.go | 24 + docs/docs/consumer-development/onboarding.md | 7 + docs/docs/features/proposals.md | 3 + .../ccv/consumer/v1/genesis.proto | 8 +- .../ccv/provider/v1/provider.proto | 7 + tests/e2e/actions.go | 198 ++++- tests/e2e/actions_sovereign_chain.go | 166 ++++ tests/e2e/config.go | 48 + tests/e2e/main.go | 11 + tests/e2e/state.go | 44 +- tests/e2e/steps.go | 15 + tests/e2e/steps_sovereign_changeover.go | 367 ++++++++ .../testnet-scripts/sovereign-genesis.json | 279 ++++++ tests/e2e/testnet-scripts/start-changeover.sh | 187 ++++ tests/e2e/testnet-scripts/start-sovereign.sh | 133 +++ tests/integration/changeover.go | 2 +- testutil/keeper/unit_test_helpers.go | 1 + x/ccv/consumer/ibc_module.go | 13 +- x/ccv/consumer/ibc_module_test.go | 7 +- x/ccv/consumer/keeper/changeover.go | 1 + x/ccv/consumer/keeper/keeper.go | 14 - x/ccv/consumer/keeper/keeper_test.go | 13 - x/ccv/consumer/keeper/migration_test.go | 2 +- x/ccv/consumer/types/genesis_test.go | 21 +- x/ccv/consumer/types/params.go | 13 +- x/ccv/provider/client/proposal_handler.go | 9 +- x/ccv/provider/keeper/proposal.go | 2 +- x/ccv/provider/keeper/proposal_test.go | 6 + x/ccv/provider/proposal_handler_test.go | 5 +- x/ccv/provider/types/proposal.go | 10 +- x/ccv/provider/types/proposal_test.go | 30 + x/ccv/provider/types/provider.pb.go | 251 +++--- x/ccv/types/shared_params.go | 10 + 44 files changed, 3329 insertions(+), 178 deletions(-) create mode 100644 app/sovereign/ante_handler.go create mode 100644 app/sovereign/app.go create mode 100644 app/sovereign/export.go create mode 100644 app/sovereign/genesis.go create mode 100644 cmd/interchain-security-sd/cmd/genaccounts.go create mode 100644 cmd/interchain-security-sd/cmd/root.go create mode 100644 cmd/interchain-security-sd/main.go create mode 100644 tests/e2e/actions_sovereign_chain.go create mode 100644 tests/e2e/steps_sovereign_changeover.go create mode 100644 tests/e2e/testnet-scripts/sovereign-genesis.json create mode 100644 tests/e2e/testnet-scripts/start-changeover.sh create mode 100644 tests/e2e/testnet-scripts/start-sovereign.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index e74438f99c..1528717b13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state m ## Notable PRs included in v2.0.0 +* (feat!) Add DistributionTransmissionChannel to ConsumerAdditionProposal [#965](https://github.com/cosmos/interchain-security/pull/965) * (feat/fix) limit vsc matured packets handled per endblocker [#1004](https://github.com/cosmos/interchain-security/pull/1004) * (fix) cosumer key prefix order to avoid complex migrations [#963](https://github.com/cosmos/interchain-security/pull/963) and [#991](https://github.com/cosmos/interchain-security/pull/991). The latter PR is the proper fix. * (feat) v1->v2 migrations to accommodate a bugfix having to do with store keys, introduce new params, and deal with consumer genesis state schema changes [#975](https://github.com/cosmos/interchain-security/pull/975) and [#997](https://github.com/cosmos/interchain-security/pull/997) diff --git a/Dockerfile b/Dockerfile index ac528fa8fb..4d81392316 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,7 +48,7 @@ COPY --from=gorelayer-builder /bin/rly /usr/local/bin/ COPY --from=is-builder /go/bin/interchain-security-pd /usr/local/bin/interchain-security-pd COPY --from=is-builder /go/bin/interchain-security-cd /usr/local/bin/interchain-security-cd COPY --from=is-builder /go/bin/interchain-security-cdd /usr/local/bin/interchain-security-cdd - +COPY --from=is-builder /go/bin/interchain-security-sd /usr/local/bin/interchain-security-sd # Copy in the shell scripts that run the testnet ADD ./tests/e2e/testnet-scripts /testnet-scripts diff --git a/Dockerfile.gaia b/Dockerfile.gaia index 196f7dfa74..87448a1d63 100644 --- a/Dockerfile.gaia +++ b/Dockerfile.gaia @@ -74,7 +74,7 @@ COPY --from=hermes-builder /usr/bin/hermes /usr/local/bin/ COPY --from=gaia-builder /go/gaia/build/gaiad /usr/local/bin/interchain-security-pd COPY --from=is-builder /go/bin/interchain-security-cd /usr/local/bin/interchain-security-cd COPY --from=is-builder /go/bin/interchain-security-cdd /usr/local/bin/interchain-security-cdd - +COPY --from=is-builder /go/bin/interchain-security-sd /usr/local/bin/interchain-security-sd # Copy in the shell scripts that run the testnet ADD ./tests/e2e/testnet-scripts /testnet-scripts diff --git a/Makefile b/Makefile index b84eca6524..351852cd21 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ install: go.sum go install $(BUILD_FLAGS) ./cmd/interchain-security-pd go install $(BUILD_FLAGS) ./cmd/interchain-security-cd go install $(BUILD_FLAGS) ./cmd/interchain-security-cdd + go install $(BUILD_FLAGS) ./cmd/interchain-security-sd # run all tests: unit, integration, diff, and E2E test: diff --git a/app/consumer-democracy/app.go b/app/consumer-democracy/app.go index ee299d6221..e9406cfecd 100644 --- a/app/consumer-democracy/app.go +++ b/app/consumer-democracy/app.go @@ -117,7 +117,7 @@ import ( const ( AppName = "interchain-security-cd" - upgradeName = "ics-v1-to-v2" // arbitrary name, define your own appropriately named upgrade + upgradeName = "sovereign-changeover" // arbitrary name, define your own appropriately named upgrade AccountAddressPrefix = "cosmos" ) @@ -645,12 +645,6 @@ func New( // upgrade handler code is application specific. However, as an example, standalone to consumer // changeover chains should utilize customized upgrade handler code similar to below. - // Setting the standalone transfer channel ID is only needed for standalone to consumer changeover chains - // who wish to preserve existing IBC transfer denoms. Here's an example. - // - // Note: This setter needs to execute before the ccv channel handshake is initiated. - app.ConsumerKeeper.SetStandaloneTransferChannelID(ctx, "hardcoded-existing-channel-id") - // TODO: should have a way to read from current node home userHomeDir, err := os.UserHomeDir() if err != nil { diff --git a/app/sovereign/ante_handler.go b/app/sovereign/ante_handler.go new file mode 100644 index 0000000000..3b1a3b7d38 --- /dev/null +++ b/app/sovereign/ante_handler.go @@ -0,0 +1,55 @@ +package app + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + ibcante "github.com/cosmos/ibc-go/v4/modules/core/ante" + ibckeeper "github.com/cosmos/ibc-go/v4/modules/core/keeper" +) + +// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC +// channel keeper. +type HandlerOptions struct { + ante.HandlerOptions + + IBCKeeper *ibckeeper.Keeper +} + +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler") + } + if options.BankKeeper == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler") + } + if options.SignModeHandler == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") + } + + sigGasConsumer := options.SigGasConsumer + if sigGasConsumer == nil { + sigGasConsumer = ante.DefaultSigVerificationGasConsumer + } + + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), + ante.NewRejectExtensionOptionsDecorator(), + ante.NewMempoolFeeDecorator(), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper), + // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewSetPubKeyDecorator(options.AccountKeeper), + ante.NewValidateSigCountDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), + ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + ante.NewIncrementSequenceDecorator(options.AccountKeeper), + ibcante.NewAnteDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/app/sovereign/app.go b/app/sovereign/app.go new file mode 100644 index 0000000000..43d732884b --- /dev/null +++ b/app/sovereign/app.go @@ -0,0 +1,838 @@ +package app + +import ( + "fmt" + "io" + stdlog "log" + "net/http" + "os" + "path/filepath" + + appparams "github.com/cosmos/interchain-security/v2/app/params" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/server/api" + "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/std" + store "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/authz" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" + "github.com/cosmos/cosmos-sdk/x/bank" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/capability" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + "github.com/cosmos/cosmos-sdk/x/crisis" + crisiskeeper "github.com/cosmos/cosmos-sdk/x/crisis/keeper" + crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/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/feegrant" + feegrantkeeper "github.com/cosmos/cosmos-sdk/x/feegrant/keeper" + feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module" + "github.com/cosmos/cosmos-sdk/x/params" + paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client" + + "github.com/cosmos/cosmos-sdk/x/slashing" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/upgrade" + upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + "github.com/cosmos/ibc-go/v4/modules/apps/transfer" + ibctransferkeeper "github.com/cosmos/ibc-go/v4/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" + ibc "github.com/cosmos/ibc-go/v4/modules/core" + ibcconnectiontypes "github.com/cosmos/ibc-go/v4/modules/core/03-connection/types" + porttypes "github.com/cosmos/ibc-go/v4/modules/core/05-port/types" + ibchost "github.com/cosmos/ibc-go/v4/modules/core/24-host" + ibckeeper "github.com/cosmos/ibc-go/v4/modules/core/keeper" + ibctestingcore "github.com/cosmos/interchain-security/v2/legacy_ibc_testing/core" + ibctesting "github.com/cosmos/interchain-security/v2/legacy_ibc_testing/testing" + "github.com/gorilla/mux" + "github.com/rakyll/statik/fs" + "github.com/spf13/cast" + abci "github.com/tendermint/tendermint/abci/types" + tmjson "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/libs/log" + tmos "github.com/tendermint/tendermint/libs/os" + dbm "github.com/tendermint/tm-db" + + sdkdistr "github.com/cosmos/cosmos-sdk/x/distribution" + distrclient "github.com/cosmos/cosmos-sdk/x/distribution/client" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + testutil "github.com/cosmos/interchain-security/v2/testutil/integration" + + sdkstaking "github.com/cosmos/cosmos-sdk/x/staking" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + + sdkgov "github.com/cosmos/cosmos-sdk/x/gov" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + // add mint + mint "github.com/cosmos/cosmos-sdk/x/mint" + mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + + paramproposal "github.com/cosmos/cosmos-sdk/x/params/types/proposal" +) + +const ( + AppName = "interchain-security-s" + upgradeName = "v07-Theta" // arbitrary name, define your own appropriately named upgrade + AccountAddressPrefix = "cosmos" +) + +var ( + // DefaultNodeHome default home directories for the application daemon + DefaultNodeHome string + + // ModuleBasics defines the module BasicManager is in charge of setting up basic, + // non-dependant module elements, such as codec registration + // and genesis verification. + ModuleBasics = module.NewBasicManager( + auth.AppModuleBasic{}, + genutil.AppModuleBasic{}, + bank.AppModuleBasic{}, + capability.AppModuleBasic{}, + sdkstaking.AppModuleBasic{}, + mint.AppModuleBasic{}, + sdkdistr.AppModuleBasic{}, + sdkgov.NewAppModuleBasic( + // TODO: eventually remove upgrade proposal handler and cancel proposal handler + paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, + ), + params.AppModuleBasic{}, + crisis.AppModuleBasic{}, + slashing.AppModuleBasic{}, + feegrantmodule.AppModuleBasic{}, + authzmodule.AppModuleBasic{}, + ibc.AppModuleBasic{}, + upgrade.AppModuleBasic{}, + evidence.AppModuleBasic{}, + transfer.AppModuleBasic{}, + vesting.AppModuleBasic{}, + ) + + // module account permissions + maccPerms = map[string][]string{ + authtypes.FeeCollectorName: nil, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + distrtypes.ModuleName: nil, + minttypes.ModuleName: {authtypes.Minter}, + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + govtypes.ModuleName: {authtypes.Burner}, + } +) + +var ( + _ simapp.App = (*App)(nil) + _ servertypes.Application = (*App)(nil) + _ ibctesting.TestingApp = (*App)(nil) +) + +// App extends an ABCI application, but with most of its parameters exported. +// They are exported for convenience in creating helper functions, as object +// capabilities aren't needed for testing. +type App struct { // nolint: golint + *baseapp.BaseApp + legacyAmino *codec.LegacyAmino + appCodec codec.Codec + interfaceRegistry types.InterfaceRegistry + + invCheckPeriod uint + + // keys to access the substores + keys map[string]*sdk.KVStoreKey + tkeys map[string]*sdk.TransientStoreKey + memKeys map[string]*sdk.MemoryStoreKey + + // keepers + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + CapabilityKeeper *capabilitykeeper.Keeper + StakingKeeper stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + MintKeeper mintkeeper.Keeper + + DistrKeeper distrkeeper.Keeper + + GovKeeper govkeeper.Keeper + CrisisKeeper crisiskeeper.Keeper + 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 + FeeGrantKeeper feegrantkeeper.Keeper + AuthzKeeper authzkeeper.Keeper + + // make scoped keepers public for test purposes + ScopedIBCKeeper capabilitykeeper.ScopedKeeper + ScopedTransferKeeper capabilitykeeper.ScopedKeeper + + // the module manager + MM *module.Manager + + // simulation manager + sm *module.SimulationManager + configurator module.Configurator +} + +func init() { + userHomeDir, err := os.UserHomeDir() + if err != nil { + stdlog.Println("Failed to get home dir %2", err) + } + + DefaultNodeHome = filepath.Join(userHomeDir, "."+AppName) +} + +// New returns a reference to an initialized App. +func New( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + skipUpgradeHeights map[int64]bool, + homePath string, + invCheckPeriod uint, + encodingConfig appparams.EncodingConfig, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), +) *App { + appCodec := encodingConfig.Marshaler + legacyAmino := encodingConfig.Amino + interfaceRegistry := encodingConfig.InterfaceRegistry + + bApp := baseapp.NewBaseApp(AppName, logger, db, encodingConfig.TxConfig.TxDecoder(), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) + bApp.SetVersion(version.Version) + bApp.SetInterfaceRegistry(interfaceRegistry) + + keys := sdk.NewKVStoreKeys( + authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, + minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, + govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, + evidencetypes.StoreKey, ibctransfertypes.StoreKey, + capabilitytypes.StoreKey, authzkeeper.StoreKey, + ) + tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) + memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) + + app := &App{ + BaseApp: bApp, + legacyAmino: legacyAmino, + appCodec: appCodec, + interfaceRegistry: interfaceRegistry, + invCheckPeriod: invCheckPeriod, + keys: keys, + tkeys: tkeys, + memKeys: memKeys, + } + + app.ParamsKeeper = initParamsKeeper( + appCodec, + legacyAmino, + keys[paramstypes.StoreKey], + tkeys[paramstypes.TStoreKey], + ) + + // set the BaseApp's parameter store + bApp.SetParamStore( + app.ParamsKeeper.Subspace(baseapp.Paramspace).WithKeyTable( + paramskeeper.ConsensusParamsKeyTable()), + ) + + // add capability keeper and ScopeToModule for ibc module + app.CapabilityKeeper = capabilitykeeper.NewKeeper( + appCodec, + keys[capabilitytypes.StoreKey], + memKeys[capabilitytypes.MemStoreKey], + ) + scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibchost.ModuleName) + scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) + app.CapabilityKeeper.Seal() + + // add keepers + app.AccountKeeper = authkeeper.NewAccountKeeper( + appCodec, + keys[authtypes.StoreKey], + app.GetSubspace(authtypes.ModuleName), + authtypes.ProtoBaseAccount, + maccPerms, + ) + + bankBlockedAddrs := app.ModuleAccountAddrs() + + app.BankKeeper = bankkeeper.NewBaseKeeper( + appCodec, + keys[banktypes.StoreKey], + app.AccountKeeper, + app.GetSubspace(banktypes.ModuleName), + bankBlockedAddrs, + ) + app.AuthzKeeper = authzkeeper.NewKeeper( + keys[authzkeeper.StoreKey], + appCodec, + app.BaseApp.MsgServiceRouter(), + ) + app.FeeGrantKeeper = feegrantkeeper.NewKeeper( + appCodec, + keys[feegrant.StoreKey], + app.AccountKeeper, + ) + + stakingKeeper := stakingkeeper.NewKeeper( + appCodec, + keys[stakingtypes.StoreKey], + app.AccountKeeper, + app.BankKeeper, + app.GetSubspace(stakingtypes.ModuleName), + ) + + app.MintKeeper = mintkeeper.NewKeeper( + appCodec, keys[minttypes.StoreKey], app.GetSubspace(minttypes.ModuleName), &stakingKeeper, + app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, + ) + + app.SlashingKeeper = slashingkeeper.NewKeeper( + appCodec, + keys[slashingtypes.StoreKey], + &app.StakingKeeper, + app.GetSubspace(slashingtypes.ModuleName), + ) + app.DistrKeeper = distrkeeper.NewKeeper( + appCodec, + keys[distrtypes.StoreKey], + app.GetSubspace(distrtypes.ModuleName), + app.AccountKeeper, + app.BankKeeper, + &stakingKeeper, + authtypes.FeeCollectorName, + app.ModuleAccountAddrs(), + ) + app.CrisisKeeper = crisiskeeper.NewKeeper( + app.GetSubspace(crisistypes.ModuleName), + invCheckPeriod, + app.BankKeeper, + authtypes.FeeCollectorName, + ) + app.UpgradeKeeper = upgradekeeper.NewKeeper( + skipUpgradeHeights, + keys[upgradetypes.StoreKey], + appCodec, + homePath, + app.BaseApp, + ) + + // register the staking hooks + // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks + app.StakingKeeper = *stakingKeeper.SetHooks( + stakingtypes.NewMultiStakingHooks( + app.DistrKeeper.Hooks(), + app.SlashingKeeper.Hooks(), + ), + ) + + // register the proposal types + sdkgovRouter := govtypes.NewRouter() + sdkgovRouter.AddRoute(govtypes.RouterKey, govtypes.ProposalHandler). + AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). + AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)) + govKeeper := govkeeper.NewKeeper( + appCodec, + keys[govtypes.StoreKey], + app.GetSubspace(govtypes.ModuleName), + app.AccountKeeper, + app.BankKeeper, + &stakingKeeper, + sdkgovRouter, + ) + + app.GovKeeper = *govKeeper.SetHooks( + govtypes.NewMultiGovHooks( + // register the governance hooks + ), + ) + + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, + keys[ibchost.StoreKey], + app.GetSubspace(ibchost.ModuleName), + &app.StakingKeeper, + app.UpgradeKeeper, + scopedIBCKeeper, + ) + + app.SlashingKeeper = slashingkeeper.NewKeeper( + appCodec, + keys[slashingtypes.StoreKey], + &app.StakingKeeper, + app.GetSubspace(slashingtypes.ModuleName), + ) + + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, + keys[ibctransfertypes.StoreKey], + app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, + app.AccountKeeper, + app.BankKeeper, + scopedTransferKeeper, + ) + transferModule := transfer.NewAppModule(app.TransferKeeper) + ibcmodule := transfer.NewIBCModule(app.TransferKeeper) + + // create static IBC router, add transfer route, then set and seal it + ibcRouter := porttypes.NewRouter() + ibcRouter.AddRoute(ibctransfertypes.ModuleName, ibcmodule) + 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 + // must be passed by reference here. + app.MM = module.NewManager( + genutil.NewAppModule( + app.AccountKeeper, + app.StakingKeeper, + app.BaseApp.DeliverTx, + encodingConfig.TxConfig, + ), + auth.NewAppModule(appCodec, app.AccountKeeper, nil), + vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), + bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants), + feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), + sdkgov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper), + mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper), + slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), + sdkdistr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), + sdkstaking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper), + upgrade.NewAppModule(app.UpgradeKeeper), + evidence.NewAppModule(app.EvidenceKeeper), + params.NewAppModule(app.ParamsKeeper), + authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + app.MM.SetOrderBeginBlockers( + upgradetypes.ModuleName, + capabilitytypes.ModuleName, + minttypes.ModuleName, + distrtypes.ModuleName, + slashingtypes.ModuleName, + evidencetypes.ModuleName, + stakingtypes.ModuleName, + authtypes.ModuleName, + banktypes.ModuleName, + govtypes.ModuleName, + crisistypes.ModuleName, + authz.ModuleName, + feegrant.ModuleName, + paramstypes.ModuleName, + genutiltypes.ModuleName, + vestingtypes.ModuleName, + ibctransfertypes.ModuleName, + ibchost.ModuleName, + ) + app.MM.SetOrderEndBlockers( + crisistypes.ModuleName, + govtypes.ModuleName, + stakingtypes.ModuleName, + capabilitytypes.ModuleName, + authtypes.ModuleName, + banktypes.ModuleName, + distrtypes.ModuleName, + slashingtypes.ModuleName, + minttypes.ModuleName, + evidencetypes.ModuleName, + authz.ModuleName, + feegrant.ModuleName, + paramstypes.ModuleName, + genutiltypes.ModuleName, + upgradetypes.ModuleName, + vestingtypes.ModuleName, + ibctransfertypes.ModuleName, + ibchost.ModuleName, + ) + + // NOTE: The genutils module must occur after staking so that pools are + // properly initialized with tokens from genesis accounts. + // NOTE: The genutils module must also occur after auth so that it can access the params from auth. + // NOTE: Capability module must occur first so that it can initialize any capabilities + // so that other modules that want to create or claim capabilities afterwards in InitChain + // can do so safely. + app.MM.SetOrderInitGenesis( + capabilitytypes.ModuleName, + authtypes.ModuleName, + banktypes.ModuleName, + distrtypes.ModuleName, + stakingtypes.ModuleName, + slashingtypes.ModuleName, + govtypes.ModuleName, + minttypes.ModuleName, + crisistypes.ModuleName, + evidencetypes.ModuleName, + authz.ModuleName, + feegrant.ModuleName, + paramstypes.ModuleName, + upgradetypes.ModuleName, + vestingtypes.ModuleName, + genutiltypes.ModuleName, + ibchost.ModuleName, + ibctransfertypes.ModuleName, + ) + + app.MM.RegisterInvariants(&app.CrisisKeeper) + app.MM.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino) + + app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) + app.MM.RegisterServices(app.configurator) + + // initialize stores + app.MountKVStores(keys) + app.MountTransientStores(tkeys) + app.MountMemoryStores(memKeys) + + anteHandler, err := NewAnteHandler( + HandlerOptions{ + HandlerOptions: ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + FeegrantKeeper: app.FeeGrantKeeper, + SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + IBCKeeper: app.IBCKeeper, + }, + ) + if err != nil { + panic(fmt.Errorf("failed to create AnteHandler: %s", err)) + } + app.SetAnteHandler(anteHandler) + + app.SetInitChainer(app.InitChainer) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + + app.UpgradeKeeper.SetUpgradeHandler( + upgradeName, + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + app.IBCKeeper.ConnectionKeeper.SetParams(ctx, ibcconnectiontypes.DefaultParams()) + + fromVM := make(map[string]uint64) + + for moduleName, eachModule := range app.MM.Modules { + fromVM[moduleName] = eachModule.ConsensusVersion() + } + + ctx.Logger().Info("start to run module migrations...") + + return app.MM.RunMigrations(ctx, app.configurator, fromVM) + }, + ) + + upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() + if err != nil { + panic(fmt.Sprintf("failed to read upgrade info from disk %s", err)) + } + + if upgradeInfo.Name == upgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := store.StoreUpgrades{} + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + } + + if loadLatest { + if err := app.LoadLatestVersion(); err != nil { + tmos.Exit(fmt.Sprintf("failed to load latest version: %s", err)) + } + } + + app.ScopedIBCKeeper = scopedIBCKeeper + app.ScopedTransferKeeper = scopedTransferKeeper + + return app +} + +// Name returns the name of the App +func (app *App) Name() string { return app.BaseApp.Name() } + +// BeginBlocker application updates every begin block +func (app *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + return app.MM.BeginBlock(ctx, req) +} + +// EndBlocker application updates every end block +func (app *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + return app.MM.EndBlock(ctx, req) +} + +// InitChainer application update at chain initialization +func (app *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + var genesisState GenesisState + if err := tmjson.Unmarshal(req.AppStateBytes, &genesisState); err != nil { + panic(err) + } + + app.UpgradeKeeper.SetModuleVersionMap(ctx, app.MM.GetVersionMap()) + return app.MM.InitGenesis(ctx, app.appCodec, genesisState) +} + +// LoadHeight loads a particular height +func (app *App) LoadHeight(height int64) error { + return app.LoadVersion(height) +} + +// ModuleAccountAddrs returns all the app's module account addresses. +func (app *App) ModuleAccountAddrs() map[string]bool { + modAccAddrs := make(map[string]bool) + for acc := range maccPerms { + modAccAddrs[authtypes.NewModuleAddress(acc).String()] = true + } + + return modAccAddrs +} + +// LegacyAmino returns App's amino codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *App) LegacyAmino() *codec.LegacyAmino { + return app.legacyAmino +} + +// AppCodec returns the app codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *App) AppCodec() codec.Codec { + return app.appCodec +} + +// InterfaceRegistry returns the InterfaceRegistry +func (app *App) InterfaceRegistry() types.InterfaceRegistry { + return app.interfaceRegistry +} + +// GetKey returns the KVStoreKey for the provided store key. +// +// NOTE: This is solely to be used for testing purposes. +func (app *App) GetKey(storeKey string) *sdk.KVStoreKey { + return app.keys[storeKey] +} + +// GetTKey returns the TransientStoreKey for the provided store key. +// +// NOTE: This is solely to be used for testing purposes. +func (app *App) GetTKey(storeKey string) *sdk.TransientStoreKey { + return app.tkeys[storeKey] +} + +// GetMemKey returns the MemStoreKey for the provided mem key. +// +// NOTE: This is solely used for testing purposes. +func (app *App) GetMemKey(storeKey string) *sdk.MemoryStoreKey { + return app.memKeys[storeKey] +} + +// GetSubspace returns a param subspace for a given module name. +// +// NOTE: This is solely to be used for testing purposes. +func (app *App) GetSubspace(moduleName string) paramstypes.Subspace { + subspace, _ := app.ParamsKeeper.GetSubspace(moduleName) + return subspace +} + +// SimulationManager implements the SimulationApp interface +func (app *App) SimulationManager() *module.SimulationManager { + return app.sm +} + +func (app *App) GetTestBankKeeper() testutil.TestBankKeeper { + return app.BankKeeper +} + +func (app *App) GetTestAccountKeeper() testutil.TestAccountKeeper { + return app.AccountKeeper +} + +func (app *App) GetTestSlashingKeeper() testutil.TestSlashingKeeper { + return app.SlashingKeeper +} + +func (app *App) GetTestEvidenceKeeper() testutil.TestEvidenceKeeper { + return app.EvidenceKeeper +} + +func (app *App) GetTestStakingKeeper() testutil.TestStakingKeeper { + return app.StakingKeeper +} + +func (app *App) GetTestDistributionKeeper() testutil.TestDistributionKeeper { + return app.DistrKeeper +} + +func (app *App) GetTestMintKeeper() testutil.TestMintKeeper { + return app.MintKeeper +} + +func (app *App) GetTestGovKeeper() testutil.TestGovKeeper { + return app.GovKeeper +} + +// TestingApp functions + +// GetBaseApp implements the TestingApp interface. +func (app *App) GetBaseApp() *baseapp.BaseApp { + return app.BaseApp +} + +// GetStakingKeeper implements the TestingApp interface. +func (app *App) GetStakingKeeper() ibctestingcore.StakingKeeper { + return app.StakingKeeper +} + +// GetIBCKeeper implements the TestingApp interface. +func (app *App) GetIBCKeeper() *ibckeeper.Keeper { + return app.IBCKeeper +} + +// GetScopedIBCKeeper implements the TestingApp interface. +func (app *App) GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper { + return app.ScopedIBCKeeper +} + +// GetTxConfig implements the TestingApp interface. +func (app *App) GetTxConfig() client.TxConfig { + return MakeTestEncodingConfig().TxConfig +} + +// RegisterAPIRoutes registers all application module routes with the provided +// API server. +func (app *App) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) { + clientCtx := apiSvr.ClientCtx + rpc.RegisterRoutes(clientCtx, apiSvr.Router) + // Register legacy tx routes. + authrest.RegisterTxRoutes(clientCtx, apiSvr.Router) + // Register new tx routes from grpc-gateway. + authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + // Register new tendermint queries routes from grpc-gateway. + tmservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register legacy and grpc-gateway routes for all modules. + ModuleBasics.RegisterRESTRoutes(clientCtx, apiSvr.Router) + ModuleBasics.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // register swagger API from root so that other applications can override easily + if apiConfig.Swagger { + RegisterSwaggerAPI(apiSvr.Router) + } +} + +// RegisterTxService implements the Application.RegisterTxService method. +func (app *App) RegisterTxService(clientCtx client.Context) { + authtx.RegisterTxService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.BaseApp.Simulate, app.interfaceRegistry) +} + +// RegisterTendermintService implements the Application.RegisterTendermintService method. +func (app *App) RegisterTendermintService(clientCtx client.Context) { + tmservice.RegisterTendermintService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.interfaceRegistry) +} + +// RegisterSwaggerAPI registers swagger route with API Server +func RegisterSwaggerAPI(rtr *mux.Router) { + statikFS, err := fs.New() + if err != nil { + panic(err) + } + + staticServer := http.FileServer(statikFS) + rtr.PathPrefix("/swagger/").Handler(http.StripPrefix("/swagger/", staticServer)) +} + +// GetMaccPerms returns a copy of the module account permissions +func GetMaccPerms() map[string][]string { + dupMaccPerms := make(map[string][]string) + for k, v := range maccPerms { + dupMaccPerms[k] = v + } + return dupMaccPerms +} + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + paramsKeeper := paramskeeper.NewKeeper(appCodec, legacyAmino, key, tkey) + + paramsKeeper.Subspace(authtypes.ModuleName) + paramsKeeper.Subspace(banktypes.ModuleName) + paramsKeeper.Subspace(stakingtypes.ModuleName) + paramsKeeper.Subspace(minttypes.ModuleName) + paramsKeeper.Subspace(distrtypes.ModuleName) + paramsKeeper.Subspace(slashingtypes.ModuleName) + paramsKeeper.Subspace(govtypes.ModuleName).WithKeyTable(govtypes.ParamKeyTable()) + paramsKeeper.Subspace(crisistypes.ModuleName) + paramsKeeper.Subspace(ibctransfertypes.ModuleName) + paramsKeeper.Subspace(ibchost.ModuleName) + + return paramsKeeper +} + +// MakeTestEncodingConfig creates an EncodingConfig for testing. This function +// should be used only in tests or when creating a new app instance (NewApp*()). +// App user shouldn't create new codecs - use the app.AppCodec instead. +// [DEPRECATED] +func MakeTestEncodingConfig() appparams.EncodingConfig { + encodingConfig := appparams.MakeTestEncodingConfig() + std.RegisterLegacyAminoCodec(encodingConfig.Amino) + std.RegisterInterfaces(encodingConfig.InterfaceRegistry) + ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) + ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) + return encodingConfig +} diff --git a/app/sovereign/export.go b/app/sovereign/export.go new file mode 100644 index 0000000000..f0b3e3ed76 --- /dev/null +++ b/app/sovereign/export.go @@ -0,0 +1,187 @@ +package app + +import ( + "encoding/json" + "log" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + servertypes "github.com/cosmos/cosmos-sdk/server/types" + sdk "github.com/cosmos/cosmos-sdk/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// ExportAppStateAndValidators exports the state of the application for a genesis +// file. +func (app *App) ExportAppStateAndValidators( + forZeroHeight bool, jailAllowedAddrs []string, +) (servertypes.ExportedApp, error) { + // as if they could withdraw from the start of the next block + ctx := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) + + // We export at last height + 1, because that's the height at which + // Tendermint will start InitChain. + height := app.LastBlockHeight() + 1 + if forZeroHeight { + height = 0 + app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) + } + + genState := app.MM.ExportGenesis(ctx, app.appCodec) + appState, err := json.MarshalIndent(genState, "", " ") + if err != nil { + return servertypes.ExportedApp{}, err + } + + validators, err := staking.WriteValidators(ctx, app.StakingKeeper) + if err != nil { + return servertypes.ExportedApp{}, err + } + return servertypes.ExportedApp{ + AppState: appState, + Validators: validators, + Height: height, + ConsensusParams: app.BaseApp.GetConsensusParams(ctx), + }, nil +} + +// prepare for fresh start at zero height +// NOTE zero height genesis is a temporary feature which will be deprecated +// +// in favour of export at a block height +func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) { + applyAllowedAddrs := false + + // check if there is a allowed address list + if len(jailAllowedAddrs) > 0 { + applyAllowedAddrs = true + } + + allowedAddrsMap := make(map[string]bool) + + for _, addr := range jailAllowedAddrs { + _, err := sdk.ValAddressFromBech32(addr) + if err != nil { + log.Fatal(err) + } + allowedAddrsMap[addr] = true + } + + /* Just to be safe, assert the invariants on current state. */ + app.CrisisKeeper.AssertInvariants(ctx) + + /* Handle fee distribution state. */ + + // withdraw all validator commission + app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + _, err := app.DistrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) + if err != nil { + panic(err) + } + return false + }) + + // withdraw all delegator rewards + dels := app.StakingKeeper.GetAllDelegations(ctx) + for _, delegation := range dels { + _, err := app.DistrKeeper.WithdrawDelegationRewards(ctx, delegation.GetDelegatorAddr(), delegation.GetValidatorAddr()) + if err != nil { + panic(err) + } + } + + // clear validator slash events + app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx) + + // clear validator historical rewards + app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx) + + // set context height to zero + height := ctx.BlockHeight() + ctx = ctx.WithBlockHeight(0) + + // reinitialize all validators + app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + // donate any unwithdrawn outstanding reward fraction tokens to the community pool + scraps := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, val.GetOperator()) + feePool := app.DistrKeeper.GetFeePool(ctx) + feePool.CommunityPool = feePool.CommunityPool.Add(scraps...) + app.DistrKeeper.SetFeePool(ctx, feePool) + + app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) + return false + }) + + // reinitialize all delegations + for _, del := range dels { + app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, del.GetDelegatorAddr(), del.GetValidatorAddr()) + app.DistrKeeper.Hooks().AfterDelegationModified(ctx, del.GetDelegatorAddr(), del.GetValidatorAddr()) + } + + // reset context height + ctx = ctx.WithBlockHeight(height) + + /* Handle staking state. */ + + // iterate through redelegations, reset creation height + app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) { + for i := range red.Entries { + red.Entries[i].CreationHeight = 0 + } + app.StakingKeeper.SetRedelegation(ctx, red) + return false + }) + + // iterate through unbonding delegations, reset creation height + app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) { + for i := range ubd.Entries { + ubd.Entries[i].CreationHeight = 0 + } + app.StakingKeeper.SetUnbondingDelegation(ctx, ubd) + return false + }) + + // Iterate through validators by power descending, reset bond heights, and + // update bond intra-tx counters. + store := ctx.KVStore(app.keys[stakingtypes.StoreKey]) + iter := sdk.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey) + counter := int16(0) + + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(iter.Key()[1:]) + validator, found := app.StakingKeeper.GetValidator(ctx, addr) + if !found { + panic("expected validator, not found") + } + + validator.UnbondingHeight = 0 + if applyAllowedAddrs && !allowedAddrsMap[addr.String()] { + validator.Jailed = true + } + + app.StakingKeeper.SetValidator(ctx, validator) + counter++ + } + + if err := iter.Close(); err != nil { + panic(err) + } + + if _, err := app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx); err != nil { + panic(err) + } + + /* Handle slashing state. */ + + // reset start height on signing infos + app.SlashingKeeper.IterateValidatorSigningInfos( + ctx, + func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + return false + }, + ) +} diff --git a/app/sovereign/genesis.go b/app/sovereign/genesis.go new file mode 100644 index 0000000000..5bf0c1da80 --- /dev/null +++ b/app/sovereign/genesis.go @@ -0,0 +1,21 @@ +package app + +import ( + "encoding/json" + + "github.com/cosmos/cosmos-sdk/codec" +) + +// The genesis state of the blockchain is represented here as a map of raw json +// messages key'd by a identifier string. +// The identifier is used to determine which module genesis information belongs +// to so it may be appropriately routed during init chain. +// Within this application default genesis information is retrieved from +// the ModuleBasicManager which populates json from each BasicModule +// object provided to it during init. +type GenesisState map[string]json.RawMessage + +// NewDefaultGenesisState generates the default state for the application. +func NewDefaultGenesisState(cdc codec.JSONCodec) GenesisState { + return ModuleBasics.DefaultGenesis(cdc) +} diff --git a/cmd/interchain-security-sd/cmd/genaccounts.go b/cmd/interchain-security-sd/cmd/genaccounts.go new file mode 100644 index 0000000000..c3340dccad --- /dev/null +++ b/cmd/interchain-security-sd/cmd/genaccounts.go @@ -0,0 +1,180 @@ +package cmd + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" +) + +const ( + flagVestingStart = "vesting-start-time" + flagVestingEnd = "vesting-end-time" + flagVestingAmt = "vesting-amount" +) + +// AddGenesisAccountCmd returns add-genesis-account cobra Command. +func AddGenesisAccountCmd(defaultNodeHome string) *cobra.Command { + cmd := &cobra.Command{ + Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]", + Short: "Add a genesis account to genesis.json", + Long: `Add a genesis account to genesis.json. The provided account must specify +the account address or key name and a list of initial coins. If a key name is given, +the address will be looked up in the local Keybase. The list of initial tokens must +contain valid denominations. Accounts may optionally be supplied with vesting parameters. +`, + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + serverCtx := server.GetServerContextFromCmd(cmd) + config := serverCtx.Config + + config.SetRoot(clientCtx.HomeDir) + + var kr keyring.Keyring + addr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + inBuf := bufio.NewReader(cmd.InOrStdin()) + keyringBackend, _ := cmd.Flags().GetString(flags.FlagKeyringBackend) + if keyringBackend != "" && clientCtx.Keyring == nil { + var err error + kr, err = keyring.New(sdk.KeyringServiceName(), keyringBackend, clientCtx.HomeDir, inBuf) + if err != nil { + return err + } + } else { + kr = clientCtx.Keyring + } + + info, err := kr.Key(args[0]) + if err != nil { + return fmt.Errorf("failed to get address from Keyring: %w", err) + } + addr = info.GetAddress() + } + + coins, err := sdk.ParseCoinsNormalized(args[1]) + if err != nil { + return fmt.Errorf("failed to parse coins: %w", err) + } + + vestingStart, _ := cmd.Flags().GetInt64(flagVestingStart) + vestingEnd, _ := cmd.Flags().GetInt64(flagVestingEnd) + vestingAmtStr, _ := cmd.Flags().GetString(flagVestingAmt) + + vestingAmt, err := sdk.ParseCoinsNormalized(vestingAmtStr) + if err != nil { + return fmt.Errorf("failed to parse vesting amount: %w", err) + } + + // create concrete account type based on input parameters + var genAccount authtypes.GenesisAccount + + balances := banktypes.Balance{Address: addr.String(), Coins: coins.Sort()} + baseAccount := authtypes.NewBaseAccount(addr, nil, 0, 0) + + if !vestingAmt.IsZero() { + baseVestingAccount := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd) + + if (balances.Coins.IsZero() && !baseVestingAccount.OriginalVesting.IsZero()) || + baseVestingAccount.OriginalVesting.IsAnyGT(balances.Coins) { + return errors.New("vesting amount cannot be greater than total amount") + } + + switch { + case vestingStart != 0 && vestingEnd != 0: + genAccount = authvesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart) + + case vestingEnd != 0: + genAccount = authvesting.NewDelayedVestingAccountRaw(baseVestingAccount) + + default: + return errors.New("invalid vesting parameters; must supply start and end time or end time") + } + } else { + genAccount = baseAccount + } + + if err := genAccount.Validate(); err != nil { + return fmt.Errorf("failed to validate new genesis account: %w", err) + } + + genFile := config.GenesisFile() + appState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFile) + if err != nil { + return fmt.Errorf("failed to unmarshal genesis state: %w", err) + } + + authGenState := authtypes.GetGenesisStateFromAppState(clientCtx.Codec, appState) + + accs, err := authtypes.UnpackAccounts(authGenState.Accounts) + if err != nil { + return fmt.Errorf("failed to get accounts from any: %w", err) + } + + if accs.Contains(addr) { + return fmt.Errorf("cannot add account at existing address %s", addr) + } + + // Add the new account to the set of genesis accounts and sanitize the + // accounts afterwards. + accs = append(accs, genAccount) + accs = authtypes.SanitizeGenesisAccounts(accs) + + genAccs, err := authtypes.PackAccounts(accs) + if err != nil { + return fmt.Errorf("failed to convert accounts into any's: %w", err) + } + authGenState.Accounts = genAccs + + authGenStateBz, err := clientCtx.Codec.MarshalJSON(&authGenState) + if err != nil { + return fmt.Errorf("failed to marshal auth genesis state: %w", err) + } + + appState[authtypes.ModuleName] = authGenStateBz + + bankGenState := banktypes.GetGenesisStateFromAppState(clientCtx.Codec, appState) + bankGenState.Balances = append(bankGenState.Balances, balances) + bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances) + bankGenState.Supply = bankGenState.Supply.Add(balances.Coins...) + + bankGenStateBz, err := clientCtx.Codec.MarshalJSON(bankGenState) + if err != nil { + return fmt.Errorf("failed to marshal bank genesis state: %w", err) + } + + appState[banktypes.ModuleName] = bankGenStateBz + + appStateJSON, err := json.Marshal(appState) + if err != nil { + return fmt.Errorf("failed to marshal application genesis state: %w", err) + } + + genDoc.AppState = appStateJSON + return genutil.ExportGenesisFile(genDoc, genFile) + }, + } + + cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") + cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)") + cmd.Flags().String(flagVestingAmt, "", "amount of coins for vesting accounts") + cmd.Flags().Int64(flagVestingStart, 0, "schedule start time (unix epoch) for vesting accounts") + cmd.Flags().Int64(flagVestingEnd, 0, "schedule end time (unix epoch) for vesting accounts") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/cmd/interchain-security-sd/cmd/root.go b/cmd/interchain-security-sd/cmd/root.go new file mode 100644 index 0000000000..56f9ad667b --- /dev/null +++ b/cmd/interchain-security-sd/cmd/root.go @@ -0,0 +1,305 @@ +package cmd + +import ( + "errors" + "io" + "os" + "path/filepath" + + serverconfig "github.com/cosmos/cosmos-sdk/server/config" + "github.com/spf13/cast" + "github.com/spf13/cobra" + tmcli "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/config" + "github.com/cosmos/cosmos-sdk/client/debug" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/pruning" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/server" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/snapshots" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/crisis" + genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" + "github.com/cosmos/interchain-security/v2/app/params" + simapp "github.com/cosmos/interchain-security/v2/app/sovereign" +) + +// NewRootCmd creates a new root command for simd. It is called once in the +// main function. +func NewRootCmd() (*cobra.Command, params.EncodingConfig) { + encodingConfig := simapp.MakeTestEncodingConfig() + initClientCtx := client.Context{}. + WithCodec(encodingConfig.Marshaler). + WithInterfaceRegistry(encodingConfig.InterfaceRegistry). + WithTxConfig(encodingConfig.TxConfig). + WithLegacyAmino(encodingConfig.Amino). + WithInput(os.Stdin). + WithAccountRetriever(types.AccountRetriever{}). + WithHomeDir(simapp.DefaultNodeHome). + WithViper("") // In simapp, we don't use any prefix for env variables. + + rootCmd := &cobra.Command{ + Use: "interchain-security-sd", + Short: "simulation app", + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + // set the default command outputs + cmd.SetOut(cmd.OutOrStdout()) + cmd.SetErr(cmd.ErrOrStderr()) + + initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags()) + if err != nil { + return err + } + + initClientCtx, err = config.ReadFromClientConfig(initClientCtx) + if err != nil { + return err + } + + if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil { + return err + } + + customAppTemplate, customAppConfig := initAppConfig() + + return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig) + }, + } + + initRootCmd(rootCmd, encodingConfig) + + return rootCmd, encodingConfig +} + +// initAppConfig helps to override default appConfig template and configs. +// return "", nil if no custom configuration is required for the application. +func initAppConfig() (string, interface{}) { + // The following code snippet is just for reference. + + // WASMConfig defines configuration for the wasm module. + type WASMConfig struct { + // This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries + QueryGasLimit uint64 `mapstructure:"query_gas_limit"` + + // Address defines the gRPC-web server to listen on + LruSize uint64 `mapstructure:"lru_size"` + } + + type CustomAppConfig struct { + serverconfig.Config + + WASM WASMConfig `mapstructure:"wasm"` + } + + // Optionally allow the chain developer to overwrite the SDK's default + // server config. + srvCfg := serverconfig.DefaultConfig() + // The SDK's default minimum gas price is set to "" (empty value) inside + // app.toml. If left empty by validators, the node will halt on startup. + // However, the chain developer can set a default app.toml value for their + // validators here. + // + // In summary: + // - if you leave srvCfg.MinGasPrices = "", all validators MUST tweak their + // own app.toml config, + // - if you set srvCfg.MinGasPrices non-empty, validators CAN tweak their + // own app.toml to override, or use this default value. + // + // In simapp, we set the min gas prices to 0. + srvCfg.MinGasPrices = "0stake" + // srvCfg.BaseConfig.IAVLDisableFastNode = true // disable fastnode by default + + customAppConfig := CustomAppConfig{ + Config: *srvCfg, + WASM: WASMConfig{ + LruSize: 1, + QueryGasLimit: 300000, + }, + } + + customAppTemplate := serverconfig.DefaultConfigTemplate + ` +[wasm] +# This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries +query_gas_limit = 300000 +# This is the number of wasm vm instances we keep cached in memory for speed-up +# Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally +lru_size = 0` + + return customAppTemplate, customAppConfig +} + +func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { + cfg := sdk.GetConfig() + cfg.Seal() + + a := appCreator{encodingConfig} + rootCmd.AddCommand( + genutilcli.InitCmd(simapp.ModuleBasics, simapp.DefaultNodeHome), + genutilcli.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome), + genutilcli.MigrateGenesisCmd(), + genutilcli.GenTxCmd(simapp.ModuleBasics, encodingConfig.TxConfig, banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome), + genutilcli.ValidateGenesisCmd(simapp.ModuleBasics), + AddGenesisAccountCmd(simapp.DefaultNodeHome), + tmcli.NewCompletionCmd(rootCmd, true), + debug.Cmd(), + config.Cmd(), + pruning.PruningCmd(a.newApp), + ) + + server.AddCommands(rootCmd, simapp.DefaultNodeHome, a.newApp, a.appExport, addModuleInitFlags) + + // add keybase, auxiliary RPC, query, and tx child commands + rootCmd.AddCommand( + rpc.StatusCommand(), + queryCommand(), + txCommand(), + keys.Commands(simapp.DefaultNodeHome), + ) + + // add rosetta + rootCmd.AddCommand(server.RosettaCommand(encodingConfig.InterfaceRegistry, encodingConfig.Marshaler)) +} + +func addModuleInitFlags(startCmd *cobra.Command) { + crisis.AddModuleInitFlags(startCmd) +} + +func queryCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + authcmd.GetAccountCmd(), + rpc.ValidatorCommand(), + rpc.BlockCommand(), + authcmd.QueryTxsByEventsCmd(), + authcmd.QueryTxCmd(), + ) + + simapp.ModuleBasics.AddQueryCommands(cmd) + cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID") + + return cmd +} + +func txCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + authcmd.GetSignCommand(), + authcmd.GetSignBatchCommand(), + authcmd.GetMultiSignCommand(), + authcmd.GetMultiSignBatchCmd(), + authcmd.GetValidateSignaturesCommand(), + authcmd.GetBroadcastCommand(), + authcmd.GetEncodeCommand(), + authcmd.GetDecodeCommand(), + ) + + simapp.ModuleBasics.AddTxCommands(cmd) + cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID") + + return cmd +} + +type appCreator struct { + encCfg params.EncodingConfig +} + +// newApp is an appCreator +func (a appCreator) newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application { + var cache sdk.MultiStorePersistentCache + + if cast.ToBool(appOpts.Get(server.FlagInterBlockCache)) { + cache = store.NewCommitKVStoreCacheManager() + } + + skipUpgradeHeights := make(map[int64]bool) + for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { + skipUpgradeHeights[int64(h)] = true + } + + pruningOpts, err := server.GetPruningOptionsFromFlags(appOpts) + if err != nil { + panic(err) + } + + snapshotDir := filepath.Join(cast.ToString(appOpts.Get(flags.FlagHome)), "data", "snapshots") + snapshotDB, err := sdk.NewLevelDB("metadata", snapshotDir) + if err != nil { + panic(err) + } + snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir) + if err != nil { + panic(err) + } + + return simapp.New( + logger, db, traceStore, true, skipUpgradeHeights, + cast.ToString(appOpts.Get(flags.FlagHome)), + cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)), + a.encCfg, + appOpts, + baseapp.SetPruning(pruningOpts), + baseapp.SetMinGasPrices(cast.ToString(appOpts.Get(server.FlagMinGasPrices))), + baseapp.SetHaltHeight(cast.ToUint64(appOpts.Get(server.FlagHaltHeight))), + baseapp.SetHaltTime(cast.ToUint64(appOpts.Get(server.FlagHaltTime))), + baseapp.SetMinRetainBlocks(cast.ToUint64(appOpts.Get(server.FlagMinRetainBlocks))), + baseapp.SetInterBlockCache(cache), + baseapp.SetTrace(cast.ToBool(appOpts.Get(server.FlagTrace))), + baseapp.SetIndexEvents(cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents))), + baseapp.SetSnapshotStore(snapshotStore), + baseapp.SetSnapshotInterval(cast.ToUint64(appOpts.Get(server.FlagStateSyncSnapshotInterval))), + baseapp.SetSnapshotKeepRecent(cast.ToUint32(appOpts.Get(server.FlagStateSyncSnapshotKeepRecent))), + baseapp.SetIAVLCacheSize(cast.ToInt(appOpts.Get(server.FlagIAVLCacheSize))), + baseapp.SetIAVLDisableFastNode(cast.ToBool(appOpts.Get(server.FlagDisableIAVLFastNode))), + ) +} + +// appExport creates a new simapp (optionally at a given height) +// and exports state. +func (a appCreator) appExport( + logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailAllowedAddrs []string, + appOpts servertypes.AppOptions, +) (servertypes.ExportedApp, error) { + var simApp *simapp.App + homePath, ok := appOpts.Get(flags.FlagHome).(string) + if !ok || homePath == "" { + return servertypes.ExportedApp{}, errors.New("application home not set") + } + + if height != -1 { + simApp = simapp.New(logger, db, traceStore, false, map[int64]bool{}, homePath, uint(1), a.encCfg, appOpts) + + if err := simApp.LoadHeight(height); err != nil { + return servertypes.ExportedApp{}, err + } + } else { + simApp = simapp.New(logger, db, traceStore, true, map[int64]bool{}, homePath, uint(1), a.encCfg, appOpts) + } + + return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs) +} diff --git a/cmd/interchain-security-sd/main.go b/cmd/interchain-security-sd/main.go new file mode 100644 index 0000000000..6b43e7b396 --- /dev/null +++ b/cmd/interchain-security-sd/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "os" + + "github.com/cosmos/cosmos-sdk/server" + svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + app "github.com/cosmos/interchain-security/v2/app/sovereign" + "github.com/cosmos/interchain-security/v2/cmd/interchain-security-sd/cmd" +) + +func main() { + rootCmd, _ := cmd.NewRootCmd() + + if err := svrcmd.Execute(rootCmd, app.DefaultNodeHome); err != nil { + switch e := err.(type) { + case server.ErrorCode: + os.Exit(e.Code) + + default: + os.Exit(1) + } + } +} diff --git a/docs/docs/consumer-development/onboarding.md b/docs/docs/consumer-development/onboarding.md index faa2969e63..89c5f32dc5 100644 --- a/docs/docs/consumer-development/onboarding.md +++ b/docs/docs/consumer-development/onboarding.md @@ -91,6 +91,13 @@ Example of a consumer chain addition proposal. // This param is a part of the cosmos sdk staking module. In the case of // a ccv enabled consumer chain, the ccv module acts as the staking module. "historical_entries": 10000, + // The ID of a token transfer channel used for the Reward Distribution + // sub-protocol. If DistributionTransmissionChannel == "", a new transfer + // channel is created on top of the same connection as the CCV channel. + // Note that transfer_channel_id is the ID of the channel end on the consumer chain. + // it is most relevant for chains performing a sovereign to consumer changeover + // in order to maintan the existing ibc transfer channel + "distribution_transmission_channel": "channel-123" } ``` diff --git a/docs/docs/features/proposals.md b/docs/docs/features/proposals.md index 9285baa62b..a34160ecf4 100644 --- a/docs/docs/features/proposals.md +++ b/docs/docs/features/proposals.md @@ -43,6 +43,9 @@ Minimal example: "historical_entries": 10000, "genesis_hash": "d86d756e10118e66e6805e9cc476949da2e750098fcc7634fd0cc77f57a0b2b0", "binary_hash": "376cdbd3a222a3d5c730c9637454cd4dd925e2f9e2e0d0f3702fc922928583f1" + // relevant for chains performing a sovereign to consumer changeover + // in order to maintan the existing ibc transfer channel + "distribution_transmission_channel": "channel-123" } ``` More examples can be found in the replicated security testnet repository [here](https://github.com/cosmos/testnets/blob/master/replicated-security/baryon-1/proposal-baryon-1.json) and [here](https://github.com/cosmos/testnets/blob/master/replicated-security/noble-1/start-proposal-noble-1.json). diff --git a/proto/interchain_security/ccv/consumer/v1/genesis.proto b/proto/interchain_security/ccv/consumer/v1/genesis.proto index bf019879ac..578518086c 100644 --- a/proto/interchain_security/ccv/consumer/v1/genesis.proto +++ b/proto/interchain_security/ccv/consumer/v1/genesis.proto @@ -34,12 +34,12 @@ message GenesisState { // OutstandingDowntimes nil on new chain, filled in on restart. repeated OutstandingDowntime outstanding_downtime_slashing = 10 [ (gogoproto.nullable) = false ]; - // PendingConsumerPackets nil on new chain, filled in on restart. - interchain_security.ccv.v1.ConsumerPacketDataList pending_consumer_packets = 11 - [ (gogoproto.nullable) = false ]; + // PendingConsumerPackets nil on new chain, filled in on restart. + interchain_security.ccv.v1.ConsumerPacketDataList pending_consumer_packets = 11 + [ (gogoproto.nullable) = false ]; // LastTransmissionBlockHeight nil on new chain, filled in on restart. interchain_security.ccv.consumer.v1.LastTransmissionBlockHeight last_transmission_block_height = 12 - [ (gogoproto.nullable) = false ]; + [ (gogoproto.nullable) = false ]; bool preCCV = 13; // flag indicating whether the consumer CCV module starts in pre-CCV state } diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index 743d2c9c33..d190b3a6ca 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -62,6 +62,13 @@ message ConsumerAdditionProposal { // This param is a part of the cosmos sdk staking module. In the case of // a ccv enabled consumer chain, the ccv module acts as the staking module. int64 historical_entries = 13; + // The ID of a token transfer channel used for the Reward Distribution + // sub-protocol. If DistributionTransmissionChannel == "", a new transfer + // channel is created on top of the same connection as the CCV channel. + // Note that transfer_channel_id is the ID of the channel end on the consumer chain. + // it is most relevant for chains performing a sovereign to consumer changeover + // in order to maintan the existing ibc transfer channel + string distribution_transmission_channel = 14; } // ConsumerRemovalProposal is a governance proposal on the provider chain to remove (and stop) a consumer chain. diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index b1e6863b7a..06bcb7f5f7 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -209,12 +209,14 @@ func (tr TestRun) submitTextProposal( } type submitConsumerAdditionProposalAction struct { - chain chainID - from validatorID - deposit uint - consumerChain chainID - spawnTime uint - initialHeight clienttypes.Height + preCCV bool + chain chainID + from validatorID + deposit uint + consumerChain chainID + spawnTime uint + initialHeight clienttypes.Height + distributionChannel string } func (tr TestRun) submitConsumerAdditionProposal( @@ -238,6 +240,7 @@ func (tr TestRun) submitConsumerAdditionProposal( TransferTimeoutPeriod: params.TransferTimeoutPeriod, UnbondingPeriod: params.UnbondingPeriod, Deposit: fmt.Sprint(action.deposit) + `stake`, + DistributionTransmissionChannel: action.distributionChannel, } bz, err := json.Marshal(prop) @@ -565,6 +568,133 @@ func (tr TestRun) startConsumerChain( }, verbose) } +type ChangeoverChainAction struct { + sovereignChain chainID + providerChain chainID + validators []StartChainValidator + genesisChanges string +} + +func (tr TestRun) changeoverChain( + action ChangeoverChainAction, + verbose bool, +) { + // sleep until the consumer chain genesis is ready on consumer + time.Sleep(5 * time.Second) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, tr.chainConfigs[action.providerChain].binaryName, + + "query", "provider", "consumer-genesis", + string(tr.chainConfigs[action.sovereignChain].chainId), + + `--node`, tr.getQueryNode(action.providerChain), + `-o`, `json`, + ) + + if verbose { + log.Println("changeoverChain cmd: ", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + consumerGenesis := ".app_state.ccvconsumer = " + string(bz) + consumerGenesisChanges := tr.chainConfigs[action.sovereignChain].genesisChanges + if consumerGenesisChanges != "" { + consumerGenesis = consumerGenesis + " | " + consumerGenesisChanges + " | " + action.genesisChanges + } + + tr.startChangeover(ChangeoverChainAction{ + validators: action.validators, + genesisChanges: consumerGenesis, + }, verbose) +} + +func (tr TestRun) startChangeover( + action ChangeoverChainAction, + verbose bool, +) { + chainConfig := tr.chainConfigs[chainID("sover")] + type jsonValAttrs struct { + Mnemonic string `json:"mnemonic"` + Allocation string `json:"allocation"` + Stake string `json:"stake"` + ValId string `json:"val_id"` + PrivValidatorKey string `json:"priv_validator_key"` + NodeKey string `json:"node_key"` + IpSuffix string `json:"ip_suffix"` + + ConsumerMnemonic string `json:"consumer_mnemonic"` + ConsumerPrivValidatorKey string `json:"consumer_priv_validator_key"` + StartWithConsumerKey bool `json:"start_with_consumer_key"` + } + + var validators []jsonValAttrs + for _, val := range action.validators { + validators = append(validators, jsonValAttrs{ + Mnemonic: tr.validatorConfigs[val.id].mnemonic, + NodeKey: tr.validatorConfigs[val.id].nodeKey, + ValId: fmt.Sprint(val.id), + PrivValidatorKey: tr.validatorConfigs[val.id].privValidatorKey, + Allocation: fmt.Sprint(val.allocation) + "stake", + Stake: fmt.Sprint(val.stake) + "stake", + IpSuffix: tr.validatorConfigs[val.id].ipSuffix, + + ConsumerMnemonic: tr.validatorConfigs[val.id].consumerMnemonic, + ConsumerPrivValidatorKey: tr.validatorConfigs[val.id].consumerPrivValidatorKey, + // if true node will be started with consumer key for each consumer chain + StartWithConsumerKey: tr.validatorConfigs[val.id].useConsumerKey, + }) + } + + vals, err := json.Marshal(validators) + if err != nil { + log.Fatal(err) + } + + // Concat genesis changes defined in chain config, with any custom genesis changes for this chain instantiation + var genesisChanges string + if action.genesisChanges != "" { + genesisChanges = chainConfig.genesisChanges + " | " + action.genesisChanges + } else { + genesisChanges = chainConfig.genesisChanges + } + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash", + "/testnet-scripts/start-changeover.sh", chainConfig.upgradeBinary, string(vals), + "sover", chainConfig.ipPrefix, genesisChanges, + tr.tendermintConfigOverride, + ) + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("startChangeover: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal("startChangeover died", err) + } +} + type addChainToRelayerAction struct { chain chainID validator validatorID @@ -683,6 +813,7 @@ func (tr TestRun) addChainToHermes( keyName, rpcAddr, wsAddr, + // action.consumer, ) bashCommand := fmt.Sprintf(`echo '%s' >> %s`, chainConfig, "/root/.hermes/config.toml") @@ -805,6 +936,51 @@ func (tr TestRun) addIbcConnectionGorelayer( tr.waitBlocks(action.chainB, 1, 10*time.Second) } +type createIbcClientsAction struct { + chainA chainID + chainB chainID +} + +// if clients are not provided hermes will first +// create new clients and then a new connection +// otherwise, it would use client provided as CLI argument (-a-client) +func (tr TestRun) createIbcClientsHermes( + action createIbcClientsAction, + verbose bool, +) { + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", + "create", "connection", + "--a-chain", string(tr.chainConfigs[action.chainA].chainId), + "--b-chain", string(tr.chainConfigs[action.chainB].chainId), + ) + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("createIbcClientsHermes: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } +} + func (tr TestRun) addIbcConnectionHermes( action addIbcConnectionAction, verbose bool, @@ -850,6 +1026,7 @@ type addIbcChannelAction struct { portA string portB string order string + version string } type startRelayerAction struct{} @@ -936,6 +1113,13 @@ func (tr TestRun) addIbcChannelHermes( action addIbcChannelAction, verbose bool, ) { + // if version is not specified, use the default version when creating ccv connections + // otherwise, use the provided version schema (usually it is ICS20-1 for IBC transfer) + chanVersion := action.version + if chanVersion == "" { + chanVersion = tr.containerConfig.ccvVersion + } + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", "create", "channel", @@ -943,7 +1127,7 @@ func (tr TestRun) addIbcChannelHermes( "--a-connection", "connection-"+fmt.Sprint(action.connectionA), "--a-port", action.portA, "--b-port", action.portB, - "--channel-version", tr.containerConfig.ccvVersion, + "--channel-version", chanVersion, "--order", action.order, ) diff --git a/tests/e2e/actions_sovereign_chain.go b/tests/e2e/actions_sovereign_chain.go new file mode 100644 index 0000000000..c61a2a9a38 --- /dev/null +++ b/tests/e2e/actions_sovereign_chain.go @@ -0,0 +1,166 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "log" + "os/exec" + "time" +) + +type StartSovereignChainAction struct { + chain chainID + validators []StartChainValidator + // Genesis changes specific to this action, appended to genesis changes defined in chain config + genesisChanges string +} + +// calls a simplified startup script (start-sovereign.sh) and runs a validator node +// upgrades are simpler with a single validator node since only one node needs to be upgraded +func (tr TestRun) startSovereignChain( + action StartSovereignChainAction, + verbose bool, +) { + chainConfig := tr.chainConfigs["sover"] + type jsonValAttrs struct { + Mnemonic string `json:"mnemonic"` + Allocation string `json:"allocation"` + Stake string `json:"stake"` + ValId string `json:"val_id"` + PrivValidatorKey string `json:"priv_validator_key"` + NodeKey string `json:"node_key"` + IpSuffix string `json:"ip_suffix"` + + ConsumerMnemonic string `json:"consumer_mnemonic"` + ConsumerPrivValidatorKey string `json:"consumer_priv_validator_key"` + StartWithConsumerKey bool `json:"start_with_consumer_key"` + } + + var validators []jsonValAttrs + for _, val := range action.validators { + validators = append(validators, jsonValAttrs{ + Mnemonic: tr.validatorConfigs[val.id].mnemonic, + NodeKey: tr.validatorConfigs[val.id].nodeKey, + ValId: fmt.Sprint(val.id), + PrivValidatorKey: tr.validatorConfigs[val.id].privValidatorKey, + Allocation: fmt.Sprint(val.allocation) + "stake", + Stake: fmt.Sprint(val.stake) + "stake", + IpSuffix: tr.validatorConfigs[val.id].ipSuffix, + + ConsumerMnemonic: tr.validatorConfigs[val.id].consumerMnemonic, + ConsumerPrivValidatorKey: tr.validatorConfigs[val.id].consumerPrivValidatorKey, + // if true node will be started with consumer key for each consumer chain + StartWithConsumerKey: tr.validatorConfigs[val.id].useConsumerKey, + }) + } + + vals, err := json.Marshal(validators) + if err != nil { + log.Fatal(err) + } + + // Concat genesis changes defined in chain config, with any custom genesis changes for this chain instantiation + var genesisChanges string + if action.genesisChanges != "" { + genesisChanges = chainConfig.genesisChanges + " | " + action.genesisChanges + } else { + genesisChanges = chainConfig.genesisChanges + } + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash", + "/testnet-scripts/start-sovereign.sh", chainConfig.binaryName, string(vals), + string(chainConfig.chainId), chainConfig.ipPrefix, genesisChanges, + tr.tendermintConfigOverride, + ) + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("startSovereignChain: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + tr.addChainToRelayer(addChainToRelayerAction{ + chain: action.chain, + validator: action.validators[0].id, + }, verbose) +} + +type UpgradeProposalAction struct { + chainID chainID + upgradeTitle string + proposer validatorID + upgradeHeight uint64 +} + +func (tr *TestRun) submitUpgradeProposal(action UpgradeProposalAction, verbose bool) { + submit := fmt.Sprintf( + `%s tx gov submit-proposal software-upgrade %s \ + --title %s \ + --deposit 10000000stake \ + --upgrade-height %s \ + --upgrade-info "perform changeover" \ + --description "perform changeover" \ + --gas 900000 \ + --from validator%s \ + --keyring-backend test \ + --chain-id %s \ + --home %s \ + --node %s \ + -b block \ + -y`, + tr.chainConfigs[chainID("sover")].binaryName, + action.upgradeTitle, + action.upgradeTitle, + fmt.Sprint(action.upgradeHeight), + action.proposer, + tr.chainConfigs[chainID("sover")].chainId, + tr.getValidatorHome(chainID("sover"), action.proposer), + tr.getValidatorNode(chainID("sover"), action.proposer), + ) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", + tr.containerConfig.instanceName, + "/bin/bash", "-c", + submit, + ) + + if verbose { + fmt.Println("submitUpgradeProposal cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } +} + +type waitUntilBlockAction struct { + block uint + chain chainID +} + +func (tr *TestRun) waitUntilBlockOnChain(action waitUntilBlockAction) { + fmt.Println("waitUntilBlockOnChain is waiting for block:", action.block) + tr.waitUntilBlock(action.chain, action.block, 120*time.Second) + fmt.Println("waitUntilBlockOnChain done waiting for block:", action.block) +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index cc755cb97a..3038f69f4e 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -47,6 +47,9 @@ type ChainConfig struct { // Example: ".app_state.gov.voting_params.voting_period = \"5s\" | .app_state.slashing.params.signed_blocks_window = \"2\" | .app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\"" genesisChanges string binaryName string + + // binary to use after upgrade height + upgradeBinary string } type ContainerConfig struct { @@ -328,6 +331,51 @@ func MultiConsumerTestRun() TestRun { } } +func ChangeoverTestRun() TestRun { + return TestRun{ + name: "changeover", + containerConfig: ContainerConfig{ + containerName: "interchain-security-changeover-container", + instanceName: "interchain-security-changeover-instance", + ccvVersion: "1", + now: time.Now(), + }, + validatorConfigs: getDefaultValidators(), + chainConfigs: map[chainID]ChainConfig{ + chainID("provi"): { + chainId: chainID("provi"), + binaryName: "interchain-security-pd", + ipPrefix: "7.7.7", + votingWaitTime: 20, + genesisChanges: ".app_state.gov.voting_params.voting_period = \"20s\" | " + + // Custom slashing parameters for testing validator downtime functionality + // See https://docs.cosmos.network/main/modules/slashing/04_begin_block.html#uptime-tracking + ".app_state.slashing.params.signed_blocks_window = \"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("sover"): { + chainId: chainID("sover"), + binaryName: "interchain-security-sd", + upgradeBinary: "interchain-security-cdd", + 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\" | " + + ".app_state.staking.params.unbonding_time = \"1728000s\"", // making the genesis unbonding time equal to unbonding time in the consumer addition proposal + }, + }, + tendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + + `s/peer_gossip_sleep_duration = "100ms"/peer_gossip_sleep_duration = "50ms"/;`, + } +} + 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 141f8a8cd8..737a318de2 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -57,6 +57,7 @@ func main() { } testRuns := []testRunWithSteps{ + {ChangeoverTestRun(), changeoverSteps}, {DefaultTestRun(), happyPathSteps}, {DemocracyTestRun(true), democracySteps}, {DemocracyTestRun(false), rewardDenomConsumerSteps}, @@ -112,6 +113,14 @@ func (tr *TestRun) runStep(step Step, verbose bool) { switch action := step.action.(type) { case StartChainAction: tr.startChain(action, verbose) + case StartSovereignChainAction: + tr.startSovereignChain(action, verbose) + case UpgradeProposalAction: + tr.submitUpgradeProposal(action, verbose) + case waitUntilBlockAction: + tr.waitUntilBlockOnChain(action) + case ChangeoverChainAction: + tr.changeoverChain(action, verbose) case SendTokensAction: tr.sendTokens(action, verbose) case submitTextProposalAction: @@ -130,6 +139,8 @@ func (tr *TestRun) runStep(step Step, verbose bool) { tr.startConsumerChain(action, verbose) case addChainToRelayerAction: tr.addChainToRelayer(action, verbose) + case createIbcClientsAction: + tr.createIbcClientsHermes(action, verbose) case addIbcConnectionAction: tr.addIbcConnection(action, verbose) case addIbcChannelAction: diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 9127c65df8..15500dd01f 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -50,6 +50,17 @@ type ConsumerAdditionProposal struct { Status string } +type UpgradeProposal struct { + Title string + Description string + UpgradeHeight uint64 + Type string + Deposit uint + Status string +} + +func (p UpgradeProposal) isProposal() {} + func (p ConsumerAdditionProposal) isProposal() {} type ConsumerRemovalProposal struct { @@ -214,6 +225,20 @@ func (tr TestRun) waitBlocks(chain chainID, blocks uint, timeout time.Duration) } } +func (tr TestRun) waitUntilBlock(chain chainID, block uint, timeout time.Duration) { + start := time.Now() + for { + thisBlock := tr.getBlockHeight(chain) + if thisBlock >= block { + return + } + if time.Since(start) > timeout { + panic(fmt.Sprintf("\n\n\nwaitBlocks method has timed out after: %s\n\n", timeout)) + } + time.Sleep(500 * time.Millisecond) + } +} + func (tr TestRun) getBalances(chain chainID, modelState map[validatorID]uint) map[validatorID]uint { actualState := map[validatorID]uint{} for k := range modelState { @@ -390,6 +415,16 @@ func (tr TestRun) getProposal(chain chainID, proposal uint) Proposal { RevisionHeight: gjson.Get(string(bz), `content.initial_height.revision_height`).Uint(), }, } + case "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal": + height := gjson.Get(string(bz), `content.plan.height`).Uint() + title := gjson.Get(string(bz), `content.plan.name`).String() + return UpgradeProposal{ + Deposit: uint(deposit), + Status: status, + UpgradeHeight: height, + Title: title, + Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", + } case "/interchain_security.ccv.provider.v1.ConsumerRemovalProposal": chainId := gjson.Get(string(bz), `content.chain_id`).String() stopTime := gjson.Get(string(bz), `content.stop_time`).Time().Sub(tr.containerConfig.now) @@ -691,7 +726,14 @@ func (tr TestRun) getQueryNode(chain chainID) string { } // getQueryNodeIP returns query node IP for chain, -// ipSuffix is hardcoded to be 253 on all query nodes. +// ipSuffix is hardcoded to be 253 on all query nodes +// except for "sover" chain where there's only one node func (tr TestRun) getQueryNodeIP(chain chainID) string { + if chain == chainID("sover") { + // return address of first and only validator + return fmt.Sprintf("%s.%s", + tr.chainConfigs[chain].ipPrefix, + tr.validatorConfigs[validatorID("alice")].ipSuffix) + } return fmt.Sprintf("%s.253", tr.chainConfigs[chain].ipPrefix) } diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 8c0b4b2661..7613b05558 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -72,3 +72,18 @@ var multipleConsumers = concatSteps( stepsMultiConsumerDowntimeFromProvider("consu", "densu"), stepsMultiConsumerDoubleSign("consu", "densu"), // double sign on one of the chains ) + +var changeoverSteps = concatSteps( + // start sovereign chain and test delegation operation + + stepRunSovereignChain(), + stepStartProviderChain(), + stepsSovereignTransferChan(), + + // the chain will halt once upgrade height is reached + // after upgrade height is reached, the chain will become a consumer + stepsUpgradeChain(), + stepsChangeoverToConsumer("sover"), + + stepsPostChangeoverDelegate("sover"), +) diff --git a/tests/e2e/steps_sovereign_changeover.go b/tests/e2e/steps_sovereign_changeover.go new file mode 100644 index 0000000000..e122c1fb2a --- /dev/null +++ b/tests/e2e/steps_sovereign_changeover.go @@ -0,0 +1,367 @@ +package main + +import clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" + +// this creates new clients on both chains and a connection (connection-0) between them +// connection-0 is used to create a transfer channel between the chains +// the transfer channel is maintained during the changeover process, meaning that +// the consumer chain will be able to send rewards to the provider chain using the old channel +// as opposed to creating a new transfer channel which happens for new consumers +func stepsSovereignTransferChan() []Step { + return []Step{ + { + action: createIbcClientsAction{ + chainA: chainID("sover"), + chainB: chainID("provi"), + }, + state: State{}, + }, + { + // this will create channel-0 connection end on both chain + action: addIbcChannelAction{ + chainA: chainID("sover"), + chainB: chainID("provi"), + connectionA: 0, + portA: "transfer", + portB: "transfer", + order: "unordered", + version: "ics20-1", + }, + state: State{}, + }, + } +} + +// steps to convert sovereign to consumer chain +func stepsChangeoverToConsumer(consumerName string) []Step { + s := []Step{ + { + action: submitConsumerAdditionProposalAction{ + preCCV: true, + chain: chainID("provi"), + from: validatorID("alice"), + deposit: 10000001, + consumerChain: chainID(consumerName), + // chain-0 is the transfer channelID that gets created in stepsSovereignTransferChan + // the consumer chain will use this channel to send rewards to the provider chain + // there is no need to create a new channel for rewards distribution + distributionChannel: "channel-0", + spawnTime: 0, + initialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 111}, // 1 block after upgrade !important + }, + state: State{ + chainID("provi"): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9489999999, + validatorID("bob"): 9500000000, + }, + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: chainID(consumerName), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 111}, + Status: "PROPOSAL_STATUS_VOTING_PERIOD", + }, + }, + }, + }, + }, + { + action: voteGovProposalAction{ + chain: chainID("provi"), + from: []validatorID{validatorID("alice"), validatorID("bob"), validatorID("carol")}, + vote: []string{"yes", "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: 111}, + Status: "PROPOSAL_STATUS_PASSED", + }, + }, + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9500000000, + validatorID("bob"): 9500000000, + }, + }, + }, + }, + { + action: ChangeoverChainAction{ + sovereignChain: chainID(consumerName), + providerChain: chainID("provi"), + validators: []StartChainValidator{ + {id: validatorID("alice"), stake: 500000000, allocation: 10000000000}, + {id: validatorID("bob"), stake: 500000000, allocation: 10000000000}, + {id: validatorID("carol"), stake: 500000000, allocation: 10000000000}, + }, + genesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + // uses val powers from consumer + validatorID("alice"): 500, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: addIbcConnectionAction{ + chainA: chainID(consumerName), + chainB: chainID("provi"), + clientA: 1, + clientB: 1, + }, + state: State{}, + }, + { + action: addIbcChannelAction{ + chainA: chainID(consumerName), + chainB: chainID("provi"), + connectionA: 1, + portA: "consumer", + portB: "provider", + order: "ordered", + }, + state: State{}, + }, + } + + return s +} + +// start sovereign chain with a single validator so it is easier to manage +// when the chain is converted to a consumer chain the validators from the +// consumer chain will be used +// validatoralice is the only validator on the sovereign chain that is in both +// sovereign validator set and consumer validator set +func stepRunSovereignChain() []Step { + return []Step{ + { + action: StartSovereignChainAction{ + chain: chainID("sover"), + validators: []StartChainValidator{ + {id: validatorID("alice"), stake: 500000000, allocation: 10000000000}, + }, + }, + state: State{ + chainID("sover"): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9500000000, + }, + }, + }, + }, + { + action: delegateTokensAction{ + chain: chainID("sover"), + from: validatorID("alice"), + to: validatorID("alice"), + amount: 11000000, + }, + state: State{ + chainID("sover"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 0, // does not exist on pre-ccv sover + validatorID("carol"): 0, // does not exist on pre-ccv sover + }, + }, + }, + }, + } +} + +// TODO: use args instead of hardcoding +func stepsUpgradeChain() []Step { + return []Step{ + { + action: UpgradeProposalAction{ + chainID: chainID("sover"), + upgradeTitle: "sovereign-changeover", + proposer: validatorID("alice"), + upgradeHeight: 110, + }, + state: State{ + chainID("sover"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: UpgradeProposal{ + Title: "sovereign-changeover", + UpgradeHeight: 110, + Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", + Deposit: 10000000, + Status: "PROPOSAL_STATUS_VOTING_PERIOD", + }, + }, + }, + }, + }, + { + action: voteGovProposalAction{ + chain: chainID("sover"), + from: []validatorID{validatorID("alice")}, + vote: []string{"yes"}, + propNumber: 1, + }, + state: State{ + chainID("sover"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: UpgradeProposal{ + Deposit: 10000000, + UpgradeHeight: 110, + Title: "sovereign-changeover", + Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", + Status: "PROPOSAL_STATUS_PASSED", + }, + }, + }, + }, + }, + { + action: waitUntilBlockAction{ + chain: chainID("sover"), + block: 110, + }, + state: State{}, + }, + } +} + +// stepsPostChangeoverDelegate tests basic delegation and resulting validator power changes after changeover +// we cannot use stepsDelegate and stepsUnbond because they make assumptions about which connection to use +// here we need to use connection-1, and in tests with new consumers connection-0 is used because the chain is new (has no IBC states prior to launch) +func stepsPostChangeoverDelegate(consumerName string) []Step { + return []Step{ + { + action: SendTokensAction{ + chain: chainID(consumerName), + from: validatorID("alice"), + to: validatorID("bob"), + amount: 100, + }, + state: State{ + chainID(consumerName): ChainState{ + // Tx should not go through, ICS channel is not setup until first VSC packet has been relayed to consumer + ValBalances: &map[validatorID]uint{ + validatorID("bob"): 0, + }, + }, + }, + }, + { + 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"): 500, + validatorID("carol"): 500, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: relayPacketsAction{ + chainA: chainID("provi"), + chainB: chainID(consumerName), + port: "provider", + channel: 1, + }, + state: State{ + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: SendTokensAction{ + chain: chainID(consumerName), + from: validatorID("alice"), + to: validatorID("bob"), + amount: 100, + }, + state: State{ + chainID(consumerName): ChainState{ + // Tx should go through, ICS channel is setup + ValBalances: &map[validatorID]uint{ + validatorID("bob"): 100, + }, + }, + }, + }, + { + action: unbondTokensAction{ + chain: chainID("provi"), + unbondFrom: validatorID("alice"), + sender: validatorID("alice"), + amount: 1000000, + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 510, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + // Voting power on consumer should not be affected yet + validatorID("alice"): 511, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: relayPacketsAction{ + chainA: chainID("provi"), + chainB: chainID(consumerName), + port: "provider", + channel: 1, + }, + state: State{ + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 510, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + } +} diff --git a/tests/e2e/testnet-scripts/sovereign-genesis.json b/tests/e2e/testnet-scripts/sovereign-genesis.json new file mode 100644 index 0000000000..94a279840e --- /dev/null +++ b/tests/e2e/testnet-scripts/sovereign-genesis.json @@ -0,0 +1,279 @@ +{ + "genesis_time": "2023-06-13T11:19:05.998449459Z", + "chain_id": "sover", + "initial_height": "1", + "consensus_params": { + "block": { + "max_bytes": "22020096", + "max_gas": "-1", + "time_iota_ms": "1000" + }, + "evidence": { + "max_age_num_blocks": "100000", + "max_age_duration": "172800000000000", + "max_bytes": "1048576" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + }, + "version": {} + }, + "app_hash": "", + "app_state": { + "auth": { + "params": { + "max_memo_characters": "256", + "tx_sig_limit": "7", + "tx_size_cost_per_byte": "10", + "sig_verify_cost_ed25519": "590", + "sig_verify_cost_secp256k1": "1000" + }, + "accounts": [ + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + "pub_key": null, + "account_number": "0", + "sequence": "0" + } + ] + }, + "authz": { + "authorization": [] + }, + "bank": { + "params": { + "send_enabled": [], + "default_send_enabled": true + }, + "balances": [ + { + "address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + "coins": [ + { + "denom": "stake", + "amount": "10000000000" + } + ] + } + ], + "supply": [ + { + "denom": "stake", + "amount": "10000000000" + } + ], + "denom_metadata": [] + }, + "capability": { + "index": "1", + "owners": [] + }, + "crisis": { + "constant_fee": { + "denom": "stake", + "amount": "1000" + } + }, + "distribution": { + "params": { + "community_tax": "0.020000000000000000", + "base_proposer_reward": "0.010000000000000000", + "bonus_proposer_reward": "0.040000000000000000", + "withdraw_addr_enabled": true + }, + "fee_pool": { + "community_pool": [] + }, + "delegator_withdraw_infos": [], + "previous_proposer": "", + "outstanding_rewards": [], + "validator_accumulated_commissions": [], + "validator_historical_rewards": [], + "validator_current_rewards": [], + "delegator_starting_infos": [], + "validator_slash_events": [] + }, + "evidence": { + "evidence": [] + }, + "feegrant": { + "allowances": [] + }, + "genutil": { + "gen_txs": [ + { + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgCreateValidator", + "description": { + "moniker": "validatoralice", + "identity": "", + "website": "", + "security_contact": "", + "details": "" + }, + "commission": { + "rate": "0.100000000000000000", + "max_rate": "0.200000000000000000", + "max_change_rate": "0.010000000000000000" + }, + "min_self_delegation": "1", + "delegator_address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + "validator_address": "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", + "pubkey": { + "@type": "/cosmos.crypto.ed25519.PubKey", + "key": "RrclQz9bIhkIy/gfL485g3PYMeiIku4qeo495787X10=" + }, + "value": { + "denom": "stake", + "amount": "500000000" + } + } + ], + "memo": "8339e14baab81c2a2350e261962263397a8d7fb0@7.7.8.254:26656", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AsFC8tmbGGQSHthsVStbsQ13/+Yza9IT8KCSXXEN7y9f" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_DIRECT" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [], + "gas_limit": "200000", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "rZuml3RLgrrZkUoNHw90FuHF/Orxzs0uiwflCkUOcvoA4bzohisjdQhkPWCn5aRw30mqZJGj1IxgXS15XleMvQ==" + ] + } + ] + }, + "gov": { + "starting_proposal_id": "1", + "deposits": [], + "votes": [], + "proposals": [], + "deposit_params": { + "min_deposit": [ + { + "denom": "stake", + "amount": "10000000" + } + ], + "max_deposit_period": "172800s" + }, + "voting_params": { + "voting_period": "20s" + }, + "tally_params": { + "quorum": "0.334000000000000000", + "threshold": "0.500000000000000000", + "veto_threshold": "0.334000000000000000" + } + }, + "ibc": { + "client_genesis": { + "clients": [], + "clients_consensus": [], + "clients_metadata": [], + "params": { + "allowed_clients": [ + "06-solomachine", + "07-tendermint" + ] + }, + "create_localhost": false, + "next_client_sequence": "0" + }, + "connection_genesis": { + "connections": [], + "client_connection_paths": [], + "next_connection_sequence": "0", + "params": { + "max_expected_time_per_block": "30000000000" + } + }, + "channel_genesis": { + "channels": [], + "acknowledgements": [], + "commitments": [], + "receipts": [], + "send_sequences": [], + "recv_sequences": [], + "ack_sequences": [], + "next_channel_sequence": "0" + } + }, + "mint": { + "minter": { + "inflation": "0.130000000000000000", + "annual_provisions": "0.000000000000000000" + }, + "params": { + "mint_denom": "stake", + "inflation_rate_change": "0.130000000000000000", + "inflation_max": "0.200000000000000000", + "inflation_min": "0.070000000000000000", + "goal_bonded": "0.670000000000000000", + "blocks_per_year": "6311520" + } + }, + "params": null, + "slashing": { + "params": { + "signed_blocks_window": "15", + "min_signed_per_window": "0.500000000000000000", + "downtime_jail_duration": "2s", + "slash_fraction_double_sign": "0.050000000000000000", + "slash_fraction_downtime": "0.010000000000000000" + }, + "signing_infos": [], + "missed_blocks": [] + }, + "staking": { + "params": { + "unbonding_time": "1814400s", + "max_validators": 100, + "max_entries": 7, + "historical_entries": 10000, + "bond_denom": "stake" + }, + "last_total_power": "0", + "last_validator_powers": [], + "validators": [], + "delegations": [], + "unbonding_delegations": [], + "redelegations": [], + "exported": false + }, + "transfer": { + "port_id": "transfer", + "denom_traces": [], + "params": { + "send_enabled": true, + "receive_enabled": true + } + }, + "upgrade": {}, + "vesting": {} + } + } \ No newline at end of file diff --git a/tests/e2e/testnet-scripts/start-changeover.sh b/tests/e2e/testnet-scripts/start-changeover.sh new file mode 100644 index 0000000000..d28479a8e7 --- /dev/null +++ b/tests/e2e/testnet-scripts/start-changeover.sh @@ -0,0 +1,187 @@ +#!/bin/bash +set -eux + +# The gaiad binary +BIN=$1 + +# JSON array of validator information +# [{ +# mnemonic: "crackle snap pop ... etc", +# allocation: "10000000000stake,10000000000footoken", +# stake: "5000000000stake", +# val_id: "alice", +# ip_suffix: "1", +# priv_validator_key: "{\"address\": \"3566F464673B2F069758DAE86FC25D04017BB147\",\"pub_key\": {\"type\": \"tendermint/PubKeyEd25519\",\"value\": \"XrLjKdc4mB2gfqplvnoySjSJq2E90RynUwaO3WhJutk=\"},\"priv_key\": {\"type\": \"tendermint/PrivKeyEd25519\",\"value\": \"czGSLs/Ocau8aJ5J5zQHMxf3d7NR0xjMECN6YGTIWqtesuMp1ziYHaB+qmW+ejJKNImrYT3RHKdTBo7daEm62Q==\"}}" +# node_key: "{\"priv_key\":{\"type\":\"tendermint/PrivKeyEd25519\",\"value\":\"alIHj6hXnzpLAadgb7+E2eeecwxoNdzuZrfhMX36EaD5/LgzL0ZUoVp7AK3np0K5T35JWLLv0jJKmeRIhG0GjA==\"}}" +# }, ... ] +VALIDATORS=$2 + +# The chain ID +CHAIN_ID=$3 + +# This is the first 3 fields of the IP addresses which will be used internally by the validators of this blockchain +# Recommended to use something starting with 7, since it is squatted by the DoD and is unroutable on the internet +# For example: "7.7.7" +CHAIN_IP_PREFIX=$4 + +# A transformation to apply to the genesis file, as a jq string +GENESIS_TRANSFORM=$5 + +# A sed string modifying the tendermint config +TENDERMINT_CONFIG_TRANSFORM=$6 + + +# CREATE VALIDATORS AND DO GENESIS CEREMONY +# !!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!! # +# data dir from the sovereign chain is copied to other nodes (namely bob and carol) +# alice simply performs a chain upgrade +echo "killing nodes" +pkill -f "^"interchain-security-sd &> /dev/null || true + +mkdir -p /root/.sovereign/config + +# apply genesis changes to existing genesis -> this creates the changeover genesis file wih intial validator set +jq "$GENESIS_TRANSFORM" /sover/validatoralice/config/genesis.json > /root/.sovereign/config/genesis.json + + +# Get number of nodes from length of validators array +NODES=$(echo "$VALIDATORS" | jq '. | length') + +# SETUP NETWORK NAMESPACES, see: https://adil.medium.com/container-networking-under-the-hood-network-namespaces-6b2b8fe8dc2a + +# Create virtual bridge device (acts like a switch) +ip link add name virtual-bridge type bridge || true + +for i in $(seq 0 $(($NODES - 1))); +do + # first validator is already setup + if [[ "$i" -ne 0 ]]; then + VAL_ID=$(echo "$VALIDATORS" | jq -r ".[$i].val_id") + VAL_IP_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[$i].ip_suffix") + NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" + IP_ADDR="$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX/24" + + # Create network namespace + ip netns add $NET_NAMESPACE_NAME + # Create virtual ethernet device to connect with bridge + ip link add $NET_NAMESPACE_NAME-in type veth peer name $NET_NAMESPACE_NAME-out + # Connect input end of virtual ethernet device to namespace + ip link set $NET_NAMESPACE_NAME-in netns $NET_NAMESPACE_NAME + # Assign ip address to namespace + ip netns exec $NET_NAMESPACE_NAME ip addr add $IP_ADDR dev $NET_NAMESPACE_NAME-in + # Connect output end of virtual ethernet device to bridge + ip link set $NET_NAMESPACE_NAME-out master virtual-bridge + fi +done + +# Enable bridge interface +ip link set virtual-bridge up + +for i in $(seq 0 $(($NODES - 1))); +do + # first validator is already setup + if [[ "$i" -ne 0 ]]; then + VAL_ID=$(echo "$VALIDATORS" | jq -r ".[$i].val_id") + NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" + + # Enable in/out interfaces for the namespace + ip link set $NET_NAMESPACE_NAME-out up + ip netns exec $NET_NAMESPACE_NAME ip link set dev $NET_NAMESPACE_NAME-in up + # Enable loopback device + ip netns exec $NET_NAMESPACE_NAME ip link set dev lo up + fi +done + +# Assign IP for bridge, to route between default network namespace and bridge +# BRIDGE_IP="$CHAIN_IP_PREFIX.254/24" +# ip addr add $BRIDGE_IP dev virtual-bridge + + +# HANDLE VALIDATOR HOMES, COPY OLD DATA FOLDER +for i in $(seq 0 $(($NODES - 1))); +do + # first validator is already setup + if [[ "$i" -ne 0 ]]; then + VAL_ID=$(echo "$VALIDATORS" | jq -r ".[$i].val_id") + echo "$VALIDATORS" | jq -r ".[$i].mnemonic" | $BIN keys add validator$VAL_ID \ + --home /$CHAIN_ID/validator$VAL_ID \ + --keyring-backend test \ + --recover > /dev/null + + # Copy in the genesis.json + cp /sover/validatoralice/config/genesis.json /$CHAIN_ID/validator$VAL_ID/config/genesis.json + cp -r /sover/validatoralice/data /$CHAIN_ID/validator$VAL_ID/ + + # Copy in validator state file + # echo '{"height": "0","round": 0,"step": 0}' > /$CHAIN_ID/validator$VAL_ID/data/priv_validator_state.json + + + PRIV_VALIDATOR_KEY=$(echo "$VALIDATORS" | jq -r ".[$i].priv_validator_key") + if [[ "$PRIV_VALIDATOR_KEY" ]]; then + echo "$PRIV_VALIDATOR_KEY" > /$CHAIN_ID/validator$VAL_ID/config/priv_validator_key.json + fi + + NODE_KEY=$(echo "$VALIDATORS" | jq -r ".[$i].node_key") + if [[ "$NODE_KEY" ]]; then + echo "$NODE_KEY" > /$CHAIN_ID/validator$VAL_ID/config/node_key.json + fi + + # Modify tendermint configs of validator + if [ "$TENDERMINT_CONFIG_TRANSFORM" != "" ] ; then + #'s/foo/bar/;s/abc/def/' + sed -i "$TENDERMINT_CONFIG_TRANSFORM" $CHAIN_ID/validator$VAL_ID/config/config.toml + fi + fi +done + + +# START VALIDATOR NODES -> this will perform the sovereign upgrade and start the chain +# ALICE is a validator on the sovereign and also the validator on the consumer chain +# BOB, CAROL are not validators on the sovereign, they will become validator once the chain switches to the consumer chain +echo "Starting validator nodes..." +for i in $(seq 0 $(($NODES - 1))); +do + VAL_ID=$(echo "$VALIDATORS" | jq -r ".[$i].val_id") + VAL_IP_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[$i].ip_suffix") + NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" + + GAIA_HOME="--home /$CHAIN_ID/validator$VAL_ID" + RPC_ADDRESS="--rpc.laddr tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26658" + GRPC_ADDRESS="--grpc.address $CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:9091" + LISTEN_ADDRESS="--address tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26655" + P2P_ADDRESS="--p2p.laddr tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26656" + # LOG_LEVEL="--log_level trace" # switch to trace to see panic messages and rich and all debug msgs + LOG_LEVEL="--log_level info" + ENABLE_WEBGRPC="--grpc-web.enable=false" + + PERSISTENT_PEERS="" + + for j in $(seq 0 $(($NODES - 1))); + do + if [ $i -ne $j ]; then + PEER_VAL_ID=$(echo "$VALIDATORS" | jq -r ".[$j].val_id") + PEER_VAL_IP_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[$j].ip_suffix") + NODE_ID=$($BIN tendermint show-node-id --home /$CHAIN_ID/validator$PEER_VAL_ID) + ADDRESS="$NODE_ID@$CHAIN_IP_PREFIX.$PEER_VAL_IP_SUFFIX:26656" + # (jq -r '.body.memo' /$CHAIN_ID/validator$j/config/gentx/*) # Getting the address from the gentx should also work + PERSISTENT_PEERS="$PERSISTENT_PEERS,$ADDRESS" + fi + done + + # Remove leading comma and concat to flag + PERSISTENT_PEERS="--p2p.persistent_peers ${PERSISTENT_PEERS:1}" + + ARGS="$GAIA_HOME $LISTEN_ADDRESS $RPC_ADDRESS $GRPC_ADDRESS $LOG_LEVEL $P2P_ADDRESS $ENABLE_WEBGRPC $PERSISTENT_PEERS" + ip netns exec $NET_NAMESPACE_NAME $BIN $ARGS start &> /$CHAIN_ID/validator$VAL_ID/logs & +done + +QUERY_NODE_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[0].ip_suffix") +echo "NODE SUFFIX: $QUERY_NODE_SUFFIX" +# poll for chain start +set +e +until $BIN query block --node "tcp://$CHAIN_IP_PREFIX.$QUERY_NODE_SUFFIX:26658" | grep -q -v '{"block_id":{"hash":"","parts":{"total":0,"hash":""}},"block":null}'; do sleep 0.3 ; done +set -e + +echo "done!!!!!!!!" + +read -p "Press Return to Close..." \ No newline at end of file diff --git a/tests/e2e/testnet-scripts/start-sovereign.sh b/tests/e2e/testnet-scripts/start-sovereign.sh new file mode 100644 index 0000000000..9daed9a207 --- /dev/null +++ b/tests/e2e/testnet-scripts/start-sovereign.sh @@ -0,0 +1,133 @@ +#!/bin/bash +set -eux + +# The gaiad binary +BIN=$1 + +# JSON array of validator information +# [{ +# mnemonic: "crackle snap pop ... etc", +# allocation: "10000000000stake,10000000000footoken", +# stake: "5000000000stake", +# val_id: "alice", +# ip_suffix: "1", +# priv_validator_key: "{\"address\": \"3566F464673B2F069758DAE86FC25D04017BB147\",\"pub_key\": {\"type\": \"tendermint/PubKeyEd25519\",\"value\": \"XrLjKdc4mB2gfqplvnoySjSJq2E90RynUwaO3WhJutk=\"},\"priv_key\": {\"type\": \"tendermint/PrivKeyEd25519\",\"value\": \"czGSLs/Ocau8aJ5J5zQHMxf3d7NR0xjMECN6YGTIWqtesuMp1ziYHaB+qmW+ejJKNImrYT3RHKdTBo7daEm62Q==\"}}" +# node_key: "{\"priv_key\":{\"type\":\"tendermint/PrivKeyEd25519\",\"value\":\"alIHj6hXnzpLAadgb7+E2eeecwxoNdzuZrfhMX36EaD5/LgzL0ZUoVp7AK3np0K5T35JWLLv0jJKmeRIhG0GjA==\"}}" +# }, ... ] +VALIDATORS=$2 + +# The chain ID +CHAIN_ID=$3 + +# This is the first 3 fields of the IP addresses which will be used internally by the validators of this blockchain +# Recommended to use something starting with 7, since it is squatted by the DoD and is unroutable on the internet +# For example: "7.7.7" +CHAIN_IP_PREFIX=$4 + +# A transformation to apply to the genesis file, as a jq string +GENESIS_TRANSFORM=$5 + +# A sed string modifying the tendermint config +TENDERMINT_CONFIG_TRANSFORM=$6 + +# SETUP NETWORK NAMESPACES, see: https://adil.medium.com/container-networking-under-the-hood-network-namespaces-6b2b8fe8dc2a + +# Create virtual bridge device (acts like a switch) +ip link add name virtual-bridge type bridge || true + +# used globally in the whole script +VAL_ID=$(echo "$VALIDATORS" | jq -r ".[0].val_id") +VAL_IP_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[0].ip_suffix") +NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" +IP_ADDR="$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX/24" + +# Create network namespace +ip netns add $NET_NAMESPACE_NAME +# Create virtual ethernet device to connect with bridge +ip link add $NET_NAMESPACE_NAME-in type veth peer name $NET_NAMESPACE_NAME-out +# Connect input end of virtual ethernet device to namespace +ip link set $NET_NAMESPACE_NAME-in netns $NET_NAMESPACE_NAME +# Assign ip address to namespace +ip netns exec $NET_NAMESPACE_NAME ip addr add $IP_ADDR dev $NET_NAMESPACE_NAME-in +# Connect output end of virtual ethernet device to bridge +ip link set $NET_NAMESPACE_NAME-out master virtual-bridge + +# Enable bridge interface +ip link set virtual-bridge up + +NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" +# Enable in/out interfaces for the namespace +ip link set $NET_NAMESPACE_NAME-out up +ip netns exec $NET_NAMESPACE_NAME ip link set dev $NET_NAMESPACE_NAME-in up +# Enable loopback device +ip netns exec $NET_NAMESPACE_NAME ip link set dev lo up + +# Assign IP for bridge, to route between default network namespace and bridge +BRIDGE_IP="$CHAIN_IP_PREFIX.254/24" +ip addr add $BRIDGE_IP dev virtual-bridge + +# first we start a genesis.json with the first validator +# the first validator will also collect the gentx's once gnerated +echo "$VALIDATORS" | jq -r ".[0].mnemonic" | $BIN init --home /$CHAIN_ID/validator$VAL_ID --chain-id=$CHAIN_ID validator$VAL_ID --recover > /dev/null + +# !!!!!!!!! IMPORTANT !!!!!!!!! # +# move the sovereign genesis to the correct validator home dir +cp /testnet-scripts/sovereign-genesis.json /$CHAIN_ID/validator$VAL_ID/config/genesis.json + +# Apply jq transformations to genesis file +jq "$GENESIS_TRANSFORM" /$CHAIN_ID/validator$VAL_ID/config/genesis.json > /$CHAIN_ID/edited-genesis.json +mv /$CHAIN_ID/edited-genesis.json /$CHAIN_ID/genesis.json +cp /$CHAIN_ID/genesis.json /$CHAIN_ID/validator$VAL_ID/config/genesis.json + + + +# SETUP LOCAL VALIDATOR STATE +echo '{"height": "0","round": 0,"step": 0}' > /$CHAIN_ID/validator$VAL_ID/data/priv_validator_state.json + +PRIV_VALIDATOR_KEY=$(echo "$VALIDATORS" | jq -r ".[0].priv_validator_key") +if [[ "$PRIV_VALIDATOR_KEY" ]]; then + echo "$PRIV_VALIDATOR_KEY" > /$CHAIN_ID/validator$VAL_ID/config/priv_validator_key.json +fi + +NODE_KEY=$(echo "$VALIDATORS" | jq -r ".[0].node_key") +if [[ "$NODE_KEY" ]]; then + echo "$NODE_KEY" > /$CHAIN_ID/validator$VAL_ID/config/node_key.json +fi + +echo "$VALIDATORS" | jq -r ".[0].mnemonic" | $BIN keys add validator$VAL_ID \ +--home /$CHAIN_ID/validator$VAL_ID \ +--keyring-backend test \ +--recover > /dev/null + +# Modify tendermint configs of validator +if [ "$TENDERMINT_CONFIG_TRANSFORM" != "" ] ; then + #'s/foo/bar/;s/abc/def/' + sed -i "$TENDERMINT_CONFIG_TRANSFORM" $CHAIN_ID/validator$VAL_ID/config/config.toml +fi + + +# START VALIDATOR NODE +VAL_IP_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[0].ip_suffix") +NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" + +GAIA_HOME="--home /$CHAIN_ID/validator$VAL_ID" +RPC_ADDRESS="--rpc.laddr tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26658" +GRPC_ADDRESS="--grpc.address $CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:9091" +LISTEN_ADDRESS="--address tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26655" +P2P_ADDRESS="--p2p.laddr tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26656" +# LOG_LEVEL="--log_level trace" # switch to trace to see panic messages and rich and all debug msgs +LOG_LEVEL="--log_level info" +ENABLE_WEBGRPC="--grpc-web.enable=false" + +ARGS="$GAIA_HOME $LISTEN_ADDRESS $RPC_ADDRESS $GRPC_ADDRESS $LOG_LEVEL $P2P_ADDRESS $ENABLE_WEBGRPC" +ip netns exec $NET_NAMESPACE_NAME $BIN $ARGS start &> /$CHAIN_ID/validator$VAL_ID/logs & + + +# poll for chain start +set +e +until $BIN query block --node "tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26658" | grep -q -v '{"block_id":{"hash":"","parts":{"total":0,"hash":""}},"block":null}'; do sleep 0.3 ; done +set -e + +echo "done!!!!!!!!" + +read -p "Press Return to Close..." \ No newline at end of file diff --git a/tests/integration/changeover.go b/tests/integration/changeover.go index 3bf4e11932..13d18a4026 100644 --- a/tests/integration/changeover.go +++ b/tests/integration/changeover.go @@ -34,7 +34,7 @@ func (suite *CCVTestSuite) TestRecycleTransferChannel() { // Setup state s.t. the consumer keeper emulates a consumer that was previously standalone consumerKeeper.MarkAsPrevStandaloneChain(suite.consumerCtx()) suite.Require().True(consumerKeeper.IsPrevStandaloneChain(suite.consumerCtx())) - suite.consumerApp.GetConsumerKeeper().SetStandaloneTransferChannelID(suite.consumerCtx(), resp.ChannelId) + suite.consumerApp.GetConsumerKeeper().SetDistributionTransmissionChannel(suite.consumerCtx(), resp.ChannelId) // Now finish setting up CCV channel suite.ExecuteCCVChannelHandshake(suite.path) diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index 426a94b4b6..77a597c726 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -237,6 +237,7 @@ func GetTestConsumerAdditionProp() *providertypes.ConsumerAdditionProposal { time.Now(), consumertypes.DefaultConsumerRedistributeFrac, consumertypes.DefaultBlocksPerDistributionTransmission, + "", consumertypes.DefaultHistoricalEntries, types.DefaultCCVTimeoutPeriod, consumertypes.DefaultTransferTimeoutPeriod, diff --git a/x/ccv/consumer/ibc_module.go b/x/ccv/consumer/ibc_module.go index d7191d0be0..b3dde3d48a 100644 --- a/x/ccv/consumer/ibc_module.go +++ b/x/ccv/consumer/ibc_module.go @@ -140,15 +140,10 @@ func (am AppModule) OnChanOpenAck( /////////////////////////////////////////////////// // Initialize distribution token transfer channel - // First check if an existing transfer channel exists, if this consumer was a previously standalone chain. - if am.keeper.IsPrevStandaloneChain(ctx) { - transChannelID := am.keeper.GetStandaloneTransferChannelID(ctx) - found := am.keeper.TransferChannelExists(ctx, transChannelID) - if found { - // If existing transfer channel is found, persist that channel ID and return - am.keeper.SetDistributionTransmissionChannel(ctx, transChannelID) - return nil - } + // First check if an existing transfer channel already exists. + transChannelID := am.keeper.GetDistributionTransmissionChannel(ctx) + if found := am.keeper.TransferChannelExists(ctx, transChannelID); found { + return nil } // NOTE The handshake for this channel is handled by the ibc-go/transfer diff --git a/x/ccv/consumer/ibc_module_test.go b/x/ccv/consumer/ibc_module_test.go index fc6c3d2200..4c8377a8c8 100644 --- a/x/ccv/consumer/ibc_module_test.go +++ b/x/ccv/consumer/ibc_module_test.go @@ -214,7 +214,7 @@ func TestOnChanOpenAck(t *testing.T) { expPass bool }{ { - "success", + "success - empty transferChannelID", func(keeper *consumerkeeper.Keeper, params *params, mocks testkeeper.MockedKeepers) { // Expected msg distrTransferMsg := channeltypes.NewMsgChannelOpenInit( @@ -226,8 +226,13 @@ func TestOnChanOpenAck(t *testing.T) { "", // signer unused ) + transferChannelID := "" + keeper.SetDistributionTransmissionChannel(params.ctx, transferChannelID) + // Expected mock calls gomock.InOrder( + mocks.MockChannelKeeper.EXPECT().GetChannel( + params.ctx, transfertypes.PortID, transferChannelID).Return(channeltypes.Channel{}, false).Times(1), mocks.MockChannelKeeper.EXPECT().GetChannel( params.ctx, params.portID, params.channelID).Return(channeltypes.Channel{ ConnectionHops: []string{"connectionID"}, diff --git a/x/ccv/consumer/keeper/changeover.go b/x/ccv/consumer/keeper/changeover.go index 50e1a57184..57770a077d 100644 --- a/x/ccv/consumer/keeper/changeover.go +++ b/x/ccv/consumer/keeper/changeover.go @@ -48,5 +48,6 @@ func (k Keeper) ChangeoverToConsumer(ctx sdk.Context) (initialValUpdates []abci. // Therefore we set the PreCCV state to false so the endblocker caller doesn't call this method again. k.DeletePreCCV(ctx) + k.Logger(ctx).Info("ICS changeover complete - you are now a consumer chain!") return initialValUpdates } diff --git a/x/ccv/consumer/keeper/keeper.go b/x/ccv/consumer/keeper/keeper.go index 54c1cd5577..d420bb9bd4 100644 --- a/x/ccv/consumer/keeper/keeper.go +++ b/x/ccv/consumer/keeper/keeper.go @@ -618,17 +618,3 @@ func (k Keeper) IsPrevStandaloneChain(ctx sdk.Context) bool { store := ctx.KVStore(k.storeKey) return store.Has(types.PrevStandaloneChainKey()) } - -// SetStandaloneTransferChannelID sets the channelID of an existing transfer channel, -// for a chain which used to be a standalone chain. -func (k Keeper) SetStandaloneTransferChannelID(ctx sdk.Context, channelID string) { - store := ctx.KVStore(k.storeKey) - store.Set(types.StandaloneTransferChannelIDKey(), []byte(channelID)) -} - -// GetStandaloneTransferChannelID returns the channelID of an existing transfer channel, -// for a chain which used to be a standalone chain. -func (k Keeper) GetStandaloneTransferChannelID(ctx sdk.Context) string { - store := ctx.KVStore(k.storeKey) - return string(store.Get(types.StandaloneTransferChannelIDKey())) -} diff --git a/x/ccv/consumer/keeper/keeper_test.go b/x/ccv/consumer/keeper/keeper_test.go index 4280f1de37..fe523ce7cf 100644 --- a/x/ccv/consumer/keeper/keeper_test.go +++ b/x/ccv/consumer/keeper/keeper_test.go @@ -541,19 +541,6 @@ func TestGetAllOutstandingDowntimes(t *testing.T) { require.Equal(t, result, expectedGetAllOrder) } -// TestStandaloneTransferChannelID tests the getter and setter for the existing transfer channel id -func TestStandaloneTransferChannelID(t *testing.T) { - ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - // Test that the default value is empty - require.Equal(t, "", ck.GetStandaloneTransferChannelID(ctx)) - - // Test that the value can be set and retrieved - ck.SetStandaloneTransferChannelID(ctx, "channelID1234") - require.Equal(t, "channelID1234", ck.GetStandaloneTransferChannelID(ctx)) -} - func TestPrevStandaloneChainFlag(t *testing.T) { ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() diff --git a/x/ccv/consumer/keeper/migration_test.go b/x/ccv/consumer/keeper/migration_test.go index ee14d2bcc8..29574a4808 100644 --- a/x/ccv/consumer/keeper/migration_test.go +++ b/x/ccv/consumer/keeper/migration_test.go @@ -116,7 +116,7 @@ func (p *v1Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(consumertypes.KeyBlocksPerDistributionTransmission, p.BlocksPerDistributionTransmission, ccvtypes.ValidatePositiveInt64), paramtypes.NewParamSetPair(consumertypes.KeyDistributionTransmissionChannel, - p.DistributionTransmissionChannel, consumertypes.ValidateDistributionTransmissionChannel), + p.DistributionTransmissionChannel, ccvtypes.ValidateDistributionTransmissionChannel), paramtypes.NewParamSetPair(consumertypes.KeyProviderFeePoolAddrStr, p.ProviderFeePoolAddrStr, consumertypes.ValidateProviderFeePoolAddrStr), paramtypes.NewParamSetPair(ccvtypes.KeyCCVTimeoutPeriod, diff --git a/x/ccv/consumer/types/genesis_test.go b/x/ccv/consumer/types/genesis_test.go index f5e6ce075c..0398b39f54 100644 --- a/x/ccv/consumer/types/genesis_test.go +++ b/x/ccv/consumer/types/genesis_test.go @@ -210,7 +210,7 @@ func TestValidateInitialGenesisState(t *testing.T) { true, }, { - "invalid new consumer genesis state: invalid params", + "invalid new consumer genesis state: invalid params - ccvTimeoutPeriod", types.NewInitialGenesisState(cs, consensusState, valUpdates, types.NewParams( true, @@ -228,6 +228,25 @@ func TestValidateInitialGenesisState(t *testing.T) { )), true, }, + { + "invalid new consumer genesis state: invalid params - distributionTransmissionChannel", + types.NewInitialGenesisState(cs, consensusState, valUpdates, + types.NewParams( + true, + types.DefaultBlocksPerDistributionTransmission, + "badchannel/", + "", + ccv.DefaultCCVTimeoutPeriod, + types.DefaultTransferTimeoutPeriod, + types.DefaultConsumerRedistributeFrac, + types.DefaultHistoricalEntries, + types.DefaultConsumerUnbondingPeriod, + types.DefaultSoftOptOutThreshold, + []string{}, + []string{}, + )), + true, + }, } for _, c := range cases { diff --git a/x/ccv/consumer/types/params.go b/x/ccv/consumer/types/params.go index 509e3725b4..7ab51fbad8 100644 --- a/x/ccv/consumer/types/params.go +++ b/x/ccv/consumer/types/params.go @@ -113,7 +113,7 @@ func (p Params) Validate() error { if err := ccvtypes.ValidatePositiveInt64(p.BlocksPerDistributionTransmission); err != nil { return err } - if err := ValidateDistributionTransmissionChannel(p.DistributionTransmissionChannel); err != nil { + if err := ccvtypes.ValidateDistributionTransmissionChannel(p.DistributionTransmissionChannel); err != nil { return err } if err := ValidateProviderFeePoolAddrStr(p.ProviderFeePoolAddrStr); err != nil { @@ -153,7 +153,7 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(KeyBlocksPerDistributionTransmission, p.BlocksPerDistributionTransmission, ccvtypes.ValidatePositiveInt64), paramtypes.NewParamSetPair(KeyDistributionTransmissionChannel, - p.DistributionTransmissionChannel, ValidateDistributionTransmissionChannel), + p.DistributionTransmissionChannel, ccvtypes.ValidateDistributionTransmissionChannel), paramtypes.NewParamSetPair(KeyProviderFeePoolAddrStr, p.ProviderFeePoolAddrStr, ValidateProviderFeePoolAddrStr), paramtypes.NewParamSetPair(ccvtypes.KeyCCVTimeoutPeriod, @@ -175,15 +175,6 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { } } -func ValidateDistributionTransmissionChannel(i interface{}) error { - // Accept empty string as valid, since this will be the default value on genesis - if i == "" { - return nil - } - // Otherwise validate as usual for a channelID - return ccvtypes.ValidateChannelIdentifier(i) -} - func ValidateProviderFeePoolAddrStr(i interface{}) error { // Accept empty string as valid, since this will be the default value on genesis if i == "" { diff --git a/x/ccv/provider/client/proposal_handler.go b/x/ccv/provider/client/proposal_handler.go index 19848d9282..76491b86d2 100644 --- a/x/ccv/provider/client/proposal_handler.go +++ b/x/ccv/provider/client/proposal_handler.go @@ -58,6 +58,7 @@ Where proposal.json contains: "spawn_time": "2022-01-27T15:59:50.121607-08:00", "blocks_per_distribution_transmission": 1000, "consumer_redistribution_fraction": "0.75", + "distribution_transmission_channel": "", "historical_entries": 10000, "transfer_timeout_period": 3600000000000, "ccv_timeout_period": 2419200000000000, @@ -82,7 +83,8 @@ Where proposal.json contains: content := types.NewConsumerAdditionProposal( proposal.Title, proposal.Description, proposal.ChainId, proposal.InitialHeight, proposal.GenesisHash, proposal.BinaryHash, proposal.SpawnTime, - proposal.ConsumerRedistributionFraction, proposal.BlocksPerDistributionTransmission, proposal.HistoricalEntries, + proposal.ConsumerRedistributionFraction, proposal.BlocksPerDistributionTransmission, + proposal.DistributionTransmissionChannel, proposal.HistoricalEntries, proposal.CcvTimeoutPeriod, proposal.TransferTimeoutPeriod, proposal.UnbondingPeriod) from := clientCtx.GetFromAddress() @@ -224,6 +226,7 @@ type ConsumerAdditionProposalJSON struct { ConsumerRedistributionFraction string `json:"consumer_redistribution_fraction"` BlocksPerDistributionTransmission int64 `json:"blocks_per_distribution_transmission"` + DistributionTransmissionChannel string `json:"distribution_transmission_channel"` HistoricalEntries int64 `json:"historical_entries"` CcvTimeoutPeriod time.Duration `json:"ccv_timeout_period"` TransferTimeoutPeriod time.Duration `json:"transfer_timeout_period"` @@ -246,6 +249,7 @@ type ConsumerAdditionProposalReq struct { ConsumerRedistributionFraction string `json:"consumer_redistribution_fraction"` BlocksPerDistributionTransmission int64 `json:"blocks_per_distribution_transmission"` + DistributionTransmissionChannel string `json:"distribution_transmission_channel"` HistoricalEntries int64 `json:"historical_entries"` CcvTimeoutPeriod time.Duration `json:"ccv_timeout_period"` TransferTimeoutPeriod time.Duration `json:"transfer_timeout_period"` @@ -375,7 +379,8 @@ func postConsumerAdditionProposalHandlerFn(clientCtx client.Context) http.Handle content := types.NewConsumerAdditionProposal( req.Title, req.Description, req.ChainId, req.InitialHeight, req.GenesisHash, req.BinaryHash, req.SpawnTime, - req.ConsumerRedistributionFraction, req.BlocksPerDistributionTransmission, req.HistoricalEntries, + req.ConsumerRedistributionFraction, req.BlocksPerDistributionTransmission, + req.DistributionTransmissionChannel, req.HistoricalEntries, req.CcvTimeoutPeriod, req.TransferTimeoutPeriod, req.UnbondingPeriod) msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer) diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index dcff74385b..e50619c066 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -303,7 +303,7 @@ func (k Keeper) MakeConsumerGenesis( consumerGenesisParams := consumertypes.NewParams( true, prop.BlocksPerDistributionTransmission, - "", // distributionTransmissionChannel + prop.DistributionTransmissionChannel, "", // providerFeePoolAddrStr, prop.CcvTimeoutPeriod, prop.TransferTimeoutPeriod, diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index cb18a119b4..68ee7e9663 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -61,6 +61,7 @@ func TestHandleConsumerAdditionProposal(t *testing.T) { now, // Spawn time "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -85,6 +86,7 @@ func TestHandleConsumerAdditionProposal(t *testing.T) { now, "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -883,6 +885,7 @@ func TestBeginBlockInit(t *testing.T) { now.Add(-time.Hour*2).UTC(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -893,6 +896,7 @@ func TestBeginBlockInit(t *testing.T) { now.Add(-time.Hour*1).UTC(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -903,6 +907,7 @@ func TestBeginBlockInit(t *testing.T) { now.Add(time.Hour).UTC(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -913,6 +918,7 @@ func TestBeginBlockInit(t *testing.T) { now.UTC(), "0.75", 10, + "", 10000, 100000000000, 100000000000, diff --git a/x/ccv/provider/proposal_handler_test.go b/x/ccv/provider/proposal_handler_test.go index ddf3698b6f..1e8e9b34b5 100644 --- a/x/ccv/provider/proposal_handler_test.go +++ b/x/ccv/provider/proposal_handler_test.go @@ -41,6 +41,7 @@ func TestProviderProposalHandler(t *testing.T) { clienttypes.NewHeight(2, 3), []byte("gen_hash"), []byte("bin_hash"), now, "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -58,14 +59,14 @@ func TestProviderProposalHandler(t *testing.T) { }, { // no slash log for equivocation - name: "invalid equivocation posal", + name: "invalid equivocation proposal", content: providertypes.NewEquivocationProposal( "title", "description", []*evidencetypes.Equivocation{equivocation}), blockTime: hourFromNow, expValidEquivocation: false, }, { - name: "valid equivocation posal", + name: "valid equivocation proposal", content: providertypes.NewEquivocationProposal( "title", "description", []*evidencetypes.Equivocation{equivocation}), blockTime: hourFromNow, diff --git a/x/ccv/provider/types/proposal.go b/x/ccv/provider/types/proposal.go index 49fe00531f..913152e68c 100644 --- a/x/ccv/provider/types/proposal.go +++ b/x/ccv/provider/types/proposal.go @@ -36,7 +36,8 @@ func NewConsumerAdditionProposal(title, description, chainID string, initialHeight clienttypes.Height, genesisHash, binaryHash []byte, spawnTime time.Time, consumerRedistributionFraction string, - blocksPerDistributionTransmission, + blocksPerDistributionTransmission int64, + distributionTransmissionChannel string, historicalEntries int64, ccvTimeoutPeriod time.Duration, transferTimeoutPeriod time.Duration, @@ -52,6 +53,7 @@ func NewConsumerAdditionProposal(title, description, chainID string, SpawnTime: spawnTime, ConsumerRedistributionFraction: consumerRedistributionFraction, BlocksPerDistributionTransmission: blocksPerDistributionTransmission, + DistributionTransmissionChannel: distributionTransmissionChannel, HistoricalEntries: historicalEntries, CcvTimeoutPeriod: ccvTimeoutPeriod, TransferTimeoutPeriod: transferTimeoutPeriod, @@ -106,6 +108,10 @@ func (cccp *ConsumerAdditionProposal) ValidateBasic() error { return errorsmod.Wrap(ErrInvalidConsumerAdditionProposal, "blocks per distribution transmission cannot be < 1") } + if err := ccvtypes.ValidateDistributionTransmissionChannel(cccp.DistributionTransmissionChannel); err != nil { + return errorsmod.Wrap(ErrInvalidConsumerAdditionProposal, "distribution transmission channel") + } + if err := ccvtypes.ValidatePositiveInt64(cccp.HistoricalEntries); err != nil { return errorsmod.Wrap(ErrInvalidConsumerAdditionProposal, "historical entries cannot be < 1") } @@ -137,6 +143,7 @@ func (cccp *ConsumerAdditionProposal) String() string { SpawnTime: %s ConsumerRedistributionFraction: %s BlocksPerDistributionTransmission: %d + DistributionTransmissionChannel: %s HistoricalEntries: %d CcvTimeoutPeriod: %d TransferTimeoutPeriod: %d @@ -150,6 +157,7 @@ func (cccp *ConsumerAdditionProposal) String() string { cccp.SpawnTime, cccp.ConsumerRedistributionFraction, cccp.BlocksPerDistributionTransmission, + cccp.DistributionTransmissionChannel, cccp.HistoricalEntries, cccp.CcvTimeoutPeriod, cccp.TransferTimeoutPeriod, diff --git a/x/ccv/provider/types/proposal_test.go b/x/ccv/provider/types/proposal_test.go index db765a86ca..d1440eb9a0 100644 --- a/x/ccv/provider/types/proposal_test.go +++ b/x/ccv/provider/types/proposal_test.go @@ -32,6 +32,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -44,6 +45,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.0", // fraction can be 0.0 but not empty 10, + "", 10000, 100000000000, 100000000000, @@ -55,6 +57,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal(" ", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -66,6 +69,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", " ", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -86,6 +90,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { CcvTimeoutPeriod: 100000000000, TransferTimeoutPeriod: 100000000000, ConsumerRedistributionFraction: "0.75", + DistributionTransmissionChannel: "", HistoricalEntries: 10000, UnbondingPeriod: 100000000000, }, @@ -96,6 +101,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte(""), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -107,6 +113,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte(""), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -118,6 +125,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Time{}, "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -129,6 +137,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "", // fraction can be 0.0 but not empty 10, + "", 10000, 100000000000, 100000000000, @@ -140,17 +149,31 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 0, + "", 100000000000, 10000, 100000000000, 100000000000), false, }, + { + "distribution transmission channel is invalid", + types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), + "0.75", + 10, + "badchannel/", + 10000, + 100000000000, + 100000000000, + 100000000000), + false, + }, { "historical entries is invalid", types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", -2, 100000000000, 100000000000, @@ -162,6 +185,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 0, 100000000000, @@ -173,6 +197,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 0, @@ -184,6 +209,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -207,6 +233,7 @@ func TestMarshalConsumerAdditionProposal(t *testing.T) { content := types.NewConsumerAdditionProposal("title", "description", "chainID", clienttypes.NewHeight(0, 1), []byte("gen_hash"), []byte("bin_hash"), time.Now().UTC(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -248,6 +275,7 @@ func TestConsumerAdditionProposalString(t *testing.T) { spawnTime, "0.75", 10001, + "", 500000, 100000000000, 10000000000, @@ -263,12 +291,14 @@ func TestConsumerAdditionProposalString(t *testing.T) { SpawnTime: %s ConsumerRedistributionFraction: %s BlocksPerDistributionTransmission: %d + DistributionTransmissionChannel: %s HistoricalEntries: %d CcvTimeoutPeriod: %d TransferTimeoutPeriod: %d UnbondingPeriod: %d`, initialHeight, []byte("gen_hash"), []byte("bin_hash"), spawnTime, "0.75", 10001, + "", 500000, 100000000000, 10000000000, diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index 37e175acea..acc95ca9af 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -74,6 +74,13 @@ type ConsumerAdditionProposal struct { // This param is a part of the cosmos sdk staking module. In the case of // a ccv enabled consumer chain, the ccv module acts as the staking module. HistoricalEntries int64 `protobuf:"varint,13,opt,name=historical_entries,json=historicalEntries,proto3" json:"historical_entries,omitempty"` + // The ID of a token transfer channel used for the Reward Distribution + // sub-protocol. If DistributionTransmissionChannel == "", a new transfer + // channel is created on top of the same connection as the CCV channel. + // Note that transfer_channel_id is the ID of the channel end on the consumer chain. + // it is most relevant for chains performing a sovereign to consumer changeover + // in order to maintan the existing ibc transfer channel + DistributionTransmissionChannel string `protobuf:"bytes,14,opt,name=distribution_transmission_channel,json=distributionTransmissionChannel,proto3" json:"distribution_transmission_channel,omitempty"` } func (m *ConsumerAdditionProposal) Reset() { *m = ConsumerAdditionProposal{} } @@ -1220,106 +1227,107 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1579 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xcd, 0x72, 0xdc, 0xc6, - 0x11, 0x26, 0xb8, 0xfc, 0xdb, 0x59, 0xfe, 0x48, 0x10, 0x65, 0x2d, 0x15, 0x66, 0xb9, 0x86, 0x13, - 0x17, 0x53, 0x2e, 0x63, 0x43, 0xfa, 0x92, 0x52, 0xc5, 0xe5, 0x22, 0x57, 0x96, 0xc5, 0x30, 0xb6, - 0xd6, 0x20, 0x43, 0x55, 0x92, 0x03, 0x6a, 0x30, 0x68, 0xed, 0x4e, 0x11, 0xc0, 0x40, 0x33, 0x03, - 0x48, 0x7b, 0xc9, 0x39, 0x47, 0xa7, 0x72, 0x71, 0x25, 0x17, 0xe7, 0x09, 0xf2, 0x1a, 0x3e, 0xfa, - 0x98, 0x93, 0x93, 0xa2, 0x0e, 0x39, 0xe4, 0x25, 0x52, 0x33, 0xf8, 0x5f, 0x52, 0xca, 0xaa, 0x92, - 0xdc, 0x30, 0x3d, 0xdd, 0x5f, 0xf7, 0x4c, 0x77, 0x7f, 0x3d, 0x40, 0x87, 0x34, 0x92, 0xc0, 0xc9, - 0x04, 0xd3, 0xc8, 0x15, 0x40, 0x12, 0x4e, 0xe5, 0x74, 0x40, 0x48, 0x3a, 0x88, 0x39, 0x4b, 0xa9, - 0x0f, 0x7c, 0x90, 0x1e, 0x94, 0xdf, 0x76, 0xcc, 0x99, 0x64, 0xe6, 0x7b, 0x37, 0xd8, 0xd8, 0x84, - 0xa4, 0x76, 0xa9, 0x97, 0x1e, 0xdc, 0xdf, 0x1e, 0xb3, 0x31, 0xd3, 0xfa, 0x03, 0xf5, 0x95, 0x99, - 0xde, 0xdf, 0x1b, 0x33, 0x36, 0x0e, 0x60, 0xa0, 0x57, 0x5e, 0xf2, 0x6c, 0x20, 0x69, 0x08, 0x42, - 0xe2, 0x30, 0xce, 0x15, 0x7a, 0xb3, 0x0a, 0x7e, 0xc2, 0xb1, 0xa4, 0x2c, 0x2a, 0x00, 0xa8, 0x47, - 0x06, 0x84, 0x71, 0x18, 0x90, 0x80, 0x42, 0x24, 0x55, 0x78, 0xd9, 0x57, 0xae, 0x30, 0x50, 0x0a, - 0x01, 0x1d, 0x4f, 0x64, 0x26, 0x16, 0x03, 0x09, 0x91, 0x0f, 0x3c, 0xa4, 0x99, 0x72, 0xb5, 0xca, - 0x0d, 0x76, 0x6b, 0xfb, 0x84, 0x4f, 0x63, 0xc9, 0x06, 0x97, 0x30, 0x15, 0xf9, 0xee, 0xfb, 0x84, - 0x89, 0x90, 0x89, 0x01, 0xa8, 0x83, 0x45, 0x04, 0x06, 0xe9, 0x81, 0x07, 0x12, 0x1f, 0x94, 0x82, - 0x22, 0xee, 0x5c, 0xcf, 0xc3, 0xa2, 0xd2, 0x21, 0x8c, 0xe6, 0x71, 0x5b, 0x7f, 0x5c, 0x41, 0xdd, - 0x21, 0x8b, 0x44, 0x12, 0x02, 0x3f, 0xf2, 0x7d, 0xaa, 0x8e, 0x34, 0xe2, 0x2c, 0x66, 0x02, 0x07, - 0xe6, 0x36, 0x5a, 0x96, 0x54, 0x06, 0xd0, 0x35, 0xfa, 0xc6, 0x7e, 0xdb, 0xc9, 0x16, 0x66, 0x1f, - 0x75, 0x7c, 0x10, 0x84, 0xd3, 0x58, 0x29, 0x77, 0x17, 0xf5, 0x5e, 0x5d, 0x64, 0xee, 0xa0, 0xb5, - 0x2c, 0x0b, 0xd4, 0xef, 0xb6, 0xf4, 0xf6, 0xaa, 0x5e, 0x9f, 0xf8, 0xe6, 0x67, 0x68, 0x93, 0x46, - 0x54, 0x52, 0x1c, 0xb8, 0x13, 0x50, 0xb7, 0xd1, 0x5d, 0xea, 0x1b, 0xfb, 0x9d, 0xc3, 0xfb, 0x36, - 0xf5, 0x88, 0xad, 0x2e, 0xd0, 0xce, 0xaf, 0x2d, 0x3d, 0xb0, 0x1f, 0x6b, 0x8d, 0xe3, 0xa5, 0x6f, - 0xbf, 0xdf, 0x5b, 0x70, 0x36, 0x72, 0xbb, 0x4c, 0x68, 0xbe, 0x8b, 0xd6, 0xc7, 0x10, 0x81, 0xa0, - 0xc2, 0x9d, 0x60, 0x31, 0xe9, 0x2e, 0xf7, 0x8d, 0xfd, 0x75, 0xa7, 0x93, 0xcb, 0x1e, 0x63, 0x31, - 0x31, 0xf7, 0x50, 0xc7, 0xa3, 0x11, 0xe6, 0xd3, 0x4c, 0x63, 0x45, 0x6b, 0xa0, 0x4c, 0xa4, 0x15, - 0x86, 0x08, 0x89, 0x18, 0xbf, 0x88, 0x5c, 0x95, 0xed, 0xee, 0x6a, 0x1e, 0x48, 0x96, 0x69, 0xbb, - 0xc8, 0xb4, 0x7d, 0x5e, 0x94, 0xc2, 0xf1, 0x9a, 0x0a, 0xe4, 0xab, 0xbf, 0xef, 0x19, 0x4e, 0x5b, - 0xdb, 0xa9, 0x1d, 0xf3, 0x0b, 0x74, 0x2b, 0x89, 0x3c, 0x16, 0xf9, 0x34, 0x1a, 0xbb, 0x31, 0x70, - 0xca, 0xfc, 0xee, 0x9a, 0x86, 0xda, 0xb9, 0x06, 0xf5, 0x30, 0x2f, 0x9a, 0x0c, 0xe9, 0x6b, 0x85, - 0xb4, 0x55, 0x1a, 0x8f, 0xb4, 0xad, 0xf9, 0x25, 0x32, 0x09, 0x49, 0x75, 0x48, 0x2c, 0x91, 0x05, - 0x62, 0x7b, 0x7e, 0xc4, 0x5b, 0x84, 0xa4, 0xe7, 0x99, 0x75, 0x0e, 0xf9, 0x5b, 0x74, 0x4f, 0x72, - 0x1c, 0x89, 0x67, 0xc0, 0x67, 0x71, 0xd1, 0xfc, 0xb8, 0x77, 0x0b, 0x8c, 0x26, 0xf8, 0x63, 0xd4, - 0x27, 0x79, 0x01, 0xb9, 0x1c, 0x7c, 0x2a, 0x24, 0xa7, 0x5e, 0xa2, 0x6c, 0xdd, 0x67, 0x1c, 0x13, - 0x5d, 0x23, 0x1d, 0x5d, 0x04, 0xbd, 0x42, 0xcf, 0x69, 0xa8, 0x3d, 0xca, 0xb5, 0xcc, 0x27, 0xe8, - 0x47, 0x5e, 0xc0, 0xc8, 0xa5, 0x50, 0xc1, 0xb9, 0x0d, 0x24, 0xed, 0x3a, 0xa4, 0x42, 0x28, 0xb4, - 0xf5, 0xbe, 0xb1, 0xdf, 0x72, 0xde, 0xcd, 0x74, 0x47, 0xc0, 0x1f, 0xd6, 0x34, 0xcf, 0x6b, 0x8a, - 0xe6, 0x87, 0xc8, 0x9c, 0x50, 0x21, 0x19, 0xa7, 0x04, 0x07, 0x2e, 0x44, 0x92, 0x53, 0x10, 0xdd, - 0x0d, 0x6d, 0x7e, 0xbb, 0xda, 0xf9, 0x34, 0xdb, 0x78, 0xb0, 0xf6, 0xfb, 0x6f, 0xf6, 0x16, 0xbe, - 0xfe, 0x66, 0x6f, 0xc1, 0xfa, 0xab, 0x81, 0xee, 0x0d, 0xcb, 0x60, 0x43, 0x96, 0xe2, 0xe0, 0xff, - 0xd9, 0x14, 0x47, 0xa8, 0x2d, 0x24, 0x8b, 0xb3, 0x32, 0x5c, 0x7a, 0x8b, 0x32, 0x5c, 0x53, 0x66, - 0x6a, 0xc3, 0xfa, 0xb3, 0x81, 0xb6, 0x3f, 0x7d, 0x9e, 0xd0, 0x94, 0x11, 0xfc, 0x3f, 0xe9, 0xe1, - 0x53, 0xb4, 0x01, 0x35, 0x3c, 0xd1, 0x6d, 0xf5, 0x5b, 0xfb, 0x9d, 0xc3, 0x1f, 0xdb, 0x19, 0xa1, - 0xd8, 0x25, 0xcf, 0xe4, 0xa4, 0x62, 0xd7, 0xbd, 0x3b, 0x4d, 0x5b, 0xeb, 0x5f, 0x06, 0xba, 0xf5, - 0x59, 0xc0, 0x3c, 0x1c, 0x9c, 0x05, 0x58, 0x4c, 0xd4, 0x85, 0x4f, 0xd5, 0xa9, 0x39, 0xe4, 0x95, - 0xae, 0xa3, 0x9b, 0xfb, 0xd4, 0xca, 0x4c, 0xf7, 0xde, 0x27, 0xe8, 0x76, 0x59, 0x7b, 0xe5, 0xe5, - 0xea, 0xc3, 0x1c, 0xdf, 0xb9, 0xfa, 0x7e, 0x6f, 0xab, 0xc8, 0xe1, 0x50, 0x5f, 0xf4, 0x43, 0x67, - 0x8b, 0x34, 0x04, 0xbe, 0xd9, 0x43, 0x1d, 0xea, 0x11, 0x57, 0xc0, 0x73, 0x37, 0x4a, 0x42, 0x9d, - 0x97, 0x25, 0xa7, 0x4d, 0x3d, 0x72, 0x06, 0xcf, 0xbf, 0x48, 0x42, 0xf3, 0x23, 0xf4, 0x4e, 0x31, - 0x3c, 0xdc, 0x14, 0x07, 0xae, 0xb2, 0x77, 0xb1, 0xef, 0x73, 0x9d, 0xa6, 0x75, 0xe7, 0x4e, 0xb1, - 0x7b, 0x81, 0x03, 0xe5, 0xec, 0xc8, 0xf7, 0xb9, 0xf5, 0xcf, 0x65, 0xb4, 0x32, 0xc2, 0x1c, 0x87, - 0xc2, 0x3c, 0x47, 0x5b, 0x12, 0xc2, 0x38, 0xc0, 0x12, 0xdc, 0x8c, 0xd7, 0xf2, 0x93, 0x7e, 0xa0, - 0xf9, 0xae, 0x3e, 0x0f, 0xec, 0xda, 0x04, 0x48, 0x0f, 0xec, 0xa1, 0x96, 0x9e, 0x49, 0x2c, 0xc1, - 0xd9, 0x2c, 0x30, 0x32, 0xa1, 0xf9, 0x33, 0xd4, 0x95, 0x3c, 0x11, 0xb2, 0x62, 0x9c, 0xaa, 0xd5, - 0xb2, 0x54, 0xbe, 0x53, 0xec, 0x67, 0x4d, 0x5a, 0xb6, 0xd8, 0xcd, 0xe4, 0xd2, 0xfa, 0x6f, 0xc8, - 0xe5, 0x0c, 0xdd, 0x51, 0xcc, 0x3c, 0x8b, 0xb9, 0x34, 0x3f, 0xe6, 0x6d, 0x65, 0xdf, 0x04, 0xfd, - 0x12, 0x99, 0xa9, 0x20, 0xb3, 0x98, 0xcb, 0x6f, 0x11, 0x67, 0x2a, 0x48, 0x13, 0xd2, 0x47, 0xbb, - 0x42, 0x15, 0x9f, 0x1b, 0x82, 0xd4, 0x54, 0x15, 0x07, 0x10, 0x51, 0x31, 0x29, 0xc0, 0x57, 0xe6, - 0x07, 0xdf, 0xd1, 0x40, 0x9f, 0x2b, 0x1c, 0xa7, 0x80, 0xc9, 0xbd, 0x0c, 0x51, 0xef, 0x66, 0x2f, - 0x65, 0x82, 0x56, 0x75, 0x82, 0x7e, 0x70, 0x03, 0x44, 0x99, 0xa5, 0x43, 0x74, 0x37, 0xc4, 0x2f, - 0x5d, 0x39, 0xe1, 0x4c, 0xca, 0x00, 0x7c, 0x37, 0xc6, 0xe4, 0x12, 0xa4, 0xd0, 0x73, 0xa5, 0xe5, - 0xdc, 0x09, 0xf1, 0xcb, 0xf3, 0x62, 0x6f, 0x94, 0x6d, 0x99, 0x02, 0xbd, 0x5f, 0xa3, 0xe1, 0x17, - 0x98, 0xfb, 0xae, 0x0f, 0x11, 0x0b, 0x5d, 0x0e, 0x63, 0xc5, 0x8f, 0x38, 0x63, 0x64, 0x80, 0x72, - 0x94, 0xe4, 0x8d, 0xac, 0x5e, 0x06, 0x65, 0x13, 0x0f, 0x19, 0x8d, 0xf2, 0x79, 0x6b, 0x55, 0x6c, - 0xad, 0xd0, 0x1e, 0x2a, 0x30, 0xa7, 0x86, 0xf5, 0x08, 0xc0, 0xf2, 0xd0, 0xed, 0xc7, 0x38, 0xf2, - 0xc5, 0x04, 0x5f, 0xc2, 0xe7, 0x20, 0xb1, 0x8f, 0x25, 0x6e, 0xf4, 0xcc, 0x33, 0x00, 0x37, 0x66, - 0x2c, 0xc8, 0x7a, 0x26, 0xa3, 0xa0, 0xb2, 0x67, 0x1e, 0x01, 0x8c, 0x18, 0x0b, 0x54, 0xcf, 0x98, - 0x5d, 0xb4, 0x9a, 0x02, 0x17, 0x55, 0x05, 0x17, 0x4b, 0xeb, 0x27, 0xa8, 0xad, 0x49, 0xe3, 0x88, - 0x5c, 0x0a, 0x73, 0x17, 0xb5, 0x15, 0x12, 0x08, 0x01, 0xa2, 0x6b, 0xf4, 0x5b, 0xfb, 0x6d, 0xa7, - 0x12, 0x58, 0x12, 0xed, 0xbc, 0xee, 0x2d, 0x23, 0xcc, 0xa7, 0x68, 0x35, 0x06, 0x3d, 0x68, 0xb5, - 0x61, 0xe7, 0xf0, 0x63, 0x7b, 0x8e, 0xf7, 0xa2, 0xfd, 0x3a, 0x40, 0xa7, 0x40, 0xb3, 0x78, 0xf5, - 0x82, 0x9a, 0x99, 0x15, 0xc2, 0xbc, 0x98, 0x75, 0xfa, 0xf3, 0xb7, 0x72, 0x3a, 0x83, 0x57, 0xf9, - 0xfc, 0x00, 0x75, 0x8e, 0xb2, 0x63, 0xff, 0x92, 0x0a, 0x79, 0xfd, 0x5a, 0xd6, 0xeb, 0xd7, 0xf2, - 0x0b, 0xb4, 0x39, 0x9c, 0xe0, 0x28, 0x82, 0xe0, 0x9c, 0x69, 0xe2, 0x33, 0x7f, 0x88, 0x10, 0xc9, - 0x24, 0x8a, 0x30, 0xb3, 0xb4, 0xb4, 0x73, 0xc9, 0x89, 0xdf, 0x18, 0x55, 0x8b, 0x8d, 0x51, 0x65, - 0x39, 0x68, 0xeb, 0x42, 0x90, 0x5f, 0x15, 0x6f, 0x96, 0x27, 0xb1, 0x30, 0xef, 0xa2, 0x15, 0xd5, - 0xab, 0x39, 0xd0, 0x92, 0xb3, 0x9c, 0x0a, 0x72, 0xe2, 0x9b, 0xfb, 0xf5, 0x77, 0x11, 0x8b, 0x5d, - 0xea, 0x8b, 0xee, 0x62, 0xbf, 0xb5, 0xbf, 0xe4, 0x6c, 0x26, 0x95, 0xf9, 0x89, 0x2f, 0xac, 0x5f, - 0xa3, 0x4e, 0x0d, 0xd0, 0xdc, 0x44, 0x8b, 0x25, 0xd6, 0x22, 0xf5, 0xcd, 0x07, 0x68, 0xa7, 0x02, - 0x6a, 0xd2, 0x7d, 0x86, 0xd8, 0x76, 0xee, 0x95, 0x0a, 0x0d, 0xc6, 0x17, 0xd6, 0x13, 0xb4, 0x7d, - 0x52, 0x91, 0x4b, 0x39, 0x4c, 0x1a, 0x27, 0x34, 0x9a, 0xc3, 0x78, 0x17, 0xb5, 0xcb, 0xc7, 0xbf, - 0x3e, 0xfd, 0x92, 0x53, 0x09, 0xac, 0x10, 0xdd, 0xba, 0x10, 0xe4, 0x0c, 0x22, 0xbf, 0x02, 0x7b, - 0xcd, 0x05, 0x1c, 0xcf, 0x02, 0xcd, 0xfd, 0xb8, 0xac, 0xdc, 0xfd, 0xc1, 0x40, 0xdd, 0x53, 0x98, - 0x1e, 0x09, 0x41, 0xc7, 0x51, 0x08, 0x91, 0x54, 0x64, 0x81, 0x09, 0xa8, 0x4f, 0xf3, 0x3d, 0xb4, - 0x51, 0x36, 0x5a, 0xd9, 0x5f, 0xeb, 0xce, 0x7a, 0x21, 0xd4, 0x8d, 0xf5, 0x00, 0xa1, 0x98, 0x43, - 0xea, 0x12, 0xf7, 0x12, 0xa6, 0x79, 0x18, 0xbb, 0xf5, 0x59, 0x93, 0xfd, 0x5b, 0xd8, 0xa3, 0xc4, - 0x0b, 0x28, 0x39, 0x85, 0xa9, 0xb3, 0xa6, 0xf4, 0x87, 0xa7, 0x30, 0x55, 0x6f, 0x87, 0x98, 0xbd, - 0x00, 0xae, 0x07, 0x44, 0xcb, 0xc9, 0x16, 0xd6, 0x9f, 0x0c, 0x74, 0xef, 0x02, 0x07, 0xd4, 0xc7, - 0x92, 0xf1, 0xe2, 0xbe, 0x47, 0x89, 0xa7, 0x2c, 0xde, 0x70, 0xaf, 0xd7, 0xa2, 0x5d, 0xbc, 0x21, - 0xda, 0x4f, 0xd0, 0x7a, 0x99, 0x61, 0x15, 0x6f, 0x6b, 0x8e, 0x78, 0x3b, 0x85, 0xc5, 0x29, 0x4c, - 0xad, 0xdf, 0xd5, 0x62, 0x3b, 0x9e, 0xd6, 0x9a, 0x97, 0xff, 0x87, 0xd8, 0x4a, 0xb7, 0xf5, 0xd8, - 0x48, 0xdd, 0xfe, 0xda, 0x01, 0x5a, 0xd7, 0x0f, 0x60, 0xfd, 0xc5, 0x40, 0xdb, 0x75, 0xaf, 0xe2, - 0x9c, 0x8d, 0x78, 0x12, 0xc1, 0x9b, 0xbc, 0x57, 0xf5, 0xb3, 0x58, 0xaf, 0x9f, 0xa7, 0x68, 0xb3, - 0x11, 0x94, 0xc8, 0x6f, 0xe3, 0xa7, 0x73, 0x51, 0x48, 0x8d, 0x1e, 0x9c, 0x8d, 0xfa, 0x39, 0xc4, - 0xf1, 0xd3, 0x6f, 0xaf, 0x7a, 0xc6, 0x77, 0x57, 0x3d, 0xe3, 0x1f, 0x57, 0x3d, 0xe3, 0xab, 0x57, - 0xbd, 0x85, 0xef, 0x5e, 0xf5, 0x16, 0xfe, 0xf6, 0xaa, 0xb7, 0xf0, 0x9b, 0x8f, 0xc7, 0x54, 0x4e, - 0x12, 0xcf, 0x26, 0x2c, 0x1c, 0xe4, 0x3f, 0x8e, 0x95, 0xaf, 0x0f, 0xcb, 0xff, 0xf0, 0xf4, 0x70, - 0xf0, 0xb2, 0xf9, 0x33, 0x2e, 0xa7, 0x31, 0x08, 0x6f, 0x45, 0x97, 0xf5, 0x47, 0xff, 0x0e, 0x00, - 0x00, 0xff, 0xff, 0x50, 0x7d, 0x10, 0xbd, 0xbd, 0x0f, 0x00, 0x00, + // 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, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -1342,6 +1350,13 @@ func (m *ConsumerAdditionProposal) MarshalToSizedBuffer(dAtA []byte) (int, error _ = i var l int _ = l + if len(m.DistributionTransmissionChannel) > 0 { + i -= len(m.DistributionTransmissionChannel) + copy(dAtA[i:], m.DistributionTransmissionChannel) + i = encodeVarintProvider(dAtA, i, uint64(len(m.DistributionTransmissionChannel))) + i-- + dAtA[i] = 0x72 + } if m.HistoricalEntries != 0 { i = encodeVarintProvider(dAtA, i, uint64(m.HistoricalEntries)) i-- @@ -2298,6 +2313,10 @@ func (m *ConsumerAdditionProposal) Size() (n int) { if m.HistoricalEntries != 0 { n += 1 + sovProvider(uint64(m.HistoricalEntries)) } + l = len(m.DistributionTransmissionChannel) + if l > 0 { + n += 1 + l + sovProvider(uint64(l)) + } return n } @@ -3080,6 +3099,38 @@ func (m *ConsumerAdditionProposal) Unmarshal(dAtA []byte) error { break } } + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DistributionTransmissionChannel", 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.DistributionTransmissionChannel = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipProvider(dAtA[iNdEx:]) diff --git a/x/ccv/types/shared_params.go b/x/ccv/types/shared_params.go index 000a4c3815..04f1cf54e1 100644 --- a/x/ccv/types/shared_params.go +++ b/x/ccv/types/shared_params.go @@ -57,6 +57,16 @@ func ValidateString(i interface{}) error { return nil } +func ValidateDistributionTransmissionChannel(i interface{}) error { + // Accept empty string as valid, since this means a new + // distribution transmission channel will be created + if i == "" { + return nil + } + // Otherwise validate as usual for a channelID + return ValidateChannelIdentifier(i) +} + func ValidateChannelIdentifier(i interface{}) error { value, ok := i.(string) if !ok { From 362b7c0d86a7c8c052eeedade1f9d9721ef4264a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:57:25 +0200 Subject: [PATCH 08/82] refactor: log when constructing IBC err ack (backport #1090) (#1094) refactor: log when constructing IBC err ack (#1090) * log with err ack * linter (cherry picked from commit 07302ffb6692fe849f5f5d393de5db6395e74a53) Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> --- tests/integration/slashing.go | 7 +++---- x/ccv/consumer/ibc_module.go | 2 +- x/ccv/consumer/keeper/relay_test.go | 2 +- x/ccv/provider/ibc_module.go | 4 ++-- x/ccv/provider/keeper/relay.go | 6 +++--- x/ccv/types/utils.go | 5 +++++ 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/tests/integration/slashing.go b/tests/integration/slashing.go index 6e60bc5df3..d44ae24c1b 100644 --- a/tests/integration/slashing.go +++ b/tests/integration/slashing.go @@ -256,7 +256,7 @@ func (s *CCVTestSuite) TestSlashPacketAcknowledgement() { err := consumerKeeper.OnAcknowledgementPacket(s.consumerCtx(), packet, channeltypes.NewResultAcknowledgement(ack.Acknowledgement())) s.Require().NoError(err) - err = consumerKeeper.OnAcknowledgementPacket(s.consumerCtx(), packet, channeltypes.NewErrorAcknowledgement(fmt.Errorf("another error"))) + err = consumerKeeper.OnAcknowledgementPacket(s.consumerCtx(), packet, ccv.NewErrorAcknowledgementWithLog(s.consumerCtx(), fmt.Errorf("another error"))) s.Require().Error(err) } @@ -331,7 +331,8 @@ func (suite *CCVTestSuite) TestOnRecvSlashPacketErrors() { errAck := providerKeeper.OnRecvSlashPacket(ctx, packet, packetData) suite.Require().False(errAck.Success()) errAckCast := errAck.(channeltypes.Acknowledgement) - // TODO: see if there's a way to get error reason like before + // Error strings in err acks are now thrown out by IBC core to prevent app hash. + // Hence a generic error string is expected. suite.Require().Equal("ABCI code: 1: error handling packet: see events for details", errAckCast.GetError()) // Restore init chain height @@ -342,7 +343,6 @@ func (suite *CCVTestSuite) TestOnRecvSlashPacketErrors() { errAck = providerKeeper.OnRecvSlashPacket(ctx, packet, packetData) suite.Require().False(errAck.Success()) errAckCast = errAck.(channeltypes.Acknowledgement) - // TODO: see if there's a way to get error reason like before suite.Require().Equal("ABCI code: 1: error handling packet: see events for details", errAckCast.GetError()) // save current VSC ID @@ -358,7 +358,6 @@ func (suite *CCVTestSuite) TestOnRecvSlashPacketErrors() { errAck = providerKeeper.OnRecvSlashPacket(ctx, packet, packetData) suite.Require().False(errAck.Success()) errAckCast = errAck.(channeltypes.Acknowledgement) - // TODO: see if there's a way to get error reason like before suite.Require().Equal("ABCI code: 1: error handling packet: see events for details", errAckCast.GetError()) // construct slashing packet with non existing validator diff --git a/x/ccv/consumer/ibc_module.go b/x/ccv/consumer/ibc_module.go index b3dde3d48a..b4a15e3de8 100644 --- a/x/ccv/consumer/ibc_module.go +++ b/x/ccv/consumer/ibc_module.go @@ -228,7 +228,7 @@ func (am AppModule) OnRecvPacket( data types.ValidatorSetChangePacketData ) if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - errAck := channeltypes.NewErrorAcknowledgement(fmt.Errorf("cannot unmarshal CCV packet data")) + errAck := types.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf("cannot unmarshal CCV packet data")) ack = &errAck } else { ack = am.keeper.OnRecvVSCPacket(ctx, packet, data) diff --git a/x/ccv/consumer/keeper/relay_test.go b/x/ccv/consumer/keeper/relay_test.go index 60fbbfe719..88099cad98 100644 --- a/x/ccv/consumer/keeper/relay_test.go +++ b/x/ccv/consumer/keeper/relay_test.go @@ -276,7 +276,7 @@ func TestOnAcknowledgementPacket(t *testing.T) { ).Return(nil).Times(1), ) - ack = channeltypes.NewErrorAcknowledgement(fmt.Errorf("error")) + ack = types.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf("error")) err = consumerKeeper.OnAcknowledgementPacket(ctx, packet, ack) require.Nil(t, err) } diff --git a/x/ccv/provider/ibc_module.go b/x/ccv/provider/ibc_module.go index a37f291340..1b48f900b4 100644 --- a/x/ccv/provider/ibc_module.go +++ b/x/ccv/provider/ibc_module.go @@ -178,7 +178,7 @@ func (am AppModule) OnRecvPacket( ) // unmarshall consumer packet if err := ccv.ModuleCdc.UnmarshalJSON(packet.GetData(), &consumerPacket); err != nil { - errAck := channeltypes.NewErrorAcknowledgement(fmt.Errorf("cannot unmarshal CCV packet data")) + errAck := ccv.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf("cannot unmarshal CCV packet data")) ack = &errAck } else { // TODO: call ValidateBasic method on consumer packet data @@ -192,7 +192,7 @@ func (am AppModule) OnRecvPacket( // handle SlashPacket ack = am.keeper.OnRecvSlashPacket(ctx, packet, *consumerPacket.GetSlashPacketData()) default: - errAck := channeltypes.NewErrorAcknowledgement(fmt.Errorf("invalid consumer packet type: %q", consumerPacket.Type)) + errAck := ccv.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf("invalid consumer packet type: %q", consumerPacket.Type)) ack = &errAck } } diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index ea07e0e9af..65edeac170 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -32,7 +32,7 @@ func (k Keeper) OnRecvVSCMaturedPacket( } if err := k.QueueThrottledVSCMaturedPacketData(ctx, chainID, packet.Sequence, data); err != nil { - return channeltypes.NewErrorAcknowledgement(fmt.Errorf( + return ccv.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf( "failed to queue VSCMatured packet data: %s", err.Error())) } @@ -319,7 +319,7 @@ func (k Keeper) OnRecvSlashPacket(ctx sdk.Context, packet channeltypes.Packet, d "vscID", data.ValsetUpdateId, "infractionType", data.Infraction, ) - return channeltypes.NewErrorAcknowledgement(err) + return ccv.NewErrorAcknowledgementWithLog(ctx, err) } // The slash packet validator address may be known only on the consumer chain, @@ -355,7 +355,7 @@ func (k Keeper) OnRecvSlashPacket(ctx sdk.Context, packet channeltypes.Packet, d // Queue slash packet data in the same (consumer chain specific) queue as vsc matured packet data, // to enforce order of handling between the two packet data types. if err := k.QueueThrottledSlashPacketData(ctx, chainID, packet.Sequence, data); err != nil { - return channeltypes.NewErrorAcknowledgement(fmt.Errorf("failed to queue slash packet data: %s", err.Error())) + return ccv.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf("failed to queue slash packet data: %s", err.Error())) } k.Logger(ctx).Info("slash packet received and enqueued", diff --git a/x/ccv/types/utils.go b/x/ccv/types/utils.go index 1d75152186..143051f8ae 100644 --- a/x/ccv/types/utils.go +++ b/x/ccv/types/utils.go @@ -92,6 +92,11 @@ func SendIBCPacket( return channelKeeper.SendPacket(ctx, channelCap, packet) } +func NewErrorAcknowledgementWithLog(ctx sdk.Context, err error) channeltypes.Acknowledgement { + ctx.Logger().Error("IBC ErrorAcknowledgement constructed", "error", err) + return channeltypes.NewErrorAcknowledgement(err) +} + // AppendMany appends a variable number of byte slices together func AppendMany(byteses ...[]byte) (out []byte) { for _, bytes := range byteses { From 16e71ab22104396b7ca3a08c83cd0a99810c31c9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 6 Jul 2023 11:22:43 +0200 Subject: [PATCH 09/82] fix: `AttributeDistributionTotal` in event emit (backport #1097) (#1114) * fix: `AttributeDistributionTotal` in event emit (#1097) * fix: emitted distribution events * docs: update changelog * fix: lint --------- Co-authored-by: MSalopek (cherry picked from commit d16c7669b4f23380a50c8dedfaf6d25f50ca0edd) # Conflicts: # CHANGELOG.md # x/ccv/consumer/keeper/distribution.go * resolve conflicts * docs: remove v3 changelog from v2 release line --------- Co-authored-by: yaruwangway <69694322+yaruwangway@users.noreply.github.com> Co-authored-by: Yaru Wang --- CHANGELOG.md | 8 +++++++- x/ccv/consumer/keeper/distribution.go | 10 ++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1528717b13..11fc543bc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # CHANGELOG -## v.2.0.0 +## [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. + +* (fix) [#720](https://github.com/cosmos/interchain-security/issues/720) Fix the attribute `AttributeDistributionTotal` value in `FeeDistribution` event emit. + +## v2.0.0 Date: June 1st, 2023 diff --git a/x/ccv/consumer/keeper/distribution.go b/x/ccv/consumer/keeper/distribution.go index 6d56f6c901..1209d433f6 100644 --- a/x/ccv/consumer/keeper/distribution.go +++ b/x/ccv/consumer/keeper/distribution.go @@ -113,11 +113,12 @@ func (k Keeper) SendRewardsToProvider(ctx sdk.Context) error { timeoutTimestamp := uint64(ctx.BlockTime().Add(transferTimeoutPeriod).UnixNano()) sentCoins := sdk.NewCoins() - + var allBalances sdk.Coins // iterate over all whitelisted reward denoms for _, denom := range k.AllowedRewardDenoms(ctx) { // get the balance of the denom in the toSendToProviderTokens address balance := k.bankKeeper.GetBalance(ctx, tstProviderAddr, denom) + allBalances = allBalances.Add(balance) // if the balance is not zero, if !balance.IsZero() { @@ -138,11 +139,8 @@ func (k Keeper) SendRewardsToProvider(ctx sdk.Context) error { } } - consumerFeePoolAddr := k.authKeeper.GetModuleAccount(ctx, k.feeCollectorName).GetAddress() - fpTokens := k.bankKeeper.GetAllBalances(ctx, consumerFeePoolAddr) - k.Logger(ctx).Info("sent block rewards to provider", - "total fee pool", fpTokens.String(), + "total fee pool", allBalances.String(), "sent", sentCoins.String(), ) currentHeight := ctx.BlockHeight() @@ -153,7 +151,7 @@ func (k Keeper) SendRewardsToProvider(ctx sdk.Context) error { sdk.NewAttribute(ccv.AttributeDistributionCurrentHeight, strconv.Itoa(int(currentHeight))), sdk.NewAttribute(ccv.AttributeDistributionNextHeight, strconv.Itoa(int(currentHeight+k.GetBlocksPerDistributionTransmission(ctx)))), sdk.NewAttribute(ccv.AttributeDistributionFraction, (k.GetConsumerRedistributionFrac(ctx))), - sdk.NewAttribute(ccv.AttributeDistributionTotal, fpTokens.String()), + sdk.NewAttribute(ccv.AttributeDistributionTotal, allBalances.String()), sdk.NewAttribute(ccv.AttributeDistributionToProvider, sentCoins.String()), ), ) From 8a1b3caa1c7bf24f59f22208970000c354c172d1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 12:30:14 -0700 Subject: [PATCH 10/82] docs: update broken md links (backport #1130) (#1142) * docs: update broken md links (#1130) (cherry picked from commit fd76f45b726f4ef65f817e8032c4f87f986f2d71) # Conflicts: # docs/docs/validators/joining-testnet.md * Update joining-testnet.md --------- Co-authored-by: MSalopek Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> --- docs/docs/features/proposals.md | 2 +- docs/docs/validators/joining-testnet.md | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/docs/features/proposals.md b/docs/docs/features/proposals.md index a34160ecf4..b68fd343d6 100644 --- a/docs/docs/features/proposals.md +++ b/docs/docs/features/proposals.md @@ -48,7 +48,7 @@ Minimal example: "distribution_transmission_channel": "channel-123" } ``` -More examples can be found in the replicated security testnet repository [here](https://github.com/cosmos/testnets/blob/master/replicated-security/baryon-1/proposal-baryon-1.json) and [here](https://github.com/cosmos/testnets/blob/master/replicated-security/noble-1/start-proposal-noble-1.json). +More examples can be found in the replicated security testnet repository [here](https://github.com/cosmos/testnets/blob/master/replicated-security/stopped/baryon-1/proposal-baryon-1.json) and [here](https://github.com/cosmos/testnets/blob/master/replicated-security/stopped/noble-1/start-proposal-noble-1.json). ## `ConsumerRemovalProposal` Proposal type used to suggest removing an existing consumer chain. diff --git a/docs/docs/validators/joining-testnet.md b/docs/docs/validators/joining-testnet.md index b9d9f8f9da..6ddd845c48 100644 --- a/docs/docs/validators/joining-testnet.md +++ b/docs/docs/validators/joining-testnet.md @@ -169,8 +169,7 @@ gaiad tx provider assign-consensus-key consumer-1 '' --from Date: Mon, 14 Aug 2023 23:24:41 +0200 Subject: [PATCH 11/82] 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 309bd853e334f9db206d845dee29661f7367fef6 Mon Sep 17 00:00:00 2001 From: MSalopek Date: Wed, 16 Aug 2023 13:42:08 +0200 Subject: [PATCH 12/82] deps!: support cosmos-sdk-v45-ics-lsm integration (#1120) * tests: check cosmos-sdk-v45-ics-lsm integration * bump cosmos-sdk to inqlusion latest * support new method signatuers * fix: bump v0.45.16-ics-lsm to latest [fixes difftests] * fix: update democracy tests representative registration * chore: bump iqlusion:cosmos-sdk to latest * chore: bump iqlusion:cosmos-sdk to latest * chore: bump iqlusion:cosmos-sdk to latest * fix!: avoid panicking on CancelUnbondingDelegation (#977) * handle CancelUnbondingDelegation message * tests: add cancel-unbond e2e test * fix: appease linter * chore: Hardcode golangci-lint version (#990) * Hardcode golangci-lint version * Hardcode version in CI config --------- Co-authored-by: MSalopek Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * tests: fix broken cancelUnbond e2e after cherry pick * chore: regenerate mocks * chore: run make proto-gen * fix: complete concel-unbond handling in hooks * chore: bump iqlusion:cosmos-sdk to latest * chore: fix brokene gov prop submit * chore: fix brokene gov prop submit * use SDK 0.45.16-ics-lsm-rc0 * add changelog entry --------- Co-authored-by: Marius Poke Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> --- CHANGELOG.md | 1 + go.mod | 2 +- go.sum | 4 +- legacy_ibc_testing/testing/app.go | 25 ++++---- tests/difference/core/driver/setup.go | 24 +++---- tests/e2e/actions.go | 72 ++++++++++++++++++++- tests/e2e/main.go | 2 + tests/e2e/step_delegation.go | 91 +++++++++++++++++++++++++++ tests/e2e/steps.go | 1 + testutil/keeper/mocks.go | 15 +++++ x/ccv/provider/keeper/hooks.go | 45 +++++++++---- x/ccv/provider/keeper/relay.go | 14 ++++- x/ccv/types/expected_keepers.go | 1 + 13 files changed, 254 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11fc543bc9..5a5011e0c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ 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. +* (deps!) [#1120](https://github.com/cosmos/interchain-security/pull/1120) Bump [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) to [v0.45.16-ics-lsm](https://github.com/cosmos/cosmos-sdk/tree/v0.45.16-ics-lsm). This requires adapting ICS to support this SDK release. Changes are state breaking. * (fix) [#720](https://github.com/cosmos/interchain-security/issues/720) Fix the attribute `AttributeDistributionTotal` value in `FeeDistribution` event emit. ## v2.0.0 diff --git a/go.mod b/go.mod index 58b8eaa667..a3423e16e5 100644 --- a/go.mod +++ b/go.mod @@ -153,7 +153,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.45.15-ics + github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.45.16-ics-lsm-rc0 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/tendermint/tendermint => github.com/cometbft/cometbft v0.34.28 google.golang.org/grpc => google.golang.org/grpc v1.33.2 diff --git a/go.sum b/go.sum index ed38bf1bab..19a06e3c81 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,8 @@ github.com/cosmos/cosmos-db v0.0.0-20221226095112-f3c38ecb5e32 h1:zlCp9n3uwQieEL github.com/cosmos/cosmos-db v0.0.0-20221226095112-f3c38ecb5e32/go.mod h1:kwMlEC4wWvB48zAShGKVqboJL6w4zCLesaNQ3YLU2BQ= github.com/cosmos/cosmos-proto v1.0.0-beta.1 h1:iDL5qh++NoXxG8hSy93FdYJut4XfgbShIocllGaXx/0= github.com/cosmos/cosmos-proto v1.0.0-beta.1/go.mod h1:8k2GNZghi5sDRFw/scPL8gMSowT1vDA+5ouxL8GjaUE= -github.com/cosmos/cosmos-sdk v0.45.15-ics h1:ujrXsulYGwggLCC0oD7CizvlAerqMQHfCHHjHqIamfY= -github.com/cosmos/cosmos-sdk v0.45.15-ics/go.mod h1:bScuNwWAP0TZJpUf+SHXRU3xGoUPp+X9nAzfeIXts40= +github.com/cosmos/cosmos-sdk v0.45.16-ics-lsm-rc0 h1:zFSOIuoTbVCCnqIbfG0KZ099bIr6i9G+J3IOR9dgG80= +github.com/cosmos/cosmos-sdk v0.45.16-ics-lsm-rc0/go.mod h1:bScuNwWAP0TZJpUf+SHXRU3xGoUPp+X9nAzfeIXts40= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= diff --git a/legacy_ibc_testing/testing/app.go b/legacy_ibc_testing/testing/app.go index 45d5248773..83c8a01d58 100644 --- a/legacy_ibc_testing/testing/app.go +++ b/legacy_ibc_testing/testing/app.go @@ -79,20 +79,23 @@ func SetupWithGenesisValSet(t *testing.T, appIniter AppIniter, valSet *tmtypes.V pkAny, err := codectypes.NewAnyWithValue(pk) require.NoError(t, err) validator := stakingtypes.Validator{ - OperatorAddress: sdk.ValAddress(val.Address).String(), - ConsensusPubkey: pkAny, - Jailed: false, - Status: stakingtypes.Bonded, - Tokens: bondAmt, - DelegatorShares: sdk.OneDec(), - Description: stakingtypes.Description{}, - UnbondingHeight: int64(0), - UnbondingTime: time.Unix(0, 0).UTC(), - Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), - MinSelfDelegation: sdk.ZeroInt(), + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: bondAmt, + DelegatorShares: sdk.OneDec(), + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + // MinSelfDelegation: sdk.ZeroInt(), } validators = append(validators, validator) + // NOTE: @MSalopek + // need more info about stakingtypes.Delegation.ValidatorBond flag usage + // setting this to both true or false does not affect the test results delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), sdk.OneDec())) } diff --git a/tests/difference/core/driver/setup.go b/tests/difference/core/driver/setup.go index 4798c2e3ca..196d59e524 100644 --- a/tests/difference/core/driver/setup.go +++ b/tests/difference/core/driver/setup.go @@ -176,17 +176,17 @@ func (b *Builder) getAppBytesAndSenders( require.NoError(b.suite.T(), err) validator := stakingtypes.Validator{ - OperatorAddress: sdk.ValAddress(val.Address).String(), - ConsensusPubkey: pkAny, - Jailed: false, - Status: status, - Tokens: tokens, - DelegatorShares: sumShares, - Description: stakingtypes.Description{}, - UnbondingHeight: int64(0), - UnbondingTime: time.Unix(0, 0).UTC(), - Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), - MinSelfDelegation: sdk.ZeroInt(), + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: status, + Tokens: tokens, + DelegatorShares: sumShares, + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + // MinSelfDelegation: sdk.ZeroInt(), } stakingValidators = append(stakingValidators, validator) @@ -413,7 +413,7 @@ func (b *Builder) addValidatorToStakingModule(privVal mock.PV) { coin, stakingtypes.Description{}, stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), - sdk.ZeroInt()) + ) b.suite.Require().NoError(err) pskServer := stakingkeeper.NewMsgServerImpl(b.providerStakingKeeper()) _, _ = pskServer.CreateValidator(sdk.WrapSDKContext(b.providerCtx()), msg) diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 06bcb7f5f7..702a82d5cd 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -331,8 +331,9 @@ func (tr TestRun) submitConsumerRemovalProposal( `--chain-id`, string(tr.chainConfigs[action.chain].chainId), `--home`, tr.getValidatorHome(action.chain, action.from), `--node`, tr.getValidatorNode(action.chain, action.from), - `--keyring-backend`, `test`, + `--gas`, `auto`, `-b`, `block`, + `--keyring-backend`, `test`, `-y`, ).CombinedOutput() @@ -1417,6 +1418,74 @@ func (tr TestRun) unbondTokens( tr.waitBlocks(action.chain, 1, 10*time.Second) } +type cancelUnbondTokensAction struct { + chain chainID + delegator validatorID + validator validatorID + amount uint +} + +func (tr TestRun) cancelUnbondTokens( + action cancelUnbondTokensAction, + verbose bool, +) { + validator := tr.validatorConfigs[action.validator].valoperAddress + if tr.validatorConfigs[action.validator].useConsumerKey { + validator = tr.validatorConfigs[action.validator].consumerValoperAddress + } + + // get creation-height from state + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, tr.chainConfigs[action.chain].binaryName, + "q", "staking", "unbonding-delegation", + tr.validatorConfigs[action.delegator].delAddress, + validator, + `--home`, tr.getValidatorHome(action.chain, action.delegator), + `--node`, tr.getValidatorNode(action.chain, action.delegator), + `-o`, `json`, + ) + if verbose { + fmt.Println("get unbonding delegations cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + creationHeight := gjson.Get(string(bz), "entries.0.creation_height").Int() + if creationHeight == 0 { + log.Fatal("invalid creation height") + } + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd = exec.Command("docker", "exec", tr.containerConfig.instanceName, tr.chainConfigs[action.chain].binaryName, + "tx", "staking", "cancel-unbond", + validator, + fmt.Sprint(action.amount)+`stake`, + fmt.Sprint(creationHeight), + `--from`, `validator`+fmt.Sprint(action.delegator), + `--chain-id`, string(tr.chainConfigs[action.chain].chainId), + `--home`, tr.getValidatorHome(action.chain, action.delegator), + `--node`, tr.getValidatorNode(action.chain, action.delegator), + `--gas`, "900000", + `--keyring-backend`, `test`, + `-o`, `json`, + `-y`, + ) + + if verbose { + fmt.Println("unbond cmd:", cmd.String()) + } + + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(action.chain, 2, 20*time.Second) +} + type redelegateTokensAction struct { chain chainID src validatorID @@ -1589,7 +1658,6 @@ func (tr TestRun) registerRepresentative( `--commission-rate`, "0.1", `--commission-max-rate`, "0.2", `--commission-max-change-rate`, "0.01", - `--min-self-delegation`, "1", `--from`, `validator`+fmt.Sprint(val), `--chain-id`, string(tr.chainConfigs[action.chain].chainId), `--home`, tr.getValidatorHome(action.chain, val), diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 737a318de2..bf89f73853 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -155,6 +155,8 @@ func (tr *TestRun) runStep(step Step, verbose bool) { tr.delegateTokens(action, verbose) case unbondTokensAction: tr.unbondTokens(action, verbose) + case cancelUnbondTokensAction: + tr.cancelUnbondTokens(action, verbose) case redelegateTokensAction: tr.redelegateTokens(action, verbose) case downtimeSlashAction: diff --git a/tests/e2e/step_delegation.go b/tests/e2e/step_delegation.go index f33fc136d4..b6e7c8ad76 100644 --- a/tests/e2e/step_delegation.go +++ b/tests/e2e/step_delegation.go @@ -129,6 +129,97 @@ func stepsUnbond(consumerName string) []Step { } } +// stepsCancelUnbond canceling unbonding operation for delegator and validator combination +// the steps perform a full unbonding where the unbonding delegation is removed from the unbonding queue +func stepsCancelUnbond(consumerName string) []Step { + return []Step{ + { + action: unbondTokensAction{ + chain: chainID("provi"), + unbondFrom: validatorID("alice"), + sender: validatorID("alice"), + amount: 1000000, + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 509, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + chainID("consu"): ChainState{ + ValPowers: &map[validatorID]uint{ + // Voting power on consumer should not be affected yet + validatorID("alice"): 510, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: relayPacketsAction{ + chainA: chainID("provi"), + chainB: chainID(consumerName), + port: "provider", + channel: 0, + }, + state: State{ + chainID("consu"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 509, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: cancelUnbondTokensAction{ + chain: chainID("provi"), + delegator: validatorID("alice"), + validator: validatorID("alice"), + amount: 1000000, // cancel unbonding the full amount + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 510, // power restored + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + chainID("consu"): ChainState{ + ValPowers: &map[validatorID]uint{ + // Voting power on consumer should not be affected yet + validatorID("alice"): 509, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: relayPacketsAction{ + chainA: chainID("provi"), + chainB: chainID(consumerName), + port: "provider", + channel: 0, + }, + state: State{ + chainID("consu"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 510, // power restored on consumer + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + } +} + // stepsRedelegateForOptOut tests redelegation, and sets up voting powers s.t // alice will have less than 5% of the total voting power. This is needed to // test opt-out functionality. diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 7613b05558..c9830c2aa2 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -18,6 +18,7 @@ var happyPathSteps = concatSteps( stepsDelegate("consu"), stepsAssignConsumerKeyOnStartedChain("consu", "bob"), stepsUnbond("consu"), + stepsCancelUnbond("consu"), stepsRedelegateForOptOut("consu"), stepsDowntimeWithOptOut("consu"), stepsRedelegate("consu"), diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 4f931d8152..9976a03b15 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -116,6 +116,21 @@ func (mr *MockStakingKeeperMockRecorder) GetLastValidators(ctx interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastValidators", reflect.TypeOf((*MockStakingKeeper)(nil).GetLastValidators), ctx) } +// GetUnbondingType mocks base method. +func (m *MockStakingKeeper) GetUnbondingType(ctx types.Context, id uint64) (types4.UnbondingType, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUnbondingType", ctx, id) + ret0, _ := ret[0].(types4.UnbondingType) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetUnbondingType indicates an expected call of GetUnbondingType. +func (mr *MockStakingKeeperMockRecorder) GetUnbondingType(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnbondingType", reflect.TypeOf((*MockStakingKeeper)(nil).GetUnbondingType), ctx, id) +} + // GetValidator mocks base method. func (m *MockStakingKeeper) GetValidator(ctx types.Context, addr types.ValAddress) (types4.Validator, bool) { m.ctrl.T.Helper() diff --git a/x/ccv/provider/keeper/hooks.go b/x/ccv/provider/keeper/hooks.go index 3cf1e6cc30..537153757b 100644 --- a/x/ccv/provider/keeper/hooks.go +++ b/x/ccv/provider/keeper/hooks.go @@ -1,8 +1,6 @@ package keeper import ( - "fmt" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -34,6 +32,19 @@ func (h Hooks) AfterUnbondingInitiated(ctx sdk.Context, id uint64) error { // Do not put the unbonding op on hold if there are no consumer chains return nil } + // Call back into staking to tell it to stop this op from unbonding when the unbonding period is complete + if err := h.k.stakingKeeper.PutUnbondingOnHold(ctx, id); err != nil { + // Note: that in the case of a validator unbonding, AfterUnbondingInitiated is called + // from staking.EndBlock. + + // In this case PutUnbondingOnHold fails if either the unbonding operation was + // not found or the UnbondingOnHoldRefCount is negative. + + // This change should be updated for SDK v0.48 because it will include changes in handling + // check: https://github.com/cosmos/cosmos-sdk/pull/16043 + ctx.Logger().Error("unbonding could not be put on hold: %w", err) + return nil + } valsetUpdateID := h.k.GetValidatorSetUpdateId(ctx) unbondingOp := providertypes.UnbondingOp{ Id: id, @@ -49,18 +60,22 @@ func (h Hooks) AfterUnbondingInitiated(ctx sdk.Context, id uint64) error { h.k.SetUnbondingOp(ctx, unbondingOp) + // NOTE: This is a temporary fix backported from ICSv3 (cosmos-sdk v0.47) -> we should not panic in this edge case + // since the AfterUnbondInitiatedHook can be called with a non-existing UnbondingEntry.id + // check: https://github.com/cosmos/cosmos-sdk/pull/16043 + // // Call back into staking to tell it to stop this op from unbonding when the unbonding period is complete - if err := h.k.stakingKeeper.PutUnbondingOnHold(ctx, id); err != nil { - // If there was an error putting the unbonding on hold, panic to end execution for - // the current tx and prevent committal of this invalid state. - // - // Note: that in the case of a validator unbonding, AfterUnbondingInitiated is called - // form staking.EndBlock, thus the following panic would halt the chain. - // In this case PutUnbondingOnHold fails if either the unbonding operation was - // not found or the UnbondingOnHoldRefCount is negative. In either cases, - // the state of the x/staking module of cosmos-sdk is invalid. - panic(fmt.Errorf("unbonding could not be put on hold: %w", err)) - } + // if err := h.k.stakingKeeper.PutUnbondingOnHold(ctx, id); err != nil { + // // If there was an error putting the unbonding on hold, panic to end execution for + // // the current tx and prevent committal of this invalid state. + // // + // // Note: that in the case of a validator unbonding, AfterUnbondingInitiated is called + // // from staking.EndBlock, thus the following panic would halt the chain. + // // In this case PutUnbondingOnHold fails if either the unbonding operation was + // // not found or the UnbondingOnHoldRefCount is negative. In either cases, + // // the state of the x/staking module of cosmos-sdk is invalid. + // panic(fmt.Errorf("unbonding could not be put on hold: %w", err)) + // } return nil } @@ -140,3 +155,7 @@ func (h Hooks) AfterValidatorBeginUnbonding(_ sdk.Context, _ sdk.ConsAddress, _ func (h Hooks) BeforeDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) { } + +func (h Hooks) BeforeTokenizeShareRecordRemoved(_ sdk.Context, _ uint64) error { + return nil +} diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 65edeac170..637036fe41 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -114,8 +114,18 @@ func (k Keeper) completeMaturedUnbondingOps(ctx sdk.Context) { // Attempt to complete unbonding in staking module err := k.stakingKeeper.UnbondingCanComplete(ctx, id) if err != nil { - // UnbondingCanComplete fails if the unbonding operation was not found, - // which means that the state of the x/staking module of cosmos-sdk is invalid. + if stakingtypes.ErrUnbondingNotFound.Is(err) { + // The unbonding was not found. + unbondingType, found := k.stakingKeeper.GetUnbondingType(ctx, id) + if found && unbondingType == stakingtypes.UnbondingType_UnbondingDelegation { + // If this is an unbonding delegation, it may have been removed + // after through a CancelUnbondingDelegation message + k.Logger(ctx).Debug("unbonding delegation was already removed:", "unbondingID", id) + continue + } + } + // UnbondingCanComplete failing means that the state of the x/staking module + // of cosmos-sdk is invalid. An exception is the case handled above panic(fmt.Sprintf("could not complete unbonding op: %s", err.Error())) } k.Logger(ctx).Debug("unbonding operation matured on all consumers", "opID", id) diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 7f18324c8d..dd81c5e801 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -44,6 +44,7 @@ type StakingKeeper interface { GetLastTotalPower(ctx sdk.Context) sdk.Int GetLastValidators(ctx sdk.Context) (validators []stakingtypes.Validator) BondDenom(ctx sdk.Context) (res string) + GetUnbondingType(ctx sdk.Context, id uint64) (unbondingType stakingtypes.UnbondingType, found bool) } type EvidenceKeeper interface { From 2573975200beba5f9fb90cf1f86c6162b8d30344 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Wed, 16 Aug 2023 13:54:18 +0200 Subject: [PATCH 13/82] docs: add v2.0.0-lsm changelog section (#1210) add changelog section --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a5011e0c6..8603bcdb9d 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.0.0-lsm + +Date: August 16th, 2023 + * (deps!) [#1120](https://github.com/cosmos/interchain-security/pull/1120) Bump [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) to [v0.45.16-ics-lsm](https://github.com/cosmos/cosmos-sdk/tree/v0.45.16-ics-lsm). This requires adapting ICS to support this SDK release. Changes are state breaking. * (fix) [#720](https://github.com/cosmos/interchain-security/issues/720) Fix the attribute `AttributeDistributionTotal` value in `FeeDistribution` event emit. From 6b0a94a633e9b13f9fd1b09bd015782b4e1ca5e5 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Fri, 18 Aug 2023 19:50:17 +0200 Subject: [PATCH 14/82] deps: bump SDK to v0.45.16-ics-lsm (#1212) bump SDK to v0.45.16-ics-lsm --- CHANGELOG.md | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8603bcdb9d..0d1f7a4e1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Add an entry to the unreleased section whenever merging a PR to main that is not ## v2.0.0-lsm -Date: August 16th, 2023 +Date: August 18th, 2023 * (deps!) [#1120](https://github.com/cosmos/interchain-security/pull/1120) Bump [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) to [v0.45.16-ics-lsm](https://github.com/cosmos/cosmos-sdk/tree/v0.45.16-ics-lsm). This requires adapting ICS to support this SDK release. Changes are state breaking. * (fix) [#720](https://github.com/cosmos/interchain-security/issues/720) Fix the attribute `AttributeDistributionTotal` value in `FeeDistribution` event emit. diff --git a/go.mod b/go.mod index a3423e16e5..e8e8dbc197 100644 --- a/go.mod +++ b/go.mod @@ -153,7 +153,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.45.16-ics-lsm-rc0 + github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.45.16-ics-lsm github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/tendermint/tendermint => github.com/cometbft/cometbft v0.34.28 google.golang.org/grpc => google.golang.org/grpc v1.33.2 diff --git a/go.sum b/go.sum index 19a06e3c81..3d7480161f 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,8 @@ github.com/cosmos/cosmos-db v0.0.0-20221226095112-f3c38ecb5e32 h1:zlCp9n3uwQieEL github.com/cosmos/cosmos-db v0.0.0-20221226095112-f3c38ecb5e32/go.mod h1:kwMlEC4wWvB48zAShGKVqboJL6w4zCLesaNQ3YLU2BQ= github.com/cosmos/cosmos-proto v1.0.0-beta.1 h1:iDL5qh++NoXxG8hSy93FdYJut4XfgbShIocllGaXx/0= github.com/cosmos/cosmos-proto v1.0.0-beta.1/go.mod h1:8k2GNZghi5sDRFw/scPL8gMSowT1vDA+5ouxL8GjaUE= -github.com/cosmos/cosmos-sdk v0.45.16-ics-lsm-rc0 h1:zFSOIuoTbVCCnqIbfG0KZ099bIr6i9G+J3IOR9dgG80= -github.com/cosmos/cosmos-sdk v0.45.16-ics-lsm-rc0/go.mod h1:bScuNwWAP0TZJpUf+SHXRU3xGoUPp+X9nAzfeIXts40= +github.com/cosmos/cosmos-sdk v0.45.16-ics-lsm h1:Cld5lg+lXvqT8plyy0l5Aytir4PdxWMHNLyFbOE3iMs= +github.com/cosmos/cosmos-sdk v0.45.16-ics-lsm/go.mod h1:bScuNwWAP0TZJpUf+SHXRU3xGoUPp+X9nAzfeIXts40= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= From 21e3d839ce3fc7141b6c808ab40b46899d5f9123 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 23 Aug 2023 16:38:35 +0200 Subject: [PATCH 15/82] 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 16/82] 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 17/82] 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 18/82] 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 19/82] 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 20/82] 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 21/82] 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 22/82] 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 23/82] 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 24/82] 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 25/82] 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 26/82] 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 27/82] 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 04ca9a1001c75144cbfbd9053458714cca4d624d Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 28 Sep 2023 11:12:09 +0200 Subject: [PATCH 28/82] fix e2e happy-path-short test --- Dockerfile | 2 +- tests/e2e/testnet-scripts/start-chain.sh | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 583a1e8009..6ed877a242 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,7 @@ FROM ghcr.io/informalsystems/cometmock:v0.37.x as cometmock-builder # Get GoRelayer FROM ghcr.io/informalsystems/relayer-no-gas-sim:v2.3.0-rc4-no-gas-sim 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/testnet-scripts/start-chain.sh b/tests/e2e/testnet-scripts/start-chain.sh index 6dbce7d524..e1e7a5c95a 100644 --- a/tests/e2e/testnet-scripts/start-chain.sh +++ b/tests/e2e/testnet-scripts/start-chain.sh @@ -367,11 +367,7 @@ NODE_HOMES=${NODE_HOMES%?} # CometMock takes the role of the query node if [[ "$USE_COMETMOCK" == "true" ]]; then sleep 2 -<<<<<<< HEAD - ip netns exec $QUERY_NET_NAMESPACE_NAME cometmock $NODE_LISTEN_ADDR_STR /$CHAIN_ID/genesis.json tcp://$CHAIN_IP_PREFIX.$QUERY_IP_SUFFIX:26658 $NODE_HOMES &> cometmock_${CHAIN_ID}_out.log & -======= ip netns exec $QUERY_NET_NAMESPACE_NAME cometmock $NODE_LISTEN_ADDR_STR /$CHAIN_ID/genesis.json tcp://$CHAIN_IP_PREFIX.$QUERY_IP_SUFFIX:26658 $NODE_HOMES grpc &> cometmock_${CHAIN_ID}_out.log & ->>>>>>> main sleep 3 fi From 8163735675bde71e57abd029f61ae9a6815e9e70 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 28 Sep 2023 15:21:28 +0200 Subject: [PATCH 29/82] make consumer misbehaviour and double signing tests pass --- tests/e2e/actions.go | 5 +++-- tests/e2e/config.go | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 551f1100c2..d1e015f755 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -172,8 +172,9 @@ func (tr *TestRun) startChain( } tr.addChainToRelayer(addChainToRelayerAction{ - Chain: action.Chain, - Validator: action.Validators[0].Id, + Chain: action.Chain, + Validator: action.Validators[0].Id, + IsConsumer: action.IsConsumer, }, verbose) // store the fact that we started the chain diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 08c2981284..89e951c400 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -396,7 +396,7 @@ func ChangeoverTestRun() TestRun { } func ConsumerMisbehaviourTestRun() TestRun { - return TestRun{ + tr := TestRun{ name: "misbehaviour", containerConfig: ContainerConfig{ ContainerName: "interchain-security-container", @@ -450,12 +450,12 @@ func ConsumerMisbehaviourTestRun() TestRun { BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, - GenesisChanges: ".app_state.gov.voting_params.voting_period = \"20s\" | " + + GenesisChanges: ".app_state.gov.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.downtime_jail_duration = \"60s\" | " + ".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\"", @@ -465,18 +465,20 @@ func ConsumerMisbehaviourTestRun() TestRun { BinaryName: "interchain-security-cd", IpPrefix: "7.7.8", VotingWaitTime: 20, - GenesisChanges: ".app_state.gov.voting_params.voting_period = \"20s\" | " + + GenesisChanges: ".app_state.gov.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.downtime_jail_duration = \"60s\" | " + ".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/;`, + `s/block_sync = true/block_sync = false/;`, } + tr.Initialize() + return tr } func (s *TestRun) SetDockerConfig(localSdkPath string, useGaia bool, gaiaTag string) { From ac9f3ff299d06d20cbceee181d09ebaa492fbf4c Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 29 Sep 2023 12:10:25 +0200 Subject: [PATCH 30/82] currently debugging democracy-reward --- app/sovereign/app.go | 5 + .../testnet-scripts/sovereign-genesis.json | 281 ------------------ 2 files changed, 5 insertions(+), 281 deletions(-) diff --git a/app/sovereign/app.go b/app/sovereign/app.go index 959fa4ed5b..c6ffb0e853 100644 --- a/app/sovereign/app.go +++ b/app/sovereign/app.go @@ -126,6 +126,10 @@ var ( ModuleBasics = module.NewBasicManager( auth.AppModuleBasic{}, genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + bank.AppModuleBasic{}, + capability.AppModuleBasic{}, + sdkstaking.AppModuleBasic{}, + mint.AppModuleBasic{}, sdkdistr.AppModuleBasic{}, sdkgov.NewAppModuleBasic( []govclient.ProposalHandler{ @@ -136,6 +140,7 @@ var ( ), params.AppModuleBasic{}, crisis.AppModuleBasic{}, + slashing.AppModuleBasic{}, feegrantmodule.AppModuleBasic{}, authzmodule.AppModuleBasic{}, ibc.AppModuleBasic{}, diff --git a/tests/e2e/testnet-scripts/sovereign-genesis.json b/tests/e2e/testnet-scripts/sovereign-genesis.json index a2691569ef..2ab953b162 100644 --- a/tests/e2e/testnet-scripts/sovereign-genesis.json +++ b/tests/e2e/testnet-scripts/sovereign-genesis.json @@ -1,284 +1,4 @@ { -<<<<<<< HEAD - "genesis_time": "2023-06-13T11:19:05.998449459Z", - "chain_id": "sover", - "initial_height": "1", - "consensus_params": { - "block": { - "max_bytes": "22020096", - "max_gas": "-1", - "time_iota_ms": "1000" - }, - "evidence": { - "max_age_num_blocks": "100000", - "max_age_duration": "172800000000000", - "max_bytes": "1048576" - }, - "validator": { - "pub_key_types": [ - "ed25519" - ] - }, - "version": {} - }, - "app_hash": "", - "app_state": { - "auth": { - "params": { - "max_memo_characters": "256", - "tx_sig_limit": "7", - "tx_size_cost_per_byte": "10", - "sig_verify_cost_ed25519": "590", - "sig_verify_cost_secp256k1": "1000" - }, - "accounts": [ - { - "@type": "/cosmos.auth.v1beta1.BaseAccount", - "address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", - "pub_key": null, - "account_number": "0", - "sequence": "0" - } - ] - }, - "authz": { - "authorization": [] - }, - "bank": { - "params": { - "send_enabled": [], - "default_send_enabled": true - }, - "balances": [ - { - "address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", - "coins": [ - { - "denom": "stake", - "amount": "10000000000" - } - ] - } - ], - "supply": [ - { - "denom": "stake", - "amount": "10000000000" - } - ], - "denom_metadata": [] - }, - "capability": { - "index": "1", - "owners": [] - }, - "crisis": { - "constant_fee": { - "denom": "stake", - "amount": "1000" - } - }, - "distribution": { - "params": { - "community_tax": "0.020000000000000000", - "base_proposer_reward": "0.010000000000000000", - "bonus_proposer_reward": "0.040000000000000000", - "withdraw_addr_enabled": true - }, - "fee_pool": { - "community_pool": [] - }, - "delegator_withdraw_infos": [], - "previous_proposer": "", - "outstanding_rewards": [], - "validator_accumulated_commissions": [], - "validator_historical_rewards": [], - "validator_current_rewards": [], - "delegator_starting_infos": [], - "validator_slash_events": [] - }, - "evidence": { - "evidence": [] - }, - "feegrant": { - "allowances": [] - }, - "genutil": { - "gen_txs": [ - { - "body": { - "messages": [ - { - "@type": "/cosmos.staking.v1beta1.MsgCreateValidator", - "description": { - "moniker": "validatoralice", - "identity": "", - "website": "", - "security_contact": "", - "details": "" - }, - "commission": { - "rate": "0.100000000000000000", - "max_rate": "0.200000000000000000", - "max_change_rate": "0.010000000000000000" - }, - "min_self_delegation": "1", - "delegator_address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", - "validator_address": "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", - "pubkey": { - "@type": "/cosmos.crypto.ed25519.PubKey", - "key": "RrclQz9bIhkIy/gfL485g3PYMeiIku4qeo495787X10=" - }, - "value": { - "denom": "stake", - "amount": "500000000" - } - } - ], - "memo": "8339e14baab81c2a2350e261962263397a8d7fb0@7.7.8.254:26656", - "timeout_height": "0", - "extension_options": [], - "non_critical_extension_options": [] - }, - "auth_info": { - "signer_infos": [ - { - "public_key": { - "@type": "/cosmos.crypto.secp256k1.PubKey", - "key": "AsFC8tmbGGQSHthsVStbsQ13/+Yza9IT8KCSXXEN7y9f" - }, - "mode_info": { - "single": { - "mode": "SIGN_MODE_DIRECT" - } - }, - "sequence": "0" - } - ], - "fee": { - "amount": [], - "gas_limit": "200000", - "payer": "", - "granter": "" - } - }, - "signatures": [ - "rZuml3RLgrrZkUoNHw90FuHF/Orxzs0uiwflCkUOcvoA4bzohisjdQhkPWCn5aRw30mqZJGj1IxgXS15XleMvQ==" - ] - } - ] - }, - "gov": { - "starting_proposal_id": "1", - "deposits": [], - "votes": [], - "proposals": [], - "deposit_params": { - "min_deposit": [ - { - "denom": "stake", - "amount": "10000000" - } - ], - "max_deposit_period": "172800s" - }, - "voting_params": { - "voting_period": "20s" - }, - "tally_params": { - "quorum": "0.334000000000000000", - "threshold": "0.500000000000000000", - "veto_threshold": "0.334000000000000000" - } - }, - "ibc": { - "client_genesis": { - "clients": [], - "clients_consensus": [], - "clients_metadata": [], - "params": { - "allowed_clients": [ - "06-solomachine", - "07-tendermint" - ] - }, - "create_localhost": false, - "next_client_sequence": "0" - }, - "connection_genesis": { - "connections": [], - "client_connection_paths": [], - "next_connection_sequence": "0", - "params": { - "max_expected_time_per_block": "30000000000" - } - }, - "channel_genesis": { - "channels": [], - "acknowledgements": [], - "commitments": [], - "receipts": [], - "send_sequences": [], - "recv_sequences": [], - "ack_sequences": [], - "next_channel_sequence": "0" - } - }, - "mint": { - "minter": { - "inflation": "0.130000000000000000", - "annual_provisions": "0.000000000000000000" - }, - "params": { - "mint_denom": "stake", - "inflation_rate_change": "0.130000000000000000", - "inflation_max": "0.200000000000000000", - "inflation_min": "0.070000000000000000", - "goal_bonded": "0.670000000000000000", - "blocks_per_year": "6311520" - } - }, - "params": null, - "slashing": { - "params": { - "signed_blocks_window": "15", - "min_signed_per_window": "0.500000000000000000", - "downtime_jail_duration": "2s", - "slash_fraction_double_sign": "0.050000000000000000", - "slash_fraction_downtime": "0.010000000000000000" - }, - "signing_infos": [], - "missed_blocks": [] - }, - "staking": { - "params": { - "unbonding_time": "1814400s", - "max_validators": 100, - "max_entries": 7, - "historical_entries": 10000, - "bond_denom": "stake" - }, - "last_total_power": "0", - "last_validator_powers": [], - "validators": [], - "delegations": [], - "unbonding_delegations": [], - "redelegations": [], - "exported": false - }, - "transfer": { - "port_id": "transfer", - "denom_traces": [], - "params": { - "send_enabled": true, - "receive_enabled": true - } - }, - "upgrade": {}, - "vesting": {} - } - } -======= "genesis_time": "2023-06-22T15:55:00.982713586Z", "chain_id": "sover", "initial_height": "1", @@ -573,4 +293,3 @@ "vesting": {} } } ->>>>>>> main From 1fbaf09134056828fe70744e7ecc8068036b69c3 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 29 Sep 2023 17:25:02 +0200 Subject: [PATCH 31/82] fix e2e democ test --- tests/e2e/actions.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index d1e015f755..8c7698b5c7 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -1747,6 +1747,7 @@ func (tr TestRun) registerRepresentative( `--commission-rate`, "0.1", `--commission-max-rate`, "0.2", `--commission-max-change-rate`, "0.01", + `--min-self-delegation`, "1", `--from`, `validator`+fmt.Sprint(val), `--chain-id`, string(tr.chainConfigs[action.Chain].ChainId), `--home`, tr.getValidatorHome(action.Chain, val), From 517cdb4757bcfc8db008c29065cfddf61271fc31 Mon Sep 17 00:00:00 2001 From: insumity Date: Fri, 29 Sep 2023 17:27:46 +0200 Subject: [PATCH 32/82] refactor: remove equivocation proposal (#1294) * remove equivocation proposal * bring back evidencekeeper * go.sum added * rebase and fix lint issues * fix mocks * clean up protos & delete unecessary file * fix E2E test * fix Dockerfile * more fixes * increase wait attempt * increase wait attempt * wait 1 block in gov proposal * fix numbers --------- Co-authored-by: Karolos Antoniadis --- Makefile | 4 - app/provider/app.go | 23 +- .../adrs/adr-013-equivocation-slashing.md | 147 +++++ docs/docs/features/proposals.md | 58 -- docs/docs/features/slashing.md | 35 +- docs/docs/introduction/overview.md | 4 - docs/docs/validators/overview.md | 2 +- .../ccv/provider/v1/provider.proto | 10 - tests/e2e/actions.go | 71 +-- tests/e2e/main.go | 2 - tests/e2e/state.go | 19 - tests/e2e/steps.go | 8 +- tests/e2e/steps_double_sign.go | 78 +-- .../e2e/steps_submit_equivocation_proposal.go | 150 ----- testutil/keeper/mocks.go | 114 ++-- testutil/keeper/unit_test_helpers.go | 3 - .../cosmos/evidence/v1beta1/evidence.proto | 21 - x/ccv/provider/client/proposal_handler.go | 126 ---- x/ccv/provider/keeper/keeper.go | 17 +- 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 - 28 files changed, 347 insertions(+), 1295 deletions(-) create mode 100644 docs/docs/adrs/adr-013-equivocation-slashing.md delete mode 100644 tests/e2e/steps_submit_equivocation_proposal.go delete mode 100644 third_party/proto/cosmos/evidence/v1beta1/evidence.proto 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..dc08e6a482 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -8,6 +8,8 @@ import ( "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" @@ -48,7 +50,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 +140,6 @@ var ( ibcclientclient.UpgradeProposalHandler, ibcproviderclient.ConsumerAdditionProposalHandler, ibcproviderclient.ConsumerRemovalProposalHandler, - ibcproviderclient.EquivocationProposalHandler, ibcproviderclient.ChangeRewardDenomsProposalHandler, ), params.AppModuleBasic{}, @@ -390,14 +390,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 +402,6 @@ func New( app.StakingKeeper, app.SlashingKeeper, app.AccountKeeper, - app.EvidenceKeeper, app.DistrKeeper, app.BankKeeper, authtypes.FeeCollectorName, @@ -459,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/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/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/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/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/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/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..321507f96d 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 @@ -526,6 +456,7 @@ func (tr TestRun) voteGovProposal( wg.Wait() time.Sleep(time.Duration(tr.chainConfigs[action.chain].votingWaitTime) * time.Second) + tr.waitBlocks(action.chain, 1, 5*time.Second) } type startConsumerChainAction struct { 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..2cf310f157 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -23,12 +23,10 @@ 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 + stepsDoubleSignOnProvider("consu"), // carol double signs on provider 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( 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, - }, - }, - }, - }, } } 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..bc3b872a2b 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 } @@ -117,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 } @@ -131,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 } @@ -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) } @@ -308,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) @@ -322,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) @@ -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/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 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..c0a5dc5ea8 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,10 +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.evidenceKeeper, "evidenceKeeper") // 12 - 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. 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 dddcd1bbb3d8842726603056472134c721b18eed Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 2 Oct 2023 10:44:14 +0200 Subject: [PATCH 33/82] add equivo removal failing tests --- app/provider/app.go | 22 +- docs/docs/features/proposals.md | 58 -- docs/docs/features/slashing.md | 31 +- docs/docs/introduction/overview.md | 4 - docs/docs/validators/overview.md | 2 +- .../ccv/provider/v1/provider.proto | 14 - tests/e2e/action_rapid_test.go | 14 - tests/e2e/actions.go | 74 --- tests/e2e/config.go | 2 +- tests/e2e/json_utils.go | 12 - tests/e2e/main.go | 2 - tests/e2e/state.go | 20 - tests/e2e/state_rapid_test.go | 13 - tests/e2e/steps.go | 14 +- tests/e2e/steps_double_sign.go | 78 +-- tests/e2e/steps_light_client_attack.go | 1 - .../e2e/steps_submit_equivocation_proposal.go | 150 ----- tests/e2e/trace_handlers_test.go | 19 - testutil/keeper/mocks.go | 102 ++-- testutil/keeper/unit_test_helpers.go | 3 - x/ccv/provider/client/proposal_handler.go | 125 +--- x/ccv/provider/keeper/keeper.go | 17 +- 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 | 26 +- x/ccv/provider/types/codec.go | 5 - x/ccv/provider/types/proposal.go | 38 -- x/ccv/provider/types/proposal_test.go | 57 -- x/ccv/provider/types/provider.pb.go | 560 +++++------------- x/ccv/types/expected_keepers.go | 5 - 31 files changed, 201 insertions(+), 1344 deletions(-) delete mode 100644 tests/e2e/steps_submit_equivocation_proposal.go diff --git a/app/provider/app.go b/app/provider/app.go index 4550ec77d2..5680f254b5 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -25,6 +25,7 @@ import ( autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" + evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" @@ -66,7 +67,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" @@ -140,7 +140,6 @@ var ( ibcclientclient.UpgradeProposalHandler, ibcproviderclient.ConsumerAdditionProposalHandler, ibcproviderclient.ConsumerRemovalProposalHandler, - ibcproviderclient.EquivocationProposalHandler, ibcproviderclient.ChangeRewardDenomsProposalHandler, }, ), @@ -404,14 +403,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], @@ -424,7 +415,6 @@ func New( app.StakingKeeper, app.SlashingKeeper, app.AccountKeeper, - app.EvidenceKeeper, app.DistrKeeper, app.BankKeeper, authtypes.FeeCollectorName, @@ -477,6 +467,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/docs/docs/features/proposals.md b/docs/docs/features/proposals.md index de4f2dc421..70077944ea 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/features/slashing.md b/docs/docs/features/slashing.md index ee01b93ce3..09ceeefd0b 100644 --- a/docs/docs/features/slashing.md +++ b/docs/docs/features/slashing.md @@ -20,33 +20,10 @@ Instead of slashing, the provider will only jail offending validator for the dur 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/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/docs/docs/validators/overview.md b/docs/docs/validators/overview.md index 35ce56cdb3..4d764703be 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/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index b0de16097b..229c4af0b3 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -12,7 +12,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"; // @@ -103,19 +102,6 @@ message ConsumerRemovalProposal { [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; } -// EquivocationProposal is a governance proposal on the provider chain to -// punish a validator for equivocation on a consumer chain. -// -// This type is only used internally to the consumer CCV module. -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/action_rapid_test.go b/tests/e2e/action_rapid_test.go index ddef903d35..2ab4bd9af1 100644 --- a/tests/e2e/action_rapid_test.go +++ b/tests/e2e/action_rapid_test.go @@ -66,7 +66,6 @@ func GetActionGen() *rapid.Generator[any] { GetSubmitConsumerAdditionProposalActionGen().AsAny(), GetSubmitConsumerRemovalProposalActionGen().AsAny(), GetSubmitParamChangeProposalActionGen().AsAny(), - GetSubmitEquivocationProposalActionGen().AsAny(), GetVoteGovProposalActionGen().AsAny(), GetStartConsumerChainActionGen().AsAny(), GetAddChainToRelayerActionGen().AsAny(), @@ -279,19 +278,6 @@ func GetSubmitParamChangeProposalActionGen() *rapid.Generator[submitParamChangeL }) } -func GetSubmitEquivocationProposalActionGen() *rapid.Generator[submitEquivocationProposalAction] { - return rapid.Custom(func(t *rapid.T) submitEquivocationProposalAction { - return submitEquivocationProposalAction{ - Chain: GetChainIDGen().Draw(t, "Chain"), - From: GetValidatorIDGen().Draw(t, "From"), - Deposit: rapid.Uint().Draw(t, "Deposit"), - Height: rapid.Int64().Draw(t, "Height"), - Time: GetTimeGen().Draw(t, "Time"), - Power: rapid.Int64().Draw(t, "Power"), - } - }) -} - func TestMarshalAndUnmarshalTime(t *testing.T) { rapid.Check(t, func(t *rapid.T) { time1 := GetTimeGen().Draw(t, "time") diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 8c7698b5c7..5c4ab5636c 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -15,8 +15,6 @@ import ( clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" "github.com/tidwall/gjson" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - "github.com/cosmos/interchain-security/v3/x/ccv/provider/client" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" @@ -432,78 +430,6 @@ func (tr TestRun) submitParamChangeProposal( tr.waitBlocks(action.Chain, 2, 60*time.Second) } -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{ - Summary: "Validator equivocation!", - 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. - // EQUIVOCATION PROPOSAL - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, providerChain.BinaryName, - - "tx", "gov", "submit-legacy-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`, - `-y`, - ).CombinedOutput() - - 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) -} - type voteGovProposalAction struct { Chain ChainID From []ValidatorID diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 89e951c400..301d2347bb 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -243,7 +243,7 @@ func DefaultTestRun() TestRun { func DemocracyTestRun(allowReward bool) TestRun { consumerGenChanges := ".app_state.ccvconsumer.params.blocks_per_distribution_transmission = \"20\" | " + - ".app_state.gov.voting_params.voting_period = \"10s\" | " + + ".app_state.gov.params.voting_period = \"10s\" | " + ".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 = \"60s\" | " + diff --git a/tests/e2e/json_utils.go b/tests/e2e/json_utils.go index 692c34a14f..37c463cdc4 100644 --- a/tests/e2e/json_utils.go +++ b/tests/e2e/json_utils.go @@ -95,12 +95,6 @@ func UnmarshalMapToActionType(rawAction json.RawMessage, actionTypeString string if err == nil { return a, nil } - case "main.submitEquivocationProposalAction": - var a submitEquivocationProposalAction - err := json.Unmarshal(rawAction, &a) - if err == nil { - return a, nil - } case "main.submitParamChangeLegacyProposalAction": var a submitParamChangeLegacyProposalAction err := json.Unmarshal(rawAction, &a) @@ -354,12 +348,6 @@ func UnmarshalProposalWithType(inputMap json.RawMessage, proposalType string) (P if err == nil { return prop, nil } - case "main.EquivocationProposal": - prop := EquivocationProposal{} - err := json.Unmarshal(inputMap, &prop) - if err == nil { - return prop, nil - } case "main.ParamsProposal": prop := ParamsProposal{} err := json.Unmarshal(inputMap, &prop) diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 56cb25a8d1..7e45dfd764 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -210,8 +210,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 submitParamChangeLegacyProposalAction: tr.submitParamChangeProposal(action, verbose) case voteGovProposalAction: diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 7e210552c7..4d0cc9c979 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -75,16 +75,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, @@ -469,16 +459,6 @@ func (tr TestRun) getProposal(chain ChainID, proposal uint) Proposal { Chain: chain, StopTime: int(stopTime.Milliseconds()), } - - case "/interchain_security.ccv.provider.v1.EquivocationProposal": - return EquivocationProposal{ - Deposit: uint(deposit), - Status: status, - Height: uint(gjson.Get(string(bz), `messages.0.content.equivocations.0.height`).Uint()), - Power: uint(gjson.Get(string(bz), `messages.0.content.equivocations.0.power`).Uint()), - ConsensusAddress: gjson.Get(string(bz), `messages.0.content.equivocations.0.consensus_address`).String(), - } - case "/cosmos.params.v1beta1.ParameterChangeProposal": return ParamsProposal{ Deposit: uint(deposit), diff --git a/tests/e2e/state_rapid_test.go b/tests/e2e/state_rapid_test.go index 6c82c2e7fb..9cf3c09894 100644 --- a/tests/e2e/state_rapid_test.go +++ b/tests/e2e/state_rapid_test.go @@ -174,7 +174,6 @@ func GetProposalGen() *rapid.Generator[Proposal] { gen := rapid.OneOf( GetConsumerAdditionProposalGen().AsAny(), GetConsumerRemovalProposalGen().AsAny(), - GetEquivocationProposalGen().AsAny(), GetTextProposalGen().AsAny(), GetParamsProposalGen().AsAny(), ) @@ -205,18 +204,6 @@ func GetConsumerRemovalProposalGen() *rapid.Generator[ConsumerRemovalProposal] { }) } -func GetEquivocationProposalGen() *rapid.Generator[EquivocationProposal] { - return rapid.Custom(func(t *rapid.T) EquivocationProposal { - return EquivocationProposal{ - Power: rapid.Uint().Draw(t, "Power"), - Height: rapid.Uint().Draw(t, "Height"), - ConsensusAddress: rapid.String().Draw(t, "ConesnsuAddress"), - Deposit: rapid.Uint().Draw(t, "Deposit"), - Status: rapid.String().Draw(t, "Status"), - } - }) -} - func GetTextProposalGen() *rapid.Generator[TextProposal] { return rapid.Custom(func(t *rapid.T) TextProposal { return TextProposal{ diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 5eb5cd734b..68417f667c 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -23,12 +23,10 @@ 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 + stepsDoubleSignOnProvider("consu"), // carol double signs on provider 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( @@ -37,9 +35,7 @@ var shortHappyPathSteps = concatSteps( stepsUnbond("consu"), stepsRedelegateShort("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 + stepsDoubleSignOnProvider("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 @@ -51,9 +47,7 @@ var lightClientAttackSteps = concatSteps( stepsUnbond("consu"), stepsRedelegateShort("consu"), stepsDowntime("consu"), - stepsRejectEquivocationProposal("consu", 2), // prop to tombstone bob is rejected stepsLightClientAttackOnProviderAndConsumer("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_double_sign.go b/tests/e2e/steps_double_sign.go index 7e3c20a022..9d6e3c73b7 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, - }, - }, - }, - }, } } diff --git a/tests/e2e/steps_light_client_attack.go b/tests/e2e/steps_light_client_attack.go index 284b3fafea..1d9f484b5c 100644 --- a/tests/e2e/steps_light_client_attack.go +++ b/tests/e2e/steps_light_client_attack.go @@ -55,7 +55,6 @@ func stepsLightClientAttackOnProviderAndConsumer(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: lightClientEquivocationAttackAction{ Chain: ChainID(consumerName), Validator: ValidatorID("bob"), diff --git a/tests/e2e/steps_submit_equivocation_proposal.go b/tests/e2e/steps_submit_equivocation_proposal.go deleted file mode 100644 index 243c01f04d..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/tests/e2e/trace_handlers_test.go b/tests/e2e/trace_handlers_test.go index 725496c51f..439b68ccad 100644 --- a/tests/e2e/trace_handlers_test.go +++ b/tests/e2e/trace_handlers_test.go @@ -159,25 +159,6 @@ func TestMarshalAndUnmarshalChainState(t *testing.T) { 10: TextProposal{}, }, }}, - "equivocation-proposal": {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{ - 5: EquivocationProposal{ - Deposit: 10000001, - Status: "PROPOSAL_STATUS_VOTING_PERIOD", - ConsensusAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - Power: 500, - Height: 10, - }, - }, - }}, } for name, tc := range tests { diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 7279a5cb91..0311873349 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -14,13 +14,12 @@ import ( types0 "github.com/cosmos/cosmos-sdk/types" types1 "github.com/cosmos/cosmos-sdk/x/auth/types" types2 "github.com/cosmos/cosmos-sdk/x/capability/types" - types3 "github.com/cosmos/cosmos-sdk/x/evidence/types" - types4 "github.com/cosmos/cosmos-sdk/x/slashing/types" - types5 "github.com/cosmos/cosmos-sdk/x/staking/types" - types6 "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" - types7 "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - types8 "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" - types9 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/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/v7/modules/apps/transfer/types" + types6 "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + types7 "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + types8 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" exported "github.com/cosmos/ibc-go/v7/modules/core/exported" gomock "github.com/golang/mock/gomock" ) @@ -63,10 +62,10 @@ func (mr *MockStakingKeeperMockRecorder) BondDenom(ctx interface{}) *gomock.Call } // Delegation mocks base method. -func (m *MockStakingKeeper) Delegation(ctx types0.Context, addr types0.AccAddress, valAddr types0.ValAddress) types5.DelegationI { +func (m *MockStakingKeeper) Delegation(ctx types0.Context, addr types0.AccAddress, valAddr types0.ValAddress) types4.DelegationI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Delegation", ctx, addr, valAddr) - ret0, _ := ret[0].(types5.DelegationI) + ret0, _ := ret[0].(types4.DelegationI) return ret0 } @@ -105,10 +104,10 @@ func (mr *MockStakingKeeperMockRecorder) GetLastValidatorPower(ctx, operator int } // GetLastValidators mocks base method. -func (m *MockStakingKeeper) GetLastValidators(ctx types0.Context) []types5.Validator { +func (m *MockStakingKeeper) GetLastValidators(ctx types0.Context) []types4.Validator { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetLastValidators", ctx) - ret0, _ := ret[0].([]types5.Validator) + ret0, _ := ret[0].([]types4.Validator) return ret0 } @@ -119,10 +118,10 @@ func (mr *MockStakingKeeperMockRecorder) GetLastValidators(ctx interface{}) *gom } // GetUnbondingType mocks base method. -func (m *MockStakingKeeper) GetUnbondingType(ctx types0.Context, id uint64) (types5.UnbondingType, bool) { +func (m *MockStakingKeeper) GetUnbondingType(ctx types0.Context, id uint64) (types4.UnbondingType, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetUnbondingType", ctx, id) - ret0, _ := ret[0].(types5.UnbondingType) + ret0, _ := ret[0].(types4.UnbondingType) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -134,10 +133,10 @@ func (mr *MockStakingKeeperMockRecorder) GetUnbondingType(ctx, id interface{}) * } // GetValidator mocks base method. -func (m *MockStakingKeeper) GetValidator(ctx types0.Context, addr types0.ValAddress) (types5.Validator, bool) { +func (m *MockStakingKeeper) GetValidator(ctx types0.Context, addr types0.ValAddress) (types4.Validator, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidator", ctx, addr) - ret0, _ := ret[0].(types5.Validator) + ret0, _ := ret[0].(types4.Validator) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -149,10 +148,10 @@ func (mr *MockStakingKeeperMockRecorder) GetValidator(ctx, addr interface{}) *go } // GetValidatorByConsAddr mocks base method. -func (m *MockStakingKeeper) GetValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) (types5.Validator, bool) { +func (m *MockStakingKeeper) GetValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) (types4.Validator, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidatorByConsAddr", ctx, consAddr) - ret0, _ := ret[0].(types5.Validator) + ret0, _ := ret[0].(types4.Validator) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -204,7 +203,7 @@ func (mr *MockStakingKeeperMockRecorder) IterateLastValidatorPowers(ctx, cb inte } // IterateValidators mocks base method. -func (m *MockStakingKeeper) IterateValidators(ctx types0.Context, f func(int64, types5.ValidatorI) bool) { +func (m *MockStakingKeeper) IterateValidators(ctx types0.Context, f func(int64, types4.ValidatorI) bool) { m.ctrl.T.Helper() m.ctrl.Call(m, "IterateValidators", ctx, f) } @@ -284,7 +283,7 @@ func (mr *MockStakingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4 inte } // SlashWithInfractionReason mocks base method. -func (m *MockStakingKeeper) SlashWithInfractionReason(arg0 types0.Context, arg1 types0.ConsAddress, arg2, arg3 int64, arg4 types0.Dec, arg5 types5.Infraction) math.Int { +func (m *MockStakingKeeper) SlashWithInfractionReason(arg0 types0.Context, arg1 types0.ConsAddress, arg2, arg3 int64, arg4 types0.Dec, arg5 types4.Infraction) math.Int { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SlashWithInfractionReason", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(math.Int) @@ -338,10 +337,10 @@ func (mr *MockStakingKeeperMockRecorder) Unjail(ctx, addr interface{}) *gomock.C } // Validator mocks base method. -func (m *MockStakingKeeper) Validator(ctx types0.Context, addr types0.ValAddress) types5.ValidatorI { +func (m *MockStakingKeeper) Validator(ctx types0.Context, addr types0.ValAddress) types4.ValidatorI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Validator", ctx, addr) - ret0, _ := ret[0].(types5.ValidatorI) + ret0, _ := ret[0].(types4.ValidatorI) return ret0 } @@ -352,10 +351,10 @@ func (mr *MockStakingKeeperMockRecorder) Validator(ctx, addr interface{}) *gomoc } // ValidatorByConsAddr mocks base method. -func (m *MockStakingKeeper) ValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) types5.ValidatorI { +func (m *MockStakingKeeper) ValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) types4.ValidatorI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ValidatorByConsAddr", ctx, consAddr) - ret0, _ := ret[0].(types5.ValidatorI) + ret0, _ := ret[0].(types4.ValidatorI) return ret0 } @@ -365,41 +364,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 types0.Context, evidence *types3.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 @@ -438,10 +402,10 @@ func (mr *MockSlashingKeeperMockRecorder) DowntimeJailDuration(arg0 interface{}) } // GetValidatorSigningInfo mocks base method. -func (m *MockSlashingKeeper) GetValidatorSigningInfo(ctx types0.Context, address types0.ConsAddress) (types4.ValidatorSigningInfo, bool) { +func (m *MockSlashingKeeper) GetValidatorSigningInfo(ctx types0.Context, address types0.ConsAddress) (types3.ValidatorSigningInfo, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidatorSigningInfo", ctx, address) - ret0, _ := ret[0].(types4.ValidatorSigningInfo) + ret0, _ := ret[0].(types3.ValidatorSigningInfo) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -556,10 +520,10 @@ func (mr *MockChannelKeeperMockRecorder) ChanCloseInit(ctx, portID, channelID, c } // GetChannel mocks base method. -func (m *MockChannelKeeper) GetChannel(ctx types0.Context, srcPort, srcChan string) (types9.Channel, bool) { +func (m *MockChannelKeeper) GetChannel(ctx types0.Context, srcPort, srcChan string) (types8.Channel, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetChannel", ctx, srcPort, srcChan) - ret0, _ := ret[0].(types9.Channel) + ret0, _ := ret[0].(types8.Channel) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -602,7 +566,7 @@ func (mr *MockChannelKeeperMockRecorder) GetNextSequenceSend(ctx, portID, channe } // SendPacket mocks base method. -func (m *MockChannelKeeper) SendPacket(ctx types0.Context, chanCap *types2.Capability, sourcePort, sourceChannel string, timeoutHeight types7.Height, timeoutTimestamp uint64, data []byte) (uint64, error) { +func (m *MockChannelKeeper) SendPacket(ctx types0.Context, chanCap *types2.Capability, sourcePort, sourceChannel string, timeoutHeight types6.Height, timeoutTimestamp uint64, data []byte) (uint64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendPacket", ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) ret0, _ := ret[0].(uint64) @@ -691,10 +655,10 @@ func (m *MockConnectionKeeper) EXPECT() *MockConnectionKeeperMockRecorder { } // GetConnection mocks base method. -func (m *MockConnectionKeeper) GetConnection(ctx types0.Context, connectionID string) (types8.ConnectionEnd, bool) { +func (m *MockConnectionKeeper) GetConnection(ctx types0.Context, connectionID string) (types7.ConnectionEnd, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetConnection", ctx, connectionID) - ret0, _ := ret[0].(types8.ConnectionEnd) + ret0, _ := ret[0].(types7.ConnectionEnd) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -1029,10 +993,10 @@ func (m *MockIBCTransferKeeper) EXPECT() *MockIBCTransferKeeperMockRecorder { } // Transfer mocks base method. -func (m *MockIBCTransferKeeper) Transfer(arg0 context.Context, arg1 *types6.MsgTransfer) (*types6.MsgTransferResponse, error) { +func (m *MockIBCTransferKeeper) Transfer(arg0 context.Context, arg1 *types5.MsgTransfer) (*types5.MsgTransferResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Transfer", arg0, arg1) - ret0, _ := ret[0].(*types6.MsgTransferResponse) + ret0, _ := ret[0].(*types5.MsgTransferResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1067,10 +1031,10 @@ func (m *MockIBCCoreKeeper) EXPECT() *MockIBCCoreKeeperMockRecorder { } // ChannelOpenInit mocks base method. -func (m *MockIBCCoreKeeper) ChannelOpenInit(goCtx context.Context, msg *types9.MsgChannelOpenInit) (*types9.MsgChannelOpenInitResponse, error) { +func (m *MockIBCCoreKeeper) ChannelOpenInit(goCtx context.Context, msg *types8.MsgChannelOpenInit) (*types8.MsgChannelOpenInitResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ChannelOpenInit", goCtx, msg) - ret0, _ := ret[0].(*types9.MsgChannelOpenInitResponse) + ret0, _ := ret[0].(*types8.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 b58d6d2471..e35b02b4f8 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 2b28c63466..0e18048bce 100644 --- a/x/ccv/provider/client/proposal_handler.go +++ b/x/ccv/provider/client/proposal_handler.go @@ -26,7 +26,6 @@ import ( var ( ConsumerAdditionProposalHandler = govclient.NewProposalHandler(SubmitConsumerAdditionPropTxCmd) ConsumerRemovalProposalHandler = govclient.NewProposalHandler(SubmitConsumerRemovalProposalTxCmd) - EquivocationProposalHandler = govclient.NewProposalHandler(SubmitEquivocationProposalTxCmd) ChangeRewardDenomsProposalHandler = govclient.NewProposalHandler(SubmitChangeRewardDenomsProposalTxCmd) ) @@ -167,69 +166,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-legacy-proposal equivocation --from= - -Where proposal.json contains: -{ - "title": "Equivoque Foo validator", - "summary": "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.Summary, proposal.Equivocations) - - from := clientCtx.GetFromAddress() - - msgContent, err := govv1.NewLegacyContent(content, authtypes.NewModuleAddress(govtypes.ModuleName).String()) - if err != nil { - return err - } - - deposit, err := sdk.ParseCoinsNormalized(proposal.Deposit) - if err != nil { - return err - } - - msg, err := govv1.NewMsgSubmitProposal([]sdk.Msg{msgContent}, deposit, from.String(), "", content.GetTitle(), proposal.Summary) - 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 { @@ -378,38 +314,6 @@ func ParseConsumerRemovalProposalJSON(proposalFile string) (ConsumerRemovalPropo return proposal, nil } -type EquivocationProposalJSON struct { - // evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - Summary string `json:"summary"` - types.EquivocationProposal - - Deposit string `json:"deposit"` -} - -type EquivocationProposalReq struct { - 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 -} - type ChangeRewardDenomsProposalJSON struct { Summary string `json:"summary"` types.ChangeRewardDenomsProposal @@ -536,33 +440,8 @@ 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 e9bb1d1bd0..428f8a0f8d 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -41,7 +41,6 @@ type Keeper struct { clientKeeper ccv.ClientKeeper stakingKeeper ccv.StakingKeeper slashingKeeper ccv.SlashingKeeper - evidenceKeeper ccv.EvidenceKeeper distributionKeeper ccv.DistributionKeeper bankKeeper ccv.BankKeeper feeCollectorName string @@ -53,8 +52,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 @@ -74,7 +73,6 @@ func NewKeeper( stakingKeeper: stakingKeeper, slashingKeeper: slashingKeeper, accountKeeper: accountKeeper, - evidenceKeeper: evidenceKeeper, distributionKeeper: distributionKeeper, bankKeeper: bankKeeper, feeCollectorName: feeCollectorName, @@ -94,8 +92,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 @@ -109,10 +107,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.evidenceKeeper, "evidenceKeeper") // 12 - 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. diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 7ea7433770..a903372056 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -605,18 +605,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 135e16eaae..e6986ebf21 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" abci "github.com/cometbft/cometbft/abci/types" @@ -1094,63 +1093,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 7af7ec4e5f..b5bd4a6597 100644 --- a/x/ccv/provider/proposal_handler.go +++ b/x/ccv/provider/proposal_handler.go @@ -12,7 +12,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) govv1beta1.Handler { return func(ctx sdk.Context, content govv1beta1.Content) error { @@ -21,8 +21,6 @@ func NewProviderProposalHandler(k keeper.Keeper) govv1beta1.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 8f1322b3d2..7e4d586097 100644 --- a/x/ccv/provider/proposal_handler_test.go +++ b/x/ccv/provider/proposal_handler_test.go @@ -10,7 +10,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" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" @@ -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( @@ -118,10 +100,6 @@ func TestProviderProposalHandler(t *testing.T) { // assert mocks for expected calls to `StopConsumerChain` when closing the underlying channel gomock.InOrder(testkeeper.GetMocksForStopConsumerChainWithCloseChannel(ctx, &mocks)...) - - case tc.expValidEquivocation: - providerKeeper.SetSlashLog(ctx, providertypes.NewProviderConsAddress(equivocation.GetConsensusAddress())) - mocks.MockEvidenceKeeper.EXPECT().HandleEquivocationEvidence(ctx, equivocation) case tc.expValidChangeRewardDenom: // Nothing to mock } @@ -131,7 +109,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 8208fd68c5..59f3d51ee6 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -27,11 +27,6 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { (*sdk.Msg)(nil), &MsgAssignConsumerKey{}, ) - registry.RegisterImplementations( - (*govv1beta1.Content)(nil), - &EquivocationProposal{}, - ) - registry.RegisterImplementations( (*sdk.Msg)(nil), &MsgSubmitConsumerMisbehaviour{}, diff --git a/x/ccv/provider/types/proposal.go b/x/ccv/provider/types/proposal.go index b2286d4adf..fb954d83aa 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" @@ -11,7 +10,6 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" @@ -20,21 +18,18 @@ import ( const ( ProposalTypeConsumerAddition = "ConsumerAddition" ProposalTypeConsumerRemoval = "ConsumerRemoval" - ProposalTypeEquivocation = "Equivocation" ProposalTypeChangeRewardDenoms = "ChangeRewardDenoms" ) var ( _ govv1beta1.Content = &ConsumerAdditionProposal{} _ govv1beta1.Content = &ConsumerRemovalProposal{} - _ govv1beta1.Content = &EquivocationProposal{} _ govv1beta1.Content = &ChangeRewardDenomsProposal{} ) func init() { govv1beta1.RegisterProposalType(ProposalTypeConsumerAddition) govv1beta1.RegisterProposalType(ProposalTypeConsumerRemoval) - govv1beta1.RegisterProposalType(ProposalTypeEquivocation) govv1beta1.RegisterProposalType(ProposalTypeChangeRewardDenoms) } @@ -203,39 +198,6 @@ func (sccp *ConsumerRemovalProposal) ValidateBasic() error { return nil } -// NewEquivocationProposal creates a new equivocation proposal. -func NewEquivocationProposal(title, description string, equivocations []*evidencetypes.Equivocation) govv1beta1.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 := govv1beta1.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, ) govv1beta1.Content { diff --git a/x/ccv/provider/types/proposal_test.go b/x/ccv/provider/types/proposal_test.go index 357c555d0e..d8f10766dd 100644 --- a/x/ccv/provider/types/proposal_test.go +++ b/x/ccv/provider/types/proposal_test.go @@ -12,7 +12,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" @@ -307,62 +306,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 govv1beta1.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 ef0aa6e5e1..3bdf4a537e 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -6,14 +6,13 @@ package types import ( fmt "fmt" crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - types2 "github.com/cosmos/cosmos-sdk/types" - types1 "github.com/cosmos/cosmos-sdk/x/evidence/types" + types1 "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" types "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" _07_tendermint "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" - types3 "github.com/cosmos/interchain-security/v3/x/ccv/types" + types2 "github.com/cosmos/interchain-security/v3/x/ccv/types" _ "google.golang.org/protobuf/types/known/durationpb" _ "google.golang.org/protobuf/types/known/timestamppb" io "io" @@ -201,73 +200,6 @@ func (m *ConsumerRemovalProposal) GetStopTime() time.Time { return time.Time{} } -// EquivocationProposal is a governance proposal on the provider chain to -// punish a validator for equivocation on a consumer chain. -// -// This type is only used internally to the consumer CCV module. -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 { @@ -285,7 +217,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) @@ -366,7 +298,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) @@ -449,14 +381,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 types2.Coin `protobuf:"bytes,9,opt,name=consumer_reward_denom_registration_fee,json=consumerRewardDenomRegistrationFee,proto3" json:"consumer_reward_denom_registration_fee"` + ConsumerRewardDenomRegistrationFee types1.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) @@ -541,11 +473,11 @@ func (m *Params) GetMaxThrottledPackets() int64 { return 0 } -func (m *Params) GetConsumerRewardDenomRegistrationFee() types2.Coin { +func (m *Params) GetConsumerRewardDenomRegistrationFee() types1.Coin { if m != nil { return m.ConsumerRewardDenomRegistrationFee } - return types2.Coin{} + return types1.Coin{} } // SlashAcks contains cons addresses of consumer chain validators @@ -558,7 +490,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{5} } func (m *SlashAcks) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -605,7 +537,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{6} } func (m *ConsumerAdditionProposals) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -652,7 +584,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{7} } func (m *ConsumerRemovalProposals) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -697,7 +629,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{8} } func (m *AddressList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -742,7 +674,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{9} } func (m *ChannelToChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -796,7 +728,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{10} } func (m *VscUnbondingOps) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -851,7 +783,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{11} } func (m *UnbondingOp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -903,7 +835,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{12} } func (m *InitTimeoutTimestamp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -955,7 +887,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{13} } func (m *VscSendTimestamp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1000,14 +932,14 @@ func (m *VscSendTimestamp) GetTimestamp() time.Time { // ValidatorSetChangePackets is a pb list of ccv.ValidatorSetChangePacketData. type ValidatorSetChangePackets struct { - List []types3.ValidatorSetChangePacketData `protobuf:"bytes,1,rep,name=list,proto3" json:"list"` + List []types2.ValidatorSetChangePacketData `protobuf:"bytes,1,rep,name=list,proto3" json:"list"` } func (m *ValidatorSetChangePackets) Reset() { *m = ValidatorSetChangePackets{} } func (m *ValidatorSetChangePackets) String() string { return proto.CompactTextString(m) } func (*ValidatorSetChangePackets) ProtoMessage() {} func (*ValidatorSetChangePackets) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{15} + return fileDescriptor_f22ec409a72b7b72, []int{14} } func (m *ValidatorSetChangePackets) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1036,7 +968,7 @@ func (m *ValidatorSetChangePackets) XXX_DiscardUnknown() { var xxx_messageInfo_ValidatorSetChangePackets proto.InternalMessageInfo -func (m *ValidatorSetChangePackets) GetList() []types3.ValidatorSetChangePacketData { +func (m *ValidatorSetChangePackets) GetList() []types2.ValidatorSetChangePacketData { if m != nil { return m.List } @@ -1053,7 +985,7 @@ func (m *MaturedUnbondingOps) Reset() { *m = MaturedUnbondingOps{} } func (m *MaturedUnbondingOps) String() string { return proto.CompactTextString(m) } func (*MaturedUnbondingOps) ProtoMessage() {} func (*MaturedUnbondingOps) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{16} + return fileDescriptor_f22ec409a72b7b72, []int{15} } func (m *MaturedUnbondingOps) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1099,7 +1031,7 @@ func (m *ExportedVscSendTimestamp) Reset() { *m = ExportedVscSendTimesta func (m *ExportedVscSendTimestamp) String() string { return proto.CompactTextString(m) } func (*ExportedVscSendTimestamp) ProtoMessage() {} func (*ExportedVscSendTimestamp) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{17} + return fileDescriptor_f22ec409a72b7b72, []int{16} } func (m *ExportedVscSendTimestamp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1152,7 +1084,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{18} + return fileDescriptor_f22ec409a72b7b72, []int{17} } func (m *KeyAssignmentReplacement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1215,7 +1147,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{19} + return fileDescriptor_f22ec409a72b7b72, []int{18} } func (m *ValidatorConsumerPubKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1278,7 +1210,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{20} + return fileDescriptor_f22ec409a72b7b72, []int{19} } func (m *ValidatorByConsumerAddr) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1340,7 +1272,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{21} + return fileDescriptor_f22ec409a72b7b72, []int{20} } func (m *ConsumerAddrsToPrune) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1393,7 +1325,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") @@ -1420,115 +1351,112 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1723 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4b, 0x73, 0x1b, 0xc7, - 0x11, 0xe6, 0x12, 0x20, 0x45, 0x34, 0xf8, 0xd2, 0x92, 0xb2, 0x40, 0x85, 0x01, 0xa9, 0x75, 0xec, - 0x30, 0xe5, 0xf2, 0xc2, 0xa4, 0x2a, 0x55, 0x2e, 0x55, 0x5c, 0x2e, 0x12, 0x94, 0x2d, 0x9a, 0xb1, - 0x45, 0x2f, 0x19, 0xaa, 0x92, 0x1c, 0xb6, 0x06, 0x33, 0x23, 0x60, 0x8a, 0xbb, 0x3b, 0xab, 0x99, - 0xc1, 0x4a, 0xb8, 0xe4, 0x9c, 0xa3, 0x73, 0x73, 0x25, 0x17, 0x27, 0x7f, 0x20, 0x7f, 0xc3, 0x47, - 0x1f, 0x73, 0xb2, 0x53, 0xd2, 0x21, 0x87, 0xfc, 0x89, 0xd4, 0xcc, 0x3e, 0x01, 0x3e, 0x02, 0x95, - 0x93, 0xdb, 0xa2, 0xa7, 0xfb, 0xeb, 0x9e, 0x7e, 0x7c, 0x3d, 0x24, 0xec, 0xb1, 0x48, 0x51, 0x81, - 0x07, 0x88, 0x45, 0xbe, 0xa4, 0x78, 0x28, 0x98, 0x1a, 0x75, 0x30, 0x4e, 0x3a, 0xb1, 0xe0, 0x09, - 0x23, 0x54, 0x74, 0x92, 0xdd, 0xe2, 0xdb, 0x8d, 0x05, 0x57, 0xdc, 0x7e, 0xfb, 0x0a, 0x1b, 0x17, - 0xe3, 0xc4, 0x2d, 0xf4, 0x92, 0xdd, 0x7b, 0x1f, 0x5c, 0x07, 0x9c, 0xec, 0x76, 0xe4, 0x00, 0x09, - 0x4a, 0x7c, 0xcc, 0x23, 0x39, 0x0c, 0x73, 0xd8, 0x7b, 0xef, 0xdc, 0x60, 0xf1, 0x82, 0x09, 0x9a, - 0xa9, 0xad, 0xf7, 0x79, 0x9f, 0x9b, 0xcf, 0x8e, 0xfe, 0xca, 0xa4, 0x5b, 0x7d, 0xce, 0xfb, 0x01, - 0xed, 0x98, 0x5f, 0xbd, 0xe1, 0xb3, 0x8e, 0x62, 0x21, 0x95, 0x0a, 0x85, 0x71, 0xa6, 0xd0, 0x9e, - 0x54, 0x20, 0x43, 0x81, 0x14, 0xe3, 0x51, 0x0e, 0xc0, 0x7a, 0xb8, 0x83, 0xb9, 0xa0, 0x1d, 0x1c, - 0x30, 0x1a, 0x29, 0xed, 0x35, 0xfd, 0xca, 0x14, 0x3a, 0x5a, 0x21, 0x60, 0xfd, 0x81, 0x4a, 0xc5, - 0xb2, 0xa3, 0x68, 0x44, 0xa8, 0x08, 0x59, 0xaa, 0x5c, 0xfe, 0xca, 0x0c, 0x36, 0x2b, 0xe7, 0x58, - 0x8c, 0x62, 0xc5, 0x3b, 0x17, 0x74, 0x24, 0xb3, 0xd3, 0x77, 0x31, 0x97, 0x21, 0x97, 0x1d, 0xaa, - 0x33, 0x16, 0x61, 0xda, 0x49, 0x76, 0x7b, 0x54, 0xa1, 0xdd, 0x42, 0x90, 0xc7, 0x9d, 0xe9, 0xf5, - 0x90, 0x2c, 0x75, 0x30, 0x67, 0x59, 0xdc, 0xce, 0x0f, 0xf3, 0xd0, 0xea, 0x66, 0x89, 0xdc, 0x27, - 0x84, 0xe9, 0x2b, 0x9d, 0x08, 0x1e, 0x73, 0x89, 0x02, 0x7b, 0x1d, 0xe6, 0x14, 0x53, 0x01, 0x6d, - 0x59, 0xdb, 0xd6, 0x4e, 0xc3, 0x4b, 0x7f, 0xd8, 0xdb, 0xd0, 0x24, 0x54, 0x62, 0xc1, 0x62, 0xad, - 0xdc, 0x9a, 0x35, 0x67, 0x55, 0x91, 0xbd, 0x01, 0x0b, 0x69, 0x1d, 0x18, 0x69, 0xd5, 0xcc, 0xf1, - 0x2d, 0xf3, 0xfb, 0x88, 0xd8, 0x9f, 0xc2, 0x32, 0x8b, 0x98, 0x62, 0x28, 0xf0, 0x07, 0x54, 0x67, - 0xa3, 0x55, 0xdf, 0xb6, 0x76, 0x9a, 0x7b, 0xf7, 0x5c, 0xd6, 0xc3, 0xae, 0x4e, 0xa0, 0x9b, 0xa5, - 0x2d, 0xd9, 0x75, 0x1f, 0x1b, 0x8d, 0x83, 0xfa, 0xb7, 0xdf, 0x6f, 0xcd, 0x78, 0x4b, 0x99, 0x5d, - 0x2a, 0xb4, 0xef, 0xc3, 0x62, 0x9f, 0x46, 0x54, 0x32, 0xe9, 0x0f, 0x90, 0x1c, 0xb4, 0xe6, 0xb6, - 0xad, 0x9d, 0x45, 0xaf, 0x99, 0xc9, 0x1e, 0x23, 0x39, 0xb0, 0xb7, 0xa0, 0xd9, 0x63, 0x11, 0x12, - 0xa3, 0x54, 0x63, 0xde, 0x68, 0x40, 0x2a, 0x32, 0x0a, 0x5d, 0x00, 0x19, 0xa3, 0x17, 0x91, 0xaf, - 0xab, 0xdd, 0xba, 0x95, 0x05, 0x92, 0x56, 0xda, 0xcd, 0x2b, 0xed, 0x9e, 0xe5, 0xad, 0x70, 0xb0, - 0xa0, 0x03, 0xf9, 0xea, 0x87, 0x2d, 0xcb, 0x6b, 0x18, 0x3b, 0x7d, 0x62, 0x7f, 0x01, 0xab, 0xc3, - 0xa8, 0xc7, 0x23, 0xc2, 0xa2, 0xbe, 0x1f, 0x53, 0xc1, 0x38, 0x69, 0x2d, 0x18, 0xa8, 0x8d, 0x4b, - 0x50, 0x87, 0x59, 0xd3, 0xa4, 0x48, 0x5f, 0x6b, 0xa4, 0x95, 0xc2, 0xf8, 0xc4, 0xd8, 0xda, 0x5f, - 0x82, 0x8d, 0x71, 0x62, 0x42, 0xe2, 0x43, 0x95, 0x23, 0x36, 0xa6, 0x47, 0x5c, 0xc5, 0x38, 0x39, - 0x4b, 0xad, 0x33, 0xc8, 0xdf, 0xc3, 0x5d, 0x25, 0x50, 0x24, 0x9f, 0x51, 0x31, 0x89, 0x0b, 0xd3, - 0xe3, 0xde, 0xc9, 0x31, 0xc6, 0xc1, 0x1f, 0xc3, 0x76, 0x3e, 0x89, 0xbe, 0xa0, 0x84, 0x49, 0x25, - 0x58, 0x6f, 0xa8, 0x6d, 0xfd, 0x67, 0x02, 0x61, 0xd3, 0x23, 0x4d, 0xd3, 0x04, 0xed, 0x5c, 0xcf, - 0x1b, 0x53, 0xfb, 0x24, 0xd3, 0xb2, 0x9f, 0xc0, 0xcf, 0x7a, 0x01, 0xc7, 0x17, 0x52, 0x07, 0xe7, - 0x8f, 0x21, 0x19, 0xd7, 0x21, 0x93, 0x52, 0xa3, 0x2d, 0x6e, 0x5b, 0x3b, 0x35, 0xef, 0x7e, 0xaa, - 0x7b, 0x42, 0xc5, 0x61, 0x45, 0xf3, 0xac, 0xa2, 0x68, 0xbf, 0x0f, 0xf6, 0x80, 0x49, 0xc5, 0x05, - 0xc3, 0x28, 0xf0, 0x69, 0xa4, 0x04, 0xa3, 0xb2, 0xb5, 0x64, 0xcc, 0x6f, 0x97, 0x27, 0x8f, 0xd2, - 0x03, 0xfb, 0x33, 0xb8, 0x7f, 0xad, 0x53, 0x1f, 0x0f, 0x50, 0x14, 0xd1, 0xa0, 0xb5, 0x6c, 0xae, - 0xb2, 0x45, 0xae, 0xf1, 0xd9, 0x4d, 0xd5, 0x1e, 0x2e, 0xfc, 0xf1, 0x9b, 0xad, 0x99, 0xaf, 0xbf, - 0xd9, 0x9a, 0x71, 0xfe, 0x6e, 0xc1, 0xdd, 0x6e, 0x71, 0xf1, 0x90, 0x27, 0x28, 0xf8, 0x7f, 0x0e, - 0xd8, 0x3e, 0x34, 0xa4, 0xe2, 0x71, 0xda, 0xd2, 0xf5, 0x37, 0x68, 0xe9, 0x05, 0x6d, 0xa6, 0x0f, - 0x9c, 0xbf, 0x58, 0xb0, 0xfe, 0xe8, 0xf9, 0x90, 0x25, 0x1c, 0xa3, 0xff, 0x09, 0x1f, 0x1c, 0xc3, - 0x12, 0xad, 0xe0, 0xc9, 0x56, 0x6d, 0xbb, 0xb6, 0xd3, 0xdc, 0x7b, 0xc7, 0x4d, 0xc9, 0xc9, 0x2d, - 0x38, 0x2b, 0x23, 0x28, 0xb7, 0xea, 0xdd, 0x1b, 0xb7, 0x75, 0xfe, 0x66, 0xc1, 0x3d, 0x9d, 0xe5, - 0x3e, 0xf5, 0xe8, 0x0b, 0x24, 0xc8, 0x21, 0x8d, 0x78, 0x28, 0x7f, 0x74, 0x8c, 0x0e, 0x2c, 0x11, - 0x83, 0xe4, 0x2b, 0xee, 0x23, 0x42, 0x4c, 0x8c, 0x46, 0x47, 0x0b, 0xcf, 0xf8, 0x3e, 0x21, 0xf6, - 0x0e, 0xac, 0x96, 0x3a, 0x42, 0xd7, 0x52, 0xa7, 0x58, 0xab, 0x2d, 0xe7, 0x6a, 0xa6, 0xc2, 0xd4, - 0xf9, 0xb7, 0x05, 0xab, 0x9f, 0x06, 0xbc, 0x87, 0x82, 0xd3, 0x00, 0xc9, 0x81, 0xee, 0xb0, 0x91, - 0x2e, 0x8d, 0xa0, 0xd9, 0x68, 0x9b, 0xf0, 0xa6, 0x2e, 0x8d, 0x36, 0x33, 0x64, 0xf3, 0x31, 0xdc, - 0x2e, 0x86, 0xad, 0xe8, 0x00, 0x73, 0x9b, 0x83, 0xb5, 0x57, 0xdf, 0x6f, 0xad, 0xe4, 0x8d, 0xd6, - 0x35, 0xdd, 0x70, 0xe8, 0xad, 0xe0, 0x31, 0x01, 0xb1, 0xdb, 0xd0, 0x64, 0x3d, 0xec, 0x4b, 0xfa, - 0xdc, 0x8f, 0x86, 0xa1, 0x69, 0x9e, 0xba, 0xd7, 0x60, 0x3d, 0x7c, 0x4a, 0x9f, 0x7f, 0x31, 0x0c, - 0xed, 0x07, 0xf0, 0x56, 0xbe, 0x86, 0xfd, 0x04, 0x05, 0x66, 0xc9, 0xea, 0x74, 0x08, 0xd3, 0x4b, - 0x8b, 0xde, 0x5a, 0x7e, 0x7a, 0x8e, 0x02, 0xed, 0x6c, 0x9f, 0x10, 0xe1, 0xfc, 0x6b, 0x0e, 0xe6, - 0x4f, 0x90, 0x40, 0xa1, 0xb4, 0xcf, 0x60, 0x45, 0xd1, 0x30, 0x0e, 0x90, 0xa2, 0x7e, 0x4a, 0xe4, - 0xd9, 0x4d, 0xdf, 0x33, 0x04, 0x5f, 0x5d, 0x80, 0x6e, 0x65, 0xe5, 0x25, 0xbb, 0x6e, 0xd7, 0x48, - 0x4f, 0x15, 0x52, 0xd4, 0x5b, 0xce, 0x31, 0x52, 0xa1, 0xfd, 0x21, 0xb4, 0x94, 0x18, 0x4a, 0x55, - 0x52, 0x6c, 0xc9, 0x2d, 0x69, 0x2d, 0xdf, 0xca, 0xcf, 0x53, 0x56, 0x2a, 0x38, 0xe5, 0x6a, 0x36, - 0xad, 0xfd, 0x18, 0x36, 0x3d, 0x85, 0x35, 0xbd, 0x8a, 0x26, 0x31, 0xeb, 0xd3, 0x63, 0xde, 0xd6, - 0xf6, 0xe3, 0xa0, 0x5f, 0x82, 0x9d, 0x48, 0x3c, 0x89, 0x39, 0xf7, 0x06, 0x71, 0x26, 0x12, 0x8f, - 0x43, 0x12, 0xd8, 0x94, 0xba, 0xf9, 0xfc, 0x90, 0x2a, 0xc3, 0xcd, 0x71, 0x40, 0x23, 0x26, 0x07, - 0x39, 0xf8, 0xfc, 0xf4, 0xe0, 0x1b, 0x06, 0xe8, 0x73, 0x8d, 0xe3, 0xe5, 0x30, 0x99, 0x97, 0x2e, - 0xb4, 0xaf, 0xf6, 0x52, 0x14, 0xe8, 0x96, 0x29, 0xd0, 0x4f, 0xae, 0x80, 0x28, 0xaa, 0xb4, 0x07, - 0x77, 0x42, 0xf4, 0xd2, 0x57, 0x03, 0xc1, 0x95, 0x0a, 0x28, 0xf1, 0x63, 0x84, 0x2f, 0xa8, 0x92, - 0x66, 0x91, 0xd6, 0xbc, 0xb5, 0x10, 0xbd, 0x3c, 0xcb, 0xcf, 0x4e, 0xd2, 0x23, 0x5b, 0xc2, 0xbb, - 0x95, 0xbd, 0xa3, 0x99, 0xc0, 0x37, 0x43, 0xe8, 0x0b, 0xda, 0xd7, 0xe4, 0x8c, 0xd2, 0x15, 0x44, - 0x69, 0xb1, 0x3b, 0x33, 0xb6, 0xd1, 0x4f, 0xa1, 0x82, 0x69, 0xba, 0x9c, 0x45, 0xd9, 0x03, 0xc3, - 0x29, 0xd7, 0x53, 0xc1, 0x2b, 0x5e, 0x05, 0xeb, 0x13, 0x4a, 0x9d, 0x5f, 0x40, 0xc3, 0x0c, 0xf4, - 0x3e, 0xbe, 0x90, 0xf6, 0x26, 0x34, 0xf4, 0x64, 0x50, 0x29, 0xa9, 0x6c, 0x59, 0x86, 0x07, 0x4a, - 0x81, 0xa3, 0x60, 0xe3, 0xba, 0x87, 0x95, 0xb4, 0x9f, 0xc2, 0xad, 0x98, 0x9a, 0xad, 0x6f, 0x0c, - 0x9b, 0x7b, 0x1f, 0xb9, 0x53, 0xbc, 0x8a, 0xdd, 0xeb, 0x00, 0xbd, 0x1c, 0xcd, 0x11, 0xe5, 0x73, - 0x6e, 0x62, 0xd9, 0x48, 0xfb, 0x7c, 0xd2, 0xe9, 0xaf, 0xde, 0xc8, 0xe9, 0x04, 0x5e, 0xe9, 0xf3, - 0x3d, 0x68, 0xee, 0xa7, 0xd7, 0xfe, 0x35, 0x93, 0xea, 0x72, 0x5a, 0x16, 0xab, 0x69, 0xf9, 0x0c, - 0x96, 0xb3, 0x1d, 0x79, 0xc6, 0x0d, 0x29, 0xd9, 0x3f, 0x05, 0xc8, 0x96, 0xab, 0x26, 0xb3, 0x94, - 0xb6, 0x1b, 0x99, 0xe4, 0x88, 0x8c, 0xed, 0xba, 0xd9, 0xb1, 0x5d, 0xe7, 0x78, 0xb0, 0x72, 0x2e, - 0xf1, 0x6f, 0xf2, 0x07, 0xd4, 0x93, 0x58, 0xda, 0x77, 0x60, 0x5e, 0xcf, 0x51, 0x06, 0x54, 0xf7, - 0xe6, 0x12, 0x89, 0x8f, 0x0c, 0x73, 0x97, 0x8f, 0x34, 0x1e, 0xfb, 0x8c, 0xc8, 0xd6, 0xec, 0x76, - 0x6d, 0xa7, 0xee, 0x2d, 0x0f, 0x4b, 0xf3, 0x23, 0x22, 0x9d, 0xdf, 0x42, 0xb3, 0x02, 0x68, 0x2f, - 0xc3, 0x6c, 0x81, 0x35, 0xcb, 0x88, 0xfd, 0x10, 0x36, 0x4a, 0xa0, 0x71, 0x2a, 0x4e, 0x11, 0x1b, - 0xde, 0xdd, 0x42, 0x61, 0x8c, 0x8d, 0xa5, 0xf3, 0x04, 0xd6, 0x8f, 0xca, 0xc1, 0x2f, 0x88, 0x7e, - 0xec, 0x86, 0xd6, 0xf8, 0x36, 0xdf, 0x84, 0x46, 0xf1, 0x97, 0x88, 0xb9, 0x7d, 0xdd, 0x2b, 0x05, - 0x4e, 0x08, 0xab, 0xe7, 0x12, 0x9f, 0xd2, 0x88, 0x94, 0x60, 0xd7, 0x24, 0xe0, 0x60, 0x12, 0x68, - 0xea, 0x97, 0x6e, 0xe9, 0x8e, 0xc3, 0xc6, 0x39, 0x0a, 0x18, 0x41, 0x8a, 0x8b, 0x53, 0xaa, 0xd2, - 0x25, 0x9c, 0x8f, 0xa3, 0x07, 0xf5, 0x80, 0x49, 0x95, 0x75, 0xd6, 0x87, 0xd7, 0x76, 0x56, 0xb2, - 0xeb, 0x5e, 0x07, 0x72, 0x88, 0x14, 0xca, 0x66, 0xd1, 0x60, 0x39, 0x3f, 0x87, 0xb5, 0xcf, 0x91, - 0x1a, 0x0a, 0x4a, 0xc6, 0x6a, 0xbc, 0x0a, 0x35, 0x5d, 0x3f, 0xcb, 0xd4, 0x4f, 0x7f, 0xea, 0x37, - 0x41, 0xeb, 0xd1, 0xcb, 0x98, 0x0b, 0x45, 0xc9, 0xa5, 0x8c, 0xdc, 0x90, 0xde, 0x0b, 0x58, 0xd3, - 0xc9, 0x92, 0x34, 0x22, 0x7e, 0x71, 0xcf, 0xb4, 0x8e, 0xcd, 0xbd, 0x5f, 0x4e, 0x35, 0x1d, 0x93, - 0xee, 0xb2, 0x0b, 0xdc, 0x4e, 0x26, 0xe4, 0xd2, 0xf9, 0x93, 0x05, 0xad, 0x63, 0x3a, 0xda, 0x97, - 0x92, 0xf5, 0xa3, 0x90, 0x46, 0x4a, 0xf3, 0x20, 0xc2, 0x54, 0x7f, 0xda, 0x6f, 0xc3, 0x52, 0xb1, - 0x77, 0xcd, 0xba, 0xb5, 0xcc, 0xba, 0x5d, 0xcc, 0x85, 0x7a, 0xc0, 0xec, 0x87, 0x00, 0xb1, 0xa0, - 0x89, 0x8f, 0xfd, 0x0b, 0x3a, 0xca, 0xaa, 0xb8, 0x59, 0x5d, 0xa3, 0xe9, 0xdf, 0x89, 0xee, 0xc9, - 0xb0, 0x17, 0x30, 0x7c, 0x4c, 0x47, 0xde, 0x82, 0xd6, 0xef, 0x1e, 0xd3, 0x91, 0x7e, 0x17, 0xc5, - 0xfc, 0x05, 0x15, 0x66, 0xf7, 0xd5, 0xbc, 0xf4, 0x87, 0xf3, 0x67, 0x0b, 0xee, 0x16, 0xe5, 0xc8, - 0xdb, 0xf5, 0x64, 0xd8, 0xd3, 0x16, 0x37, 0xe4, 0xed, 0x52, 0xb4, 0xb3, 0x57, 0x44, 0xfb, 0x31, - 0x2c, 0x16, 0x03, 0xa2, 0xe3, 0xad, 0x4d, 0x11, 0x6f, 0x33, 0xb7, 0x38, 0xa6, 0x23, 0xe7, 0x0f, - 0x95, 0xd8, 0x0e, 0x46, 0x15, 0xee, 0x13, 0xff, 0x25, 0xb6, 0xc2, 0x6d, 0x35, 0x36, 0x5c, 0xb5, - 0xbf, 0x74, 0x81, 0xda, 0xe5, 0x0b, 0x38, 0x7f, 0xb5, 0x60, 0xbd, 0xea, 0x55, 0x9e, 0xf1, 0x13, - 0x31, 0x8c, 0xe8, 0x4d, 0xde, 0xcb, 0xf1, 0x9b, 0xad, 0x8e, 0xdf, 0x53, 0x58, 0x1e, 0x0b, 0x4a, - 0x66, 0xd9, 0xf8, 0x60, 0xaa, 0x1e, 0xab, 0xb0, 0xab, 0xb7, 0x54, 0xbd, 0x87, 0x3c, 0x78, 0xfa, - 0xed, 0xab, 0xb6, 0xf5, 0xdd, 0xab, 0xb6, 0xf5, 0xcf, 0x57, 0x6d, 0xeb, 0xab, 0xd7, 0xed, 0x99, - 0xef, 0x5e, 0xb7, 0x67, 0xfe, 0xf1, 0xba, 0x3d, 0xf3, 0xbb, 0x8f, 0xfa, 0x4c, 0x0d, 0x86, 0x3d, - 0x17, 0xf3, 0xb0, 0x93, 0xfd, 0x13, 0xa0, 0xf4, 0xf5, 0x7e, 0xf1, 0x1f, 0x92, 0xe4, 0x41, 0xe7, - 0xe5, 0xf8, 0x7f, 0x6c, 0xd4, 0x28, 0xa6, 0xb2, 0x37, 0x6f, 0x58, 0xe1, 0xc1, 0x7f, 0x02, 0x00, - 0x00, 0xff, 0xff, 0x47, 0xa2, 0xef, 0x46, 0xe2, 0x11, 0x00, 0x00, + // 1668 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcb, 0x6f, 0x1b, 0xc7, + 0x19, 0xd7, 0x8a, 0x94, 0x2c, 0x7e, 0xd4, 0xcb, 0x2b, 0x3b, 0xa6, 0x5c, 0x95, 0x92, 0x37, 0x7d, + 0xa8, 0x08, 0xb2, 0x1b, 0xc9, 0x28, 0x10, 0x18, 0x0d, 0x02, 0x89, 0x4a, 0x62, 0xc5, 0x4d, 0xac, + 0xac, 0x54, 0x19, 0x6d, 0x0f, 0x8b, 0xe1, 0xcc, 0x98, 0x1c, 0x68, 0x77, 0x67, 0x33, 0x33, 0x5c, + 0x9b, 0x97, 0x9e, 0x7b, 0x4c, 0x6f, 0x41, 0x4f, 0x69, 0xff, 0x81, 0xfe, 0x1b, 0x39, 0xe6, 0xd8, + 0x53, 0x52, 0xd8, 0x87, 0x1e, 0xfa, 0x4f, 0x14, 0x33, 0xfb, 0x24, 0xf5, 0x28, 0x8d, 0xa0, 0xb7, + 0xd9, 0x6f, 0xbe, 0xef, 0xf7, 0xbd, 0xe6, 0x7b, 0x90, 0xb0, 0xcf, 0x62, 0x45, 0x05, 0x1e, 0x22, + 0x16, 0x07, 0x92, 0xe2, 0x91, 0x60, 0x6a, 0xec, 0x61, 0x9c, 0x7a, 0x89, 0xe0, 0x29, 0x23, 0x54, + 0x78, 0xe9, 0x5e, 0x79, 0x76, 0x13, 0xc1, 0x15, 0xb7, 0xdf, 0xbe, 0x42, 0xc6, 0xc5, 0x38, 0x75, + 0x4b, 0xbe, 0x74, 0xef, 0xfe, 0x7b, 0xd7, 0x01, 0xa7, 0x7b, 0x9e, 0x1c, 0x22, 0x41, 0x49, 0x80, + 0x79, 0x2c, 0x47, 0x51, 0x01, 0x7b, 0xff, 0xe7, 0x37, 0x48, 0xbc, 0x60, 0x82, 0xe6, 0x6c, 0x77, + 0x06, 0x7c, 0xc0, 0xcd, 0xd1, 0xd3, 0xa7, 0x9c, 0xba, 0x3d, 0xe0, 0x7c, 0x10, 0x52, 0xcf, 0x7c, + 0xf5, 0x47, 0xcf, 0x3d, 0xc5, 0x22, 0x2a, 0x15, 0x8a, 0x92, 0x9c, 0xa1, 0x3b, 0xcd, 0x40, 0x46, + 0x02, 0x29, 0xc6, 0xe3, 0x02, 0x80, 0xf5, 0xb1, 0x87, 0xb9, 0xa0, 0x1e, 0x0e, 0x19, 0x8d, 0x95, + 0xd6, 0x9a, 0x9d, 0x72, 0x06, 0x4f, 0x33, 0x84, 0x6c, 0x30, 0x54, 0x19, 0x59, 0x7a, 0x8a, 0xc6, + 0x84, 0x8a, 0x88, 0x65, 0xcc, 0xd5, 0x57, 0x2e, 0xb0, 0x55, 0xbb, 0xc7, 0x62, 0x9c, 0x28, 0xee, + 0x5d, 0xd0, 0xb1, 0x2c, 0xec, 0xc1, 0x5c, 0x46, 0x5c, 0x7a, 0x7d, 0x24, 0xa9, 0x97, 0xee, 0xf5, + 0xa9, 0x42, 0x7b, 0x1e, 0xe6, 0x2c, 0xb7, 0xc7, 0xf9, 0x61, 0x11, 0x3a, 0xbd, 0x3c, 0x40, 0x07, + 0x84, 0x30, 0x6d, 0xea, 0x89, 0xe0, 0x09, 0x97, 0x28, 0xb4, 0xef, 0xc0, 0x82, 0x62, 0x2a, 0xa4, + 0x1d, 0x6b, 0xc7, 0xda, 0x6d, 0xf9, 0xd9, 0x87, 0xbd, 0x03, 0x6d, 0x42, 0x25, 0x16, 0x2c, 0xd1, + 0xcc, 0x9d, 0x79, 0x73, 0x57, 0x27, 0xd9, 0x9b, 0xb0, 0x94, 0xc5, 0x97, 0x91, 0x4e, 0xc3, 0x5c, + 0xdf, 0x32, 0xdf, 0xc7, 0xc4, 0xfe, 0x04, 0x56, 0x59, 0xcc, 0x14, 0x43, 0x61, 0x30, 0xa4, 0xda, + 0xcb, 0x4e, 0x73, 0xc7, 0xda, 0x6d, 0xef, 0xdf, 0x77, 0x59, 0x1f, 0xbb, 0x3a, 0x30, 0x6e, 0x1e, + 0x8e, 0x74, 0xcf, 0x7d, 0x6c, 0x38, 0x0e, 0x9b, 0xdf, 0x7e, 0xbf, 0x3d, 0xe7, 0xaf, 0xe4, 0x72, + 0x19, 0xd1, 0x7e, 0x00, 0xcb, 0x03, 0x1a, 0x53, 0xc9, 0x64, 0x30, 0x44, 0x72, 0xd8, 0x59, 0xd8, + 0xb1, 0x76, 0x97, 0xfd, 0x76, 0x4e, 0x7b, 0x8c, 0xe4, 0xd0, 0xde, 0x86, 0x76, 0x9f, 0xc5, 0x48, + 0x8c, 0x33, 0x8e, 0x45, 0xc3, 0x01, 0x19, 0xc9, 0x30, 0xf4, 0x00, 0x64, 0x82, 0x5e, 0xc4, 0x81, + 0xce, 0x62, 0xe7, 0x56, 0x6e, 0x48, 0x96, 0x41, 0xb7, 0xc8, 0xa0, 0x7b, 0x56, 0xa4, 0xf8, 0x70, + 0x49, 0x1b, 0xf2, 0xd5, 0x0f, 0xdb, 0x96, 0xdf, 0x32, 0x72, 0xfa, 0xc6, 0xfe, 0x1c, 0xd6, 0x47, + 0x71, 0x9f, 0xc7, 0x84, 0xc5, 0x83, 0x20, 0xa1, 0x82, 0x71, 0xd2, 0x59, 0x32, 0x50, 0x9b, 0x97, + 0xa0, 0x8e, 0xf2, 0xc7, 0x90, 0x21, 0x7d, 0xad, 0x91, 0xd6, 0x4a, 0xe1, 0x13, 0x23, 0x6b, 0x7f, + 0x01, 0x36, 0xc6, 0xa9, 0x31, 0x89, 0x8f, 0x54, 0x81, 0xd8, 0x9a, 0x1d, 0x71, 0x1d, 0xe3, 0xf4, + 0x2c, 0x93, 0xce, 0x21, 0xff, 0x08, 0xf7, 0x94, 0x40, 0xb1, 0x7c, 0x4e, 0xc5, 0x34, 0x2e, 0xcc, + 0x8e, 0x7b, 0xb7, 0xc0, 0x98, 0x04, 0x7f, 0x0c, 0x3b, 0x45, 0x85, 0x05, 0x82, 0x12, 0x26, 0x95, + 0x60, 0xfd, 0x91, 0x96, 0x0d, 0x9e, 0x0b, 0x84, 0xcd, 0x1b, 0x69, 0x9b, 0x47, 0xd0, 0x2d, 0xf8, + 0xfc, 0x09, 0xb6, 0x8f, 0x73, 0x2e, 0xfb, 0x29, 0xfc, 0xac, 0x1f, 0x72, 0x7c, 0x21, 0xb5, 0x71, + 0xc1, 0x04, 0x92, 0x51, 0x1d, 0x31, 0x29, 0x35, 0xda, 0xf2, 0x8e, 0xb5, 0xdb, 0xf0, 0x1f, 0x64, + 0xbc, 0x27, 0x54, 0x1c, 0xd5, 0x38, 0xcf, 0x6a, 0x8c, 0xf6, 0xbb, 0x60, 0x0f, 0x99, 0x54, 0x5c, + 0x30, 0x8c, 0xc2, 0x80, 0xc6, 0x4a, 0x30, 0x2a, 0x3b, 0x2b, 0x46, 0xfc, 0x76, 0x75, 0xf3, 0x51, + 0x76, 0x61, 0x7f, 0x0a, 0x0f, 0xae, 0x55, 0x1a, 0xe0, 0x21, 0x8a, 0x63, 0x1a, 0x76, 0x56, 0x8d, + 0x2b, 0xdb, 0xe4, 0x1a, 0x9d, 0xbd, 0x8c, 0xed, 0xd1, 0xd2, 0x9f, 0xbf, 0xd9, 0x9e, 0xfb, 0xfa, + 0x9b, 0xed, 0x39, 0xe7, 0x1f, 0x16, 0xdc, 0xeb, 0x95, 0x8e, 0x47, 0x3c, 0x45, 0xe1, 0xff, 0xb3, + 0xc0, 0x0e, 0xa0, 0x25, 0x15, 0x4f, 0xb2, 0x27, 0xdd, 0x7c, 0x83, 0x27, 0xbd, 0xa4, 0xc5, 0xf4, + 0x85, 0xf3, 0x77, 0x0b, 0xee, 0x6b, 0x3f, 0x06, 0xd4, 0xa7, 0x2f, 0x90, 0x20, 0x47, 0x34, 0xe6, + 0x91, 0xfc, 0xd1, 0x46, 0x3b, 0xb0, 0x42, 0x0c, 0x52, 0xa0, 0x78, 0x80, 0x88, 0xb6, 0xbc, 0x91, + 0xf1, 0x68, 0xe2, 0x19, 0x3f, 0x20, 0xc4, 0xde, 0x85, 0xf5, 0x8a, 0x47, 0xe8, 0x68, 0x69, 0x27, + 0x34, 0xdb, 0x6a, 0xc1, 0x66, 0x62, 0x48, 0x9d, 0xff, 0x58, 0xb0, 0xfe, 0x49, 0xc8, 0xfb, 0x28, + 0x3c, 0x0d, 0x91, 0x1c, 0xea, 0x1c, 0x8e, 0xb5, 0xf3, 0x82, 0xe6, 0xc5, 0x63, 0xcc, 0x9b, 0xd9, + 0x79, 0x2d, 0x66, 0xca, 0xf9, 0x43, 0xb8, 0x5d, 0x3e, 0xe7, 0x32, 0xc6, 0xc6, 0x9b, 0xc3, 0x8d, + 0x57, 0xdf, 0x6f, 0xaf, 0x15, 0xa9, 0xec, 0x99, 0x78, 0x1f, 0xf9, 0x6b, 0x78, 0x82, 0x40, 0xec, + 0x2e, 0xb4, 0x59, 0x1f, 0x07, 0x92, 0x7e, 0x19, 0xc4, 0xa3, 0xc8, 0xa4, 0xa7, 0xe9, 0xb7, 0x58, + 0x1f, 0x9f, 0xd2, 0x2f, 0x3f, 0x1f, 0x45, 0xf6, 0x43, 0x78, 0xab, 0x18, 0x60, 0x41, 0x8a, 0x42, + 0x33, 0x9e, 0x74, 0x38, 0x84, 0xc9, 0xd6, 0xb2, 0xbf, 0x51, 0xdc, 0x9e, 0xa3, 0x50, 0x2b, 0x3b, + 0x20, 0x44, 0x38, 0xff, 0x5e, 0x80, 0xc5, 0x13, 0x24, 0x50, 0x24, 0xed, 0x33, 0x58, 0x53, 0x34, + 0x4a, 0x42, 0xa4, 0x68, 0x90, 0xb5, 0xca, 0xdc, 0xd3, 0x77, 0x4c, 0x0b, 0xad, 0x8f, 0x0e, 0xb7, + 0x36, 0x2c, 0xd2, 0x3d, 0xb7, 0x67, 0xa8, 0xa7, 0x0a, 0x29, 0xea, 0xaf, 0x16, 0x18, 0x19, 0xd1, + 0x7e, 0x1f, 0x3a, 0x4a, 0x8c, 0xa4, 0xaa, 0x9a, 0x58, 0x55, 0xbd, 0x59, 0x2e, 0xdf, 0x2a, 0xee, + 0xb3, 0xba, 0x2f, 0xab, 0xf6, 0xea, 0x7e, 0xd5, 0xf8, 0x31, 0xfd, 0xea, 0x14, 0x36, 0x74, 0xb3, + 0x9f, 0xc6, 0x6c, 0xce, 0x8e, 0x79, 0x5b, 0xcb, 0x4f, 0x82, 0x7e, 0x01, 0x76, 0x2a, 0xf1, 0x34, + 0xe6, 0xc2, 0x1b, 0xd8, 0x99, 0x4a, 0x3c, 0x09, 0x49, 0x60, 0x4b, 0xea, 0xc7, 0x17, 0x44, 0x54, + 0x99, 0xee, 0x97, 0x84, 0x34, 0x66, 0x72, 0x58, 0x80, 0x2f, 0xce, 0x0e, 0xbe, 0x69, 0x80, 0x3e, + 0xd3, 0x38, 0x7e, 0x01, 0x93, 0x6b, 0xe9, 0x41, 0xf7, 0x6a, 0x2d, 0x65, 0x82, 0x6e, 0x99, 0x04, + 0xfd, 0xe4, 0x0a, 0x88, 0x32, 0x4b, 0xfb, 0x70, 0x37, 0x42, 0x2f, 0x03, 0x35, 0x14, 0x5c, 0xa9, + 0x90, 0x92, 0x20, 0x41, 0xf8, 0x82, 0x2a, 0x69, 0x46, 0x55, 0xc3, 0xdf, 0x88, 0xd0, 0xcb, 0xb3, + 0xe2, 0xee, 0x24, 0xbb, 0xb2, 0x25, 0xfc, 0xa2, 0xd6, 0xd9, 0x75, 0x27, 0x08, 0x4c, 0x11, 0x06, + 0x82, 0x0e, 0x74, 0xfb, 0x43, 0x59, 0x93, 0xa7, 0xb4, 0x9c, 0x4e, 0xd9, 0xb2, 0xe1, 0xea, 0x65, + 0xc3, 0xcd, 0x97, 0x0d, 0xb7, 0xc7, 0x59, 0x9c, 0x8f, 0x70, 0xa7, 0x1a, 0x00, 0x65, 0x5f, 0xf1, + 0x6b, 0x58, 0x1f, 0x53, 0xea, 0xfc, 0x0a, 0x5a, 0xa6, 0xa0, 0x0f, 0xf0, 0x85, 0xb4, 0xb7, 0xa0, + 0xa5, 0x2b, 0x83, 0x4a, 0x49, 0x65, 0xc7, 0x32, 0x7d, 0xa0, 0x22, 0x38, 0x0a, 0x36, 0xaf, 0x5b, + 0x5d, 0xa4, 0xfd, 0x0c, 0x6e, 0x25, 0xd4, 0xcc, 0x55, 0x23, 0xd8, 0xde, 0xff, 0xc0, 0x9d, 0x61, + 0x9f, 0x74, 0xaf, 0x03, 0xf4, 0x0b, 0x34, 0x47, 0x54, 0x0b, 0xd3, 0x54, 0x3b, 0x97, 0xf6, 0xf9, + 0xb4, 0xd2, 0xdf, 0xbc, 0x91, 0xd2, 0x29, 0xbc, 0x4a, 0xe7, 0x3b, 0xd0, 0x3e, 0xc8, 0xdc, 0xfe, + 0x2d, 0x93, 0xea, 0x72, 0x58, 0x96, 0xeb, 0x61, 0xf9, 0x14, 0x56, 0xf3, 0x29, 0x74, 0xc6, 0x4d, + 0x53, 0xb2, 0x7f, 0x0a, 0x90, 0x8f, 0x2f, 0xdd, 0xcc, 0xb2, 0xb6, 0xdd, 0xca, 0x29, 0xc7, 0x64, + 0x62, 0x9a, 0xcc, 0x4f, 0x4c, 0x13, 0xc7, 0x87, 0xb5, 0x73, 0x89, 0x7f, 0x57, 0xac, 0x28, 0x4f, + 0x13, 0x69, 0xdf, 0x85, 0x45, 0x5d, 0x47, 0x39, 0x50, 0xd3, 0x5f, 0x48, 0x25, 0x3e, 0x36, 0x9d, + 0xbb, 0x5a, 0x83, 0x78, 0x12, 0x30, 0x22, 0x3b, 0xf3, 0x3b, 0x8d, 0xdd, 0xa6, 0xbf, 0x3a, 0xaa, + 0xc4, 0x8f, 0x89, 0x74, 0x7e, 0x0f, 0xed, 0x1a, 0xa0, 0xbd, 0x0a, 0xf3, 0x25, 0xd6, 0x3c, 0x23, + 0xf6, 0x23, 0xd8, 0xac, 0x80, 0x26, 0x5b, 0x71, 0x86, 0xd8, 0xf2, 0xef, 0x95, 0x0c, 0x13, 0xdd, + 0x58, 0x3a, 0x4f, 0xe1, 0xce, 0x71, 0x55, 0xf8, 0x65, 0xa3, 0x9f, 0xf0, 0xd0, 0x9a, 0x9c, 0x97, + 0x5b, 0xd0, 0x2a, 0x77, 0x78, 0xe3, 0x7d, 0xd3, 0xaf, 0x08, 0x4e, 0x04, 0xeb, 0xe7, 0x12, 0x9f, + 0xd2, 0x98, 0x54, 0x60, 0xd7, 0x04, 0xe0, 0x70, 0x1a, 0x68, 0xe6, 0x5d, 0xb2, 0x52, 0xc7, 0x61, + 0xf3, 0x1c, 0x85, 0x8c, 0x20, 0xc5, 0xc5, 0x29, 0x55, 0xd9, 0x10, 0x2e, 0xca, 0xd1, 0x87, 0x66, + 0xc8, 0xa4, 0xca, 0x5f, 0xd6, 0xfb, 0xd7, 0xbe, 0xac, 0x74, 0xcf, 0xbd, 0x0e, 0xe4, 0x08, 0x29, + 0x94, 0xd7, 0xa2, 0xc1, 0x72, 0x7e, 0x09, 0x1b, 0x9f, 0x21, 0x35, 0x12, 0x94, 0x4c, 0xe4, 0x78, + 0x1d, 0x1a, 0x3a, 0x7f, 0x96, 0xc9, 0x9f, 0x3e, 0xea, 0x9d, 0xa0, 0xf3, 0xd1, 0xcb, 0x84, 0x0b, + 0x45, 0xc9, 0xa5, 0x88, 0xdc, 0x10, 0xde, 0x0b, 0xd8, 0xd0, 0xc1, 0x92, 0x34, 0x26, 0x41, 0xe9, + 0x67, 0x96, 0xc7, 0xf6, 0xfe, 0xaf, 0x67, 0xaa, 0x8e, 0x69, 0x75, 0xb9, 0x03, 0xb7, 0xd3, 0x29, + 0xba, 0x74, 0xfe, 0x62, 0x41, 0xe7, 0x09, 0x1d, 0x1f, 0x48, 0xc9, 0x06, 0x71, 0x44, 0x63, 0xa5, + 0xfb, 0x20, 0xc2, 0x54, 0x1f, 0xed, 0xb7, 0x61, 0xa5, 0x9c, 0xbb, 0x66, 0xdc, 0x5a, 0x66, 0xdc, + 0x2e, 0x17, 0x44, 0x5d, 0x60, 0xf6, 0x23, 0x80, 0x44, 0xd0, 0x34, 0xc0, 0xc1, 0x05, 0x1d, 0xe7, + 0x59, 0xdc, 0xaa, 0x8f, 0xd1, 0xec, 0x17, 0x96, 0x7b, 0x32, 0xea, 0x87, 0x0c, 0x3f, 0xa1, 0x63, + 0x7f, 0x49, 0xf3, 0xf7, 0x9e, 0xd0, 0xb1, 0xde, 0x8b, 0x12, 0xfe, 0x82, 0x0a, 0x33, 0xfb, 0x1a, + 0x7e, 0xf6, 0xe1, 0xfc, 0xd5, 0x82, 0x7b, 0x65, 0x3a, 0x8a, 0xe7, 0x7a, 0x32, 0xea, 0x6b, 0x89, + 0x1b, 0xe2, 0x76, 0xc9, 0xda, 0xf9, 0x2b, 0xac, 0xfd, 0x10, 0x96, 0xcb, 0x02, 0xd1, 0xf6, 0x36, + 0x66, 0xb0, 0xb7, 0x5d, 0x48, 0x3c, 0xa1, 0x63, 0xe7, 0x4f, 0x35, 0xdb, 0x0e, 0xc7, 0xb5, 0xde, + 0x27, 0xfe, 0x87, 0x6d, 0xa5, 0xda, 0xba, 0x6d, 0xb8, 0x2e, 0x7f, 0xc9, 0x81, 0xc6, 0x65, 0x07, + 0x9c, 0xbf, 0x59, 0x70, 0xa7, 0xae, 0x55, 0x9e, 0xf1, 0x13, 0x31, 0x8a, 0xe9, 0x4d, 0xda, 0xab, + 0xf2, 0x9b, 0xaf, 0x97, 0xdf, 0x33, 0x58, 0x9d, 0x30, 0x4a, 0xe6, 0xd1, 0x78, 0x6f, 0xa6, 0x37, + 0x56, 0xeb, 0xae, 0xfe, 0x4a, 0xdd, 0x0f, 0x79, 0xf8, 0xec, 0xdb, 0x57, 0x5d, 0xeb, 0xbb, 0x57, + 0x5d, 0xeb, 0x5f, 0xaf, 0xba, 0xd6, 0x57, 0xaf, 0xbb, 0x73, 0xdf, 0xbd, 0xee, 0xce, 0xfd, 0xf3, + 0x75, 0x77, 0xee, 0x0f, 0x1f, 0x0c, 0x98, 0x1a, 0x8e, 0xfa, 0x2e, 0xe6, 0x91, 0x97, 0xff, 0xcc, + 0xae, 0x74, 0xbd, 0x5b, 0xfe, 0xb7, 0x90, 0x3e, 0xf4, 0x5e, 0x4e, 0xfe, 0xd7, 0xa1, 0xc6, 0x09, + 0x95, 0xfd, 0x45, 0xd3, 0x15, 0x1e, 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x67, 0x5a, 0xbb, + 0x1c, 0x11, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -1707,57 +1635,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) @@ -2684,29 +2561,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 @@ -3734,154 +3588,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 @@ -5515,7 +5221,7 @@ func (m *ValidatorSetChangePackets) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.List = append(m.List, types3.ValidatorSetChangePacketData{}) + m.List = append(m.List, types2.ValidatorSetChangePacketData{}) if err := m.List[len(m.List)-1].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 3e2ff14c75..ac11c5dce5 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -15,7 +15,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" @@ -52,10 +51,6 @@ type StakingKeeper interface { BondDenom(ctx sdk.Context) (res string) } -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 d9903a2ea4b69e22f06f338838f2a9692f426a1a Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 2 Oct 2023 12:05:51 +0200 Subject: [PATCH 34/82] fix democ e2e tests --- tests/e2e/actions.go | 8 ++++++++ tests/e2e/state.go | 9 +++++++++ tests/e2e/steps.go | 8 +++++--- tests/e2e/steps_democracy.go | 2 +- tests/e2e/steps_reward_denom.go | 2 +- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 5c4ab5636c..bcfff03483 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -1260,6 +1260,11 @@ func (tr TestRun) relayPacketsHermes( ) { // hermes clear packets ibc0 transfer channel-13 //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + fmt.Println("hermes", "clear", "packets", + "--chain", string(tr.chainConfigs[action.ChainA].ChainId), + "--port", action.Port, + "--channel", "channel-"+fmt.Sprint(action.Channel)) + cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "hermes", "clear", "packets", "--chain", string(tr.chainConfigs[action.ChainA].ChainId), "--port", action.Port, @@ -1291,6 +1296,9 @@ func (tr TestRun) relayRewardPacketsToProvider( ) { blockPerDistribution, _ := strconv.ParseUint(strings.Trim(tr.getParam(action.ConsumerChain, Param{Subspace: "ccvconsumer", Key: "BlocksPerDistributionTransmission"}), "\""), 10, 64) currentBlock := uint64(tr.getBlockHeight(action.ConsumerChain)) + fmt.Println("blockPerDistribution", blockPerDistribution) + fmt.Println("currentBlock", currentBlock) + if currentBlock <= blockPerDistribution { tr.waitBlocks(action.ConsumerChain, uint(blockPerDistribution-currentBlock+1), 60*time.Second) } diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 4d0cc9c979..61f7410283 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -319,10 +319,19 @@ func (tr TestRun) getRewards(chain ChainID, modelState Rewards) Rewards { } func (tr TestRun) getReward(chain ChainID, validator ValidatorID, blockHeight uint, isNativeDenom bool) float64 { + delAddresss := tr.validatorConfigs[validator].DelAddress if chain != ChainID("provi") && tr.validatorConfigs[validator].UseConsumerKey { delAddresss = tr.validatorConfigs[validator].ConsumerDelAddress } + + fmt.Println("getReward", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, + "query", "distribution", "rewards", + delAddresss, + + `--height`, fmt.Sprint(blockHeight), + `--node`, tr.getQueryNode(chain), + `-o`, `json`) //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 68417f667c..498516bea8 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -24,6 +24,7 @@ var happyPathSteps = concatSteps( stepsRedelegate("consu"), stepsDowntime("consu"), stepsDoubleSignOnProvider("consu"), // carol double signs on provider + // TODO: add double sign on consumer stepsStartRelayer(), stepsConsumerRemovalPropNotPassing("consu", 2), // submit removal prop but vote no on it - chain should stay stepsStopChain("consu", 3), // stop chain @@ -35,10 +36,11 @@ var shortHappyPathSteps = concatSteps( stepsUnbond("consu"), stepsRedelegateShort("consu"), stepsDowntime("consu"), - stepsDoubleSignOnProvider("consu"), // carol double signs on provider, bob double signs on consumer + stepsDoubleSignOnProvider("consu"), // carol double signs on provider + // TODO: add double sign 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 lightClientAttackSteps = concatSteps( diff --git a/tests/e2e/steps_democracy.go b/tests/e2e/steps_democracy.go index 9779ce45bc..7877f719c0 100644 --- a/tests/e2e/steps_democracy.go +++ b/tests/e2e/steps_democracy.go @@ -100,7 +100,7 @@ func stepsDemocracy(consumerName string) []Step { State: State{ ChainID(consumerName): ChainState{ ValBalances: &map[ValidatorID]uint{ - ValidatorID("alice"): 9889999998, + ValidatorID("alice"): 9899999999, ValidatorID("bob"): 9960000001, }, // Check that the parameter is changed on gov-consumer chain diff --git a/tests/e2e/steps_reward_denom.go b/tests/e2e/steps_reward_denom.go index 125f3dd401..1a2d4af6a3 100644 --- a/tests/e2e/steps_reward_denom.go +++ b/tests/e2e/steps_reward_denom.go @@ -98,7 +98,7 @@ func stepsRewardDenomConsumer(consumerName string) []Step { State: State{ ChainID(consumerName): ChainState{ ValBalances: &map[ValidatorID]uint{ - ValidatorID("alice"): 9889999998, + ValidatorID("alice"): 9899999999, ValidatorID("bob"): 9960000001, }, // Check that the parameter is changed on gov-consumer chain From 67b8c3170aad76d82b695ea8280f366442012187 Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 27 Sep 2023 15:26:51 +0200 Subject: [PATCH 35/82] make mem tests pass --- 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/integration/debug_test.go | 2 +- testutil/keeper/mocks.go | 56 +++ x/ccv/provider/keeper/double_vote.go | 8 +- 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/expected_keepers.go | 8 +- 11 files changed, 694 insertions(+), 38 deletions(-) diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index 84b504541e..560dfd069c 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 9d6e3c73b7..6d228274c3 100644 --- a/tests/e2e/steps_double_sign.go +++ b/tests/e2e/steps_double_sign.go @@ -70,6 +70,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{ @@ -81,7 +86,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), @@ -93,6 +98,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{ @@ -118,6 +130,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 457be3ebc9..679a7c664b 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 := sdk.NewDecFromInt(validator.GetTokens()) + // 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 := sdk.NewDecFromInt(val.GetTokens()) + 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 := sdk.NewDecFromInt(unb.InitialBalance) + currentBalance := sdk.NewDecFromInt(unb.Balance) + s.Require().True(initialBalance.Sub(initialBalance.Mul(slashFraction)).Equal(currentBalance)) + } + }) +} diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index f43df4b285..336a2bf215 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 := sdk.NewDecFromInt(validator.GetTokens()) + 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 := sdk.NewDecFromInt(validator.GetTokens()) + s.Require().True(initialTokens.Sub(initialTokens.Mul(slashFraction)).Equal(actualTokens)) } } diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index d06a30e1b2..a2132266e7 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -274,7 +274,7 @@ func TestCheckMisbehaviour(t *testing.T) { } // -// Equivocation test +// Consumer Equivocation test // func TestHandleConsumerDoubleVoting(t *testing.T) { diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 0311873349..b00678a50a 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -117,6 +117,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 types0.Context, valAddr types0.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 types0.Context, valAddr types0.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 types0.Context, id uint64) (types4.UnbondingType, bool) { m.ctrl.T.Helper() @@ -282,6 +310,34 @@ func (mr *MockStakingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4 inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Slash", reflect.TypeOf((*MockStakingKeeper)(nil).Slash), arg0, arg1, arg2, arg3, arg4) } +// SlashRedelegation mocks base method. +func (m *MockStakingKeeper) SlashRedelegation(arg0 types0.Context, arg1 types4.Validator, arg2 types4.Redelegation, arg3 int64, arg4 types0.Dec) math.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SlashRedelegation", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(math.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 types0.Context, arg1 types4.UnbondingDelegation, arg2 int64, arg3 types0.Dec) math.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SlashUnbondingDelegation", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(math.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) +} + // SlashWithInfractionReason mocks base method. func (m *MockStakingKeeper) SlashWithInfractionReason(arg0 types0.Context, arg1 types0.ConsAddress, arg2, arg3 int64, arg4 types0.Dec, arg5 types4.Infraction) math.Int { m.ctrl.T.Helper() diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 7e78d7bee6..0d18eed809 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -33,8 +33,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 c75b559cd7..b366a5ebc8 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + tmtypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -33,14 +35,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) } @@ -49,6 +59,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 b9ededfd00..e008bdeed6 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/v3/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.SlashWithInfractionReason(ctx, providerAddr.ToSdkConsAddr(), 0, totalPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN) + return nil } diff --git a/x/ccv/provider/keeper/punish_validator_test.go b/x/ccv/provider/keeper/punish_validator_test.go index 28c3529d51..2efc0c26e2 100644 --- a/x/ccv/provider/keeper/punish_validator_test.go +++ b/x/ccv/provider/keeper/punish_validator_test.go @@ -1,7 +1,13 @@ package keeper_test import ( + "fmt" "testing" + "time" + + "cosmossdk.io/math" + 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 +16,14 @@ import ( testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" "github.com/golang/mock/gomock" + + tmtypes "github.com/cometbft/cometbft/types" + "github.com/stretchr/testify/require" ) -// 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 +97,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 +124,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 +140,344 @@ 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()), + MinSelfDelegation: math.OneInt(), + UnbondingOnHoldRefCount: 0, + } + + 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(). + SlashWithInfractionReason(ctx, consAddr, expectedInfractionHeight, expectedSlashPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN). + 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/expected_keepers.go b/x/ccv/types/expected_keepers.go index ac11c5dce5..2c561ab0c9 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -16,6 +16,7 @@ import ( auth "github.com/cosmos/cosmos-sdk/x/auth/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" abci "github.com/cometbft/cometbft/abci/types" @@ -30,10 +31,11 @@ type StakingKeeper interface { UnbondingTime(ctx sdk.Context) time.Duration GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator stakingtypes.Validator, found bool) GetLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress) (power int64) - // 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) math.Int SlashWithInfractionReason(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec, stakingtypes.Infraction) math.Int + SlashUnbondingDelegation(sdk.Context, types.UnbondingDelegation, int64, sdk.Dec) math.Int + SlashRedelegation(sdk.Context, types.Validator, types.Redelegation, int64, sdk.Dec) math.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)) @@ -47,8 +49,10 @@ type StakingKeeper interface { MaxValidators(ctx sdk.Context) uint32 GetLastTotalPower(ctx sdk.Context) math.Int GetLastValidators(ctx sdk.Context) (validators []stakingtypes.Validator) - GetUnbondingType(ctx sdk.Context, id uint64) (unbondingType stakingtypes.UnbondingType, found bool) 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) } // SlashingKeeper defines the contract expected to perform ccv slashing From 1fd84094dbb31667220acfed1811c7d7a9c260f2 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 3 Oct 2023 14:55:22 +0200 Subject: [PATCH 36/82] lint --- app/provider/app.go | 2 +- app/sovereign/app.go | 1 - tests/e2e/actions.go | 5 ----- tests/e2e/state.go | 1 - tests/integration/double_vote.go | 6 +++-- tests/integration/misbehaviour.go | 6 +++-- x/ccv/provider/client/cli/tx.go | 6 ++--- x/ccv/provider/keeper/double_vote.go | 12 +++++----- x/ccv/provider/keeper/double_vote_test.go | 7 ++++-- x/ccv/provider/keeper/misbehaviour.go | 16 +++++++++----- x/ccv/provider/keeper/msg_server.go | 5 ++--- x/ccv/provider/keeper/punish_validator.go | 4 +++- .../provider/keeper/punish_validator_test.go | 22 ++++++++++--------- x/ccv/provider/types/msg.go | 12 ++++++---- x/ccv/types/expected_keepers.go | 5 ++--- 15 files changed, 61 insertions(+), 49 deletions(-) diff --git a/app/provider/app.go b/app/provider/app.go index 5680f254b5..497158449a 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -25,7 +25,6 @@ import ( autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" - evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" @@ -67,6 +66,7 @@ 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" diff --git a/app/sovereign/app.go b/app/sovereign/app.go index c6ffb0e853..b5629bfc22 100644 --- a/app/sovereign/app.go +++ b/app/sovereign/app.go @@ -79,7 +79,6 @@ import ( govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - // add mint mint "github.com/cosmos/cosmos-sdk/x/mint" mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index bcfff03483..52abaa37a1 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -1260,11 +1260,6 @@ func (tr TestRun) relayPacketsHermes( ) { // hermes clear packets ibc0 transfer channel-13 //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - fmt.Println("hermes", "clear", "packets", - "--chain", string(tr.chainConfigs[action.ChainA].ChainId), - "--port", action.Port, - "--channel", "channel-"+fmt.Sprint(action.Channel)) - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "hermes", "clear", "packets", "--chain", string(tr.chainConfigs[action.ChainA].ChainId), "--port", action.Port, diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 61f7410283..e59c91b7d2 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -319,7 +319,6 @@ func (tr TestRun) getRewards(chain ChainID, modelState Rewards) Rewards { } func (tr TestRun) getReward(chain ChainID, validator ValidatorID, blockHeight uint, isNativeDenom bool) float64 { - delAddresss := tr.validatorConfigs[validator].DelAddress if chain != ChainID("provi") && tr.validatorConfigs[validator].UseConsumerKey { delAddresss = tr.validatorConfigs[validator].ConsumerDelAddress diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index 679a7c664b..c5309b17fb 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -1,10 +1,12 @@ package integration import ( - tmcrypto "github.com/cometbft/cometbft/crypto" - tmtypes "github.com/cometbft/cometbft/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" + + tmcrypto "github.com/cometbft/cometbft/crypto" + tmtypes "github.com/cometbft/cometbft/types" + testutil "github.com/cosmos/interchain-security/v3/testutil/crypto" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 336a2bf215..f531f79178 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -3,11 +3,13 @@ package integration import ( "time" + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" tmtypes "github.com/cometbft/cometbft/types" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + + "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ) // TestHandleConsumerMisbehaviour tests that handling a valid misbehaviour, diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 4d97cd6d4f..994bdd0c10 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -4,16 +4,16 @@ import ( "fmt" "strings" + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" "github.com/spf13/cobra" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" - sdk "github.com/cosmos/cosmos-sdk/types" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ) diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 0d18eed809..3b459bb8c1 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -4,11 +4,13 @@ import ( "bytes" "fmt" + errorsmod "cosmossdk.io/errors" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" tmtypes "github.com/cometbft/cometbft/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" ) @@ -67,7 +69,7 @@ func (k Keeper) VerifyDoubleVotingEvidence( if evidence.VoteA.Height != evidence.VoteB.Height || evidence.VoteA.Round != evidence.VoteB.Round || evidence.VoteA.Type != evidence.VoteB.Type { - return sdkerrors.Wrapf( + return errorsmod.Wrapf( ccvtypes.ErrInvalidDoubleVotingEvidence, "h/r/s does not match: %d/%d/%v vs %d/%d/%v", evidence.VoteA.Height, evidence.VoteA.Round, evidence.VoteA.Type, @@ -76,7 +78,7 @@ func (k Keeper) VerifyDoubleVotingEvidence( // Addresses must be the same if !bytes.Equal(evidence.VoteA.ValidatorAddress, evidence.VoteB.ValidatorAddress) { - return sdkerrors.Wrapf( + return errorsmod.Wrapf( ccvtypes.ErrInvalidDoubleVotingEvidence, "validator addresses do not match: %X vs %X", evidence.VoteA.ValidatorAddress, @@ -86,7 +88,7 @@ func (k Keeper) VerifyDoubleVotingEvidence( // BlockIDs must be different if evidence.VoteA.BlockID.Equals(evidence.VoteB.BlockID) { - return sdkerrors.Wrapf( + return errorsmod.Wrapf( ccvtypes.ErrInvalidDoubleVotingEvidence, "block IDs are the same (%v) - not a real duplicate vote", evidence.VoteA.BlockID, diff --git a/x/ccv/provider/keeper/double_vote_test.go b/x/ccv/provider/keeper/double_vote_test.go index 175607b2b0..d3f8e86e45 100644 --- a/x/ccv/provider/keeper/double_vote_test.go +++ b/x/ccv/provider/keeper/double_vote_test.go @@ -4,12 +4,15 @@ import ( "testing" "time" - tmtypes "github.com/cometbft/cometbft/types" + "github.com/stretchr/testify/require" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + + tmtypes "github.com/cometbft/cometbft/types" + testutil "github.com/cosmos/interchain-security/v3/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" - "github.com/stretchr/testify/require" ) func TestVerifyDoubleVotingEvidence(t *testing.T) { diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index b366a5ebc8..fd7ec6bf0d 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -3,11 +3,15 @@ package keeper import ( "fmt" - tmtypes "github.com/cometbft/cometbft/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + tmtypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ) @@ -137,7 +141,7 @@ func headerToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { clientState, found := k.clientKeeper.GetClientState(ctx, misbehaviour.ClientId) if !found { - return sdkerrors.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot check misbehaviour for client with ID %s", misbehaviour.ClientId) + return errorsmod.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot check misbehaviour for client with ID %s", misbehaviour.ClientId) } clientStore := k.clientKeeper.ClientStore(ctx, misbehaviour.ClientId) @@ -146,7 +150,7 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe // the misbehaviour is for a light client attack and not a time violation, // 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") + return errorsmod.Wrap(ibcclienttypes.ErrInvalidMisbehaviour, "headers are not at same height") } // CheckMisbehaviourAndUpdateState verifies the misbehaviour against the trusted consensus states @@ -155,7 +159,7 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe // see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go ok := clientState.CheckForMisbehaviour(ctx, k.cdc, clientStore, &misbehaviour) if !ok { - return sdkerrors.Wrapf(ibcclienttypes.ErrInvalidMisbehaviour, "invalid misbehaviour for client-id: %s", misbehaviour.ClientId) + return errorsmod.Wrapf(ibcclienttypes.ErrInvalidMisbehaviour, "invalid misbehaviour for client-id: %s", misbehaviour.ClientId) } return nil diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index 08d4c708d2..cc08c7f32f 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -4,15 +4,14 @@ import ( "context" "encoding/base64" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - errorsmod "cosmossdk.io/errors" - tmtypes "github.com/cometbft/cometbft/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go index e008bdeed6..29664841c0 100644 --- a/x/ccv/provider/keeper/punish_validator.go +++ b/x/ccv/provider/keeper/punish_validator.go @@ -4,11 +4,13 @@ import ( "fmt" errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" 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/v3/x/ccv/provider/types" ) @@ -50,7 +52,7 @@ func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.Pr // `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, + redelegations []stakingtypes.Redelegation, power int64, powerReduction math.Int, ) int64 { // compute the total numbers of tokens currently being undelegated undelegationsInTokens := sdk.NewInt(0) diff --git a/x/ccv/provider/keeper/punish_validator_test.go b/x/ccv/provider/keeper/punish_validator_test.go index 2efc0c26e2..cce8749710 100644 --- a/x/ccv/provider/keeper/punish_validator_test.go +++ b/x/ccv/provider/keeper/punish_validator_test.go @@ -5,20 +5,22 @@ import ( "testing" "time" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "cosmossdk.io/math" + 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" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + tmtypes "github.com/cometbft/cometbft/types" + cryptotestutil "github.com/cosmos/interchain-security/v3/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" - "github.com/golang/mock/gomock" - - tmtypes "github.com/cometbft/cometbft/types" - "github.com/stretchr/testify/require" ) // TestJailAndTombstoneValidator tests that the jailing of a validator is only executed @@ -190,7 +192,7 @@ func TestComputePowerToSlash(t *testing.T) { undelegations []stakingtypes.UnbondingDelegation redelegations []stakingtypes.Redelegation power int64 - powerReduction sdk.Int + powerReduction math.Int expectedPower int64 }{ { @@ -305,7 +307,7 @@ func TestComputePowerToSlash(t *testing.T) { 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 { + func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) math.Int { sum := sdk.NewInt(0) for _, r := range undelegation.Entries { if r.IsMature(ctx.BlockTime()) { @@ -318,7 +320,7 @@ func TestComputePowerToSlash(t *testing.T) { 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 { + func(ctx sdk.Context, _ stakingtypes.Validator, redelegation stakingtypes.Redelegation, _ int64, _ sdk.Dec) math.Int { sum := sdk.NewInt(0) for _, r := range redelegation.Entries { if r.IsMature(ctx.BlockTime()) { @@ -420,7 +422,7 @@ func TestSlashValidator(t *testing.T) { mocks.MockStakingKeeper.EXPECT(). SlashUnbondingDelegation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn( - func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) sdk.Int { + func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) math.Int { sum := sdk.NewInt(0) for _, r := range undelegation.Entries { if r.IsMature(ctx.BlockTime()) { @@ -433,7 +435,7 @@ func TestSlashValidator(t *testing.T) { 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 { + func(_ sdk.Context, _ stakingtypes.Validator, redelegation stakingtypes.Redelegation, _ int64, _ sdk.Dec) math.Int { sum := sdk.NewInt(0) for _, r := range redelegation.Entries { if r.IsMature(ctx.BlockTime()) { diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 426c6b316f..c2551f9ceb 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -5,11 +5,15 @@ import ( "fmt" "strings" - tmtypes "github.com/cometbft/cometbft/proto/tendermint/types" + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + tmtypes "github.com/cometbft/cometbft/proto/tendermint/types" + ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" ) @@ -121,7 +125,7 @@ func (msg MsgSubmitConsumerMisbehaviour) Type() string { // Type implements the sdk.Msg interface. func (msg MsgSubmitConsumerMisbehaviour) ValidateBasic() error { if msg.Submitter == "" { - return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Submitter) + return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, msg.Submitter) } if err := msg.Misbehaviour.ValidateBasic(); err != nil { @@ -161,7 +165,7 @@ func (msg MsgSubmitConsumerDoubleVoting) Type() string { // Type implements the sdk.Msg interface. func (msg MsgSubmitConsumerDoubleVoting) ValidateBasic() error { if msg.Submitter == "" { - return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Submitter) + return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, msg.Submitter) } if msg.DuplicateVoteEvidence == nil { return fmt.Errorf("double voting evidence cannot be nil") diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 2c561ab0c9..5b2f3f7a53 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -16,7 +16,6 @@ import ( auth "github.com/cosmos/cosmos-sdk/x/auth/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" - "github.com/cosmos/cosmos-sdk/x/staking/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" abci "github.com/cometbft/cometbft/abci/types" @@ -34,8 +33,8 @@ type StakingKeeper interface { Jail(sdk.Context, sdk.ConsAddress) // jail a validator Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec) math.Int SlashWithInfractionReason(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec, stakingtypes.Infraction) math.Int - SlashUnbondingDelegation(sdk.Context, types.UnbondingDelegation, int64, sdk.Dec) math.Int - SlashRedelegation(sdk.Context, types.Validator, types.Redelegation, int64, sdk.Dec) math.Int + SlashUnbondingDelegation(sdk.Context, stakingtypes.UnbondingDelegation, int64, sdk.Dec) math.Int + SlashRedelegation(sdk.Context, stakingtypes.Validator, stakingtypes.Redelegation, int64, sdk.Dec) math.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)) From e27b7b7184335ce95985a61199c2d75534547909 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 3 Oct 2023 15:27:28 +0200 Subject: [PATCH 37/82] fix Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6ed877a242..583a1e8009 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,7 @@ FROM ghcr.io/informalsystems/cometmock:v0.37.x as cometmock-builder # Get GoRelayer FROM ghcr.io/informalsystems/relayer-no-gas-sim:v2.3.0-rc4-no-gas-sim 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 e7a8e7b0a43240cfaa3242ef94111435fa702029 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 3 Oct 2023 15:36:47 +0200 Subject: [PATCH 38/82] update changelog --- CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd87d51491..a6e4ec6928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Add an entry to the unreleased provider section whenever merging a PR to main th * (deps!) [#1196](https://github.com/cosmos/interchain-security/pull/1196) Bump [ibc-go](https://github.com/cosmos/ibc-go) to [v7.2.0](https://github.com/cosmos/ibc-go/releases/tag/v7.2.0). * `[x/ccv/provider]` (fix) [#1076](https://github.com/cosmos/interchain-security/pull/1076) Add `InitTimeoutTimestamps` and `ExportedVscSendTimestamps` to exported genesis. +### 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). ## [Unreleased for Consumer] @@ -94,9 +96,6 @@ Upgrading a provider from `v1.1.0-multiden` to `v2.0.0` will require state migra Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state migrations. See the consumer module's `ConsensusVersion` in [module](./x/ccv/consumer/module.go) -### 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 From 8349b828121701a76c2fab6843107de22aaa36ef Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 3 Oct 2023 16:27:09 +0200 Subject: [PATCH 39/82] fix nits --- CHANGELOG.md | 58 +++++++++++++++++++ tests/e2e/actions.go | 3 - tests/e2e/actions_consumer_misbehaviour.go | 21 ------- tests/e2e/main.go | 2 - tests/e2e/state.go | 9 +-- .../testnet-scripts/sovereign-genesis.json | 2 +- tests/e2e/testnet-scripts/start-chain.sh | 1 - .../proto/tendermint/types/evidence.proto | 38 ------------ 8 files changed, 60 insertions(+), 74 deletions(-) delete mode 100644 third_party/proto/tendermint/types/evidence.proto diff --git a/CHANGELOG.md b/CHANGELOG.md index a6e4ec6928..4716179956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -156,6 +156,64 @@ Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state m * (api) Add consumer QueryParams [#746](https://github.com/cosmos/interchain-security/pull/746) * (feature) New validation for keeper fields [#740](https://github.com/cosmos/interchain-security/pull/740) + +## v1.2.0-multiden + +The first release candidate for a fix built on top of v1.2.0, intended for consumers. This release adds a list of denoms on the consumer that are allowed to be sent to the provider as rewards. This prevents a potential DOS attack that was discovered during the audit of Replicated Security performed by Oak Security and funded by the Cosmos Hub community through Proposal 687. In an effort to move quickly, this release also includes a multisig fix that is effective only for provider. It shouldn't affect the consumer module. + +Note PRs were made in a private security repo. + +[full diff](https://github.com/cosmos/interchain-security/compare/v1.2.0...v1.2.0-multiden-rc0) + +## v1.1.0-multiden + +This release combines two fixes on top of v1.1.0, that we judged were urgent to get onto the Cosmos Hub before the launch of the first ICS consumer chain. This is an emergency release intended for providers. + +The first fix is to enable the use of multisigs and Ledger devices when assigning keys for consumer chains. The second is to prevent a possible DOS vector involving the reward distribution system. + +Note PRs were made in a private security repo. + +[full diff](https://github.com/cosmos/interchain-security/compare/v1.1.0...release/v1.1.0-multiden) + +### Multisig fix + +On April 25th (a week and a half ago), we began receiving reports that validators using multisigs and Ledger devices were getting errors reading Error: unable to resolve type URL /interchain_security.ccv.provider.v1.MsgAssignConsumerKey: tx parse error when attempting to assign consensus keys for consumer chains. + +We quickly narrowed the problem down to issues having to do with using the PubKey type directly in the MsgAssignConsumerKey transaction, and Amino (a deprecated serialization library still used in Ledger devices and multisigs) not being able to handle this. We attempted to fix this with the assistance of the Cosmos-SDK team, but after making no headway for a few days, we decided to simply use a JSON representation of the PubKey in the transaction. This is how it is usually represented anyway. We have verified that this fixes the problem. + +### Distribution fix + +The ICS distribution system works by allowing consumer chains to send rewards to a module address on the provider called the FeePoolAddress. From here they are automatically distributed to all validators and delegators through the distribution system that already exists to distribute staking rewards. The FeePoolAddress is usually blocked so that no tokens can be sent to it, but to enable ICS distribution we had to unblock it. + +We recently realized that unblocking the FeePoolAddress could enable an attacker to send a huge number of different denoms into the distribution system. The distribution system would then attempt to distribute them all, leading to out of memory errors. Fixing a similar attack vector that existed in the distribution system before ICS led us to this realization. + +To fix this problem, we have re-blocked the FeePoolAddress and created a new address called the ConsumerRewardsPool. Consumer chains now send rewards to this new address. There is also a new transaction type called RegisterConsumerRewardDenom. This transaction allows people to register denoms to be used as rewards from consumer chains. It costs 10 Atoms to run this transaction.The Atoms are transferred to the community pool. Only denoms registered with this command are then transferred to the FeePoolAddress and distributed out to delegators and validators. + +## v1.2.1 + +* (fix) Remove SPM [#812](https://github.com/cosmos/interchain-security/pull/812) +* (refactor) Key assignment type safety [#725](https://github.com/cosmos/interchain-security/pull/725) + +## v1.2.0 + +Date: April 13th, 2023 + +* (feat) Soft opt-out [#833](https://github.com/cosmos/interchain-security/pull/833) +* (fix) Correctly handle VSC packet with duplicate val updates on consumer [#846](https://github.com/cosmos/interchain-security/pull/846) +* (chore) bump: sdk v0.45.15-ics [#805](https://github.com/cosmos/interchain-security/pull/805) +* (api) add interchain security consumer QueryParams [#746](https://github.com/cosmos/interchain-security/pull/746) + +## v1.1.1 + +* (fix) Remove SPM [#812](https://github.com/cosmos/interchain-security/pull/812) +* (refactor) Key assignment type safety [#725](https://github.com/cosmos/interchain-security/pull/725) + +## v1.1.0 + +Date: March 24th, 2023 + +* (fix) StopConsumerChain not running in cachedContext [#802](https://github.com/cosmos/interchain-security/pull/802) + ## v1.0.0 Date: February 6th, 2023 diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index c1f11bb62d..9dac11a608 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -1291,9 +1291,6 @@ func (tr TestConfig) relayRewardPacketsToProvider( ) { blockPerDistribution, _ := strconv.ParseUint(strings.Trim(tr.getParam(action.ConsumerChain, Param{Subspace: "ccvconsumer", Key: "BlocksPerDistributionTransmission"}), "\""), 10, 64) currentBlock := uint64(tr.getBlockHeight(action.ConsumerChain)) - fmt.Println("blockPerDistribution", blockPerDistribution) - fmt.Println("currentBlock", currentBlock) - if currentBlock <= blockPerDistribution { tr.waitBlocks(action.ConsumerChain, uint(blockPerDistribution-currentBlock+1), 60*time.Second) } diff --git a/tests/e2e/actions_consumer_misbehaviour.go b/tests/e2e/actions_consumer_misbehaviour.go index 46e349b356..7d71d58d1d 100644 --- a/tests/e2e/actions_consumer_misbehaviour.go +++ b/tests/e2e/actions_consumer_misbehaviour.go @@ -2,7 +2,6 @@ package main import ( "bufio" - "fmt" "log" "os/exec" "strconv" @@ -97,23 +96,3 @@ func (tc TestConfig) updateLightClient( tc.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 (tc TestConfig) assertChainIsHalted( - action assertChainIsHaltedAction, - verbose bool, -) { - blockHeight := tc.getBlockHeight(action.chain) - time.Sleep(20 * time.Second) - if blockHeight != tc.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 17b33545b5..fb73b6ee8f 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -393,8 +393,6 @@ func (tr *TestConfig) runStep(step Step, verbose bool) { tr.forkConsumerChain(action, verbose) case updateLightClientAction: tr.updateLightClient(action, verbose) - case assertChainIsHaltedAction: - tr.assertChainIsHalted(action, verbose) case startConsumerEvidenceDetectorAction: tr.startConsumerEvidenceDetector(action, verbose) case submitChangeRewardDenomsProposalAction: diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 11cc8cc03e..a2a4bdbeac 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -26,7 +26,7 @@ type ChainState struct { Rewards *Rewards ConsumerChains *map[ChainID]bool AssignedKeys *map[ValidatorID]string - ProviderKeys *map[ValidatorID]string // ValidatorID: validator provider key + ProviderKeys *map[ValidatorID]string // validatorID: validator provider key ConsumerChainQueueSizes *map[ChainID]uint GlobalSlashQueueSize *uint RegisteredConsumerRewardDenoms *[]string @@ -324,13 +324,6 @@ func (tr TestConfig) getReward(chain ChainID, validator ValidatorID, blockHeight delAddresss = tr.validatorConfigs[validator].ConsumerDelAddress } - fmt.Println("getReward", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, - "query", "distribution", "rewards", - delAddresss, - - `--height`, fmt.Sprint(blockHeight), - `--node`, tr.getQueryNode(chain), - `-o`, `json`) //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, diff --git a/tests/e2e/testnet-scripts/sovereign-genesis.json b/tests/e2e/testnet-scripts/sovereign-genesis.json index 2ab953b162..c3ae9da36c 100644 --- a/tests/e2e/testnet-scripts/sovereign-genesis.json +++ b/tests/e2e/testnet-scripts/sovereign-genesis.json @@ -292,4 +292,4 @@ "upgrade": {}, "vesting": {} } -} +} \ 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 e1e7a5c95a..e3e6b6054f 100644 --- a/tests/e2e/testnet-scripts/start-chain.sh +++ b/tests/e2e/testnet-scripts/start-chain.sh @@ -198,7 +198,6 @@ do #'s/foo/bar/;s/abc/def/' sed -i "$TENDERMINT_CONFIG_TRANSFORM" $CHAIN_ID/validator$VAL_ID/config/config.toml fi - done diff --git a/third_party/proto/tendermint/types/evidence.proto b/third_party/proto/tendermint/types/evidence.proto deleted file mode 100644 index 451b8dca3c..0000000000 --- a/third_party/proto/tendermint/types/evidence.proto +++ /dev/null @@ -1,38 +0,0 @@ -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]; -} From ea545b490b53b8e6f422492b4fb2820ab0bc140b Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 4 Oct 2023 09:32:07 +0200 Subject: [PATCH 40/82] update rapid test --- tests/e2e/action_rapid_test.go | 33 ++ tests/e2e/config.go | 2 +- tests/e2e/json_utils.go | 18 + tests/e2e/main.go | 3 +- tests/e2e/trace_handlers_test.go | 4 +- .../e2e/tracehandler_testdata/changeover.json | 19 +- .../consumer-double-sign.json | 465 ++++++++++++++++++ .../consumer-misbehaviour.json | 437 ++++++++++++++++ .../e2e/tracehandler_testdata/democracy.json | 33 +- .../democracyRewardsSteps.json | 32 +- .../e2e/tracehandler_testdata/happyPath.json | 448 +++-------------- .../multipleConsumers.json | 99 +++- .../e2e/tracehandler_testdata/shorthappy.json | 431 ++-------------- .../tracehandler_testdata/slashThrottle.json | 28 +- 14 files changed, 1273 insertions(+), 779 deletions(-) create mode 100644 tests/e2e/tracehandler_testdata/consumer-double-sign.json create mode 100644 tests/e2e/tracehandler_testdata/consumer-misbehaviour.json diff --git a/tests/e2e/action_rapid_test.go b/tests/e2e/action_rapid_test.go index 2ab4bd9af1..49773ef883 100644 --- a/tests/e2e/action_rapid_test.go +++ b/tests/e2e/action_rapid_test.go @@ -89,6 +89,9 @@ func GetActionGen() *rapid.Generator[any] { CreateLightClientEquivocationAttackActionGen().AsAny(), CreateLightClientAmnesiaAttackActionGen().AsAny(), CreateLightClientLunaticAttackActionGen().AsAny(), + GetStartConsumerEvidenceDetectorActionGen().AsAny(), + GetForkConsumerChainActionGen().AsAny(), + GetUpdateLightClientActionGen().AsAny(), ) } @@ -485,3 +488,33 @@ func GetSlashThrottleDequeueActionGen() *rapid.Generator[slashThrottleDequeueAct } }) } + +func GetForkConsumerChainActionGen() *rapid.Generator[forkConsumerChainAction] { + return rapid.Custom(func(t *rapid.T) forkConsumerChainAction { + return forkConsumerChainAction{ + ConsumerChain: GetChainIDGen().Draw(t, "ConsumerChain"), + ProviderChain: GetChainIDGen().Draw(t, "ProviderChain"), + Validator: GetValidatorIDGen().Draw(t, "Validator"), + RelayerConfig: rapid.String().Draw(t, "RelayerConfig"), + } + }) +} + +func GetStartConsumerEvidenceDetectorActionGen() *rapid.Generator[startConsumerEvidenceDetectorAction] { + return rapid.Custom(func(t *rapid.T) startConsumerEvidenceDetectorAction { + return startConsumerEvidenceDetectorAction{ + Chain: GetChainIDGen().Draw(t, "Chain"), + } + }) +} + +func GetUpdateLightClientActionGen() *rapid.Generator[updateLightClientAction] { + return rapid.Custom(func(t *rapid.T) updateLightClientAction { + return updateLightClientAction{ + Chain: GetChainIDGen().Draw(t, "Chain"), + HostChain: GetChainIDGen().Draw(t, "HostChain"), + RelayerConfig: rapid.String().Draw(t, "RelayerConfig"), + ClientID: rapid.String().Draw(t, "ClientID"), + } + }) +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 08ad4ae728..54ea824431 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -397,7 +397,7 @@ func ChangeoverTestConfig() TestConfig { func ConsumerMisbehaviourTestConfig() TestConfig { tc := TestConfig{ - name: "misbehaviour", + name: "consumer-misbehaviour", containerConfig: ContainerConfig{ ContainerName: "interchain-security-container", InstanceName: "interchain-security-instance", diff --git a/tests/e2e/json_utils.go b/tests/e2e/json_utils.go index 37c463cdc4..549bfe78c9 100644 --- a/tests/e2e/json_utils.go +++ b/tests/e2e/json_utils.go @@ -269,6 +269,24 @@ func UnmarshalMapToActionType(rawAction json.RawMessage, actionTypeString string if err == nil { return a, nil } + case "main.forkConsumerChainAction": + var a forkConsumerChainAction + err := json.Unmarshal(rawAction, &a) + if err == nil { + return a, nil + } + case "main.startConsumerEvidenceDetectorAction": + var a startConsumerEvidenceDetectorAction + err := json.Unmarshal(rawAction, &a) + if err == nil { + return a, nil + } + case "main.updateLightClientAction": + var a updateLightClientAction + err := json.Unmarshal(rawAction, &a) + if err == nil { + return a, nil + } default: return nil, fmt.Errorf("unknown action type: %s", actionTypeString) } diff --git a/tests/e2e/main.go b/tests/e2e/main.go index fb73b6ee8f..ac365e35b3 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -226,7 +226,8 @@ func getTestCases(selectedPredefinedTests, selectedTestFiles TestSet) (tests []t if len(selectedPredefinedTests) == 0 && len(selectedTestFiles) == 0 { selectedPredefinedTests = TestSet{ "changeover", "happy-path", - "democracy-reward", "democracy", "slash-throttle", + "democracy-reward", "democracy", + "slash-throttle", "consumer-double-sign", "consumer-misbehaviour", } if includeMultiConsumer != nil && *includeMultiConsumer { selectedPredefinedTests = append(selectedPredefinedTests, "multiconsumer") diff --git a/tests/e2e/trace_handlers_test.go b/tests/e2e/trace_handlers_test.go index 439b68ccad..024daae450 100644 --- a/tests/e2e/trace_handlers_test.go +++ b/tests/e2e/trace_handlers_test.go @@ -82,6 +82,8 @@ func TestWriteExamples(t *testing.T) { "shorthappy": {shortHappyPathSteps}, "democracyRewardsSteps": {democracyRewardsSteps}, "changeover": {changeoverSteps}, + "consumer-misbehaviour": {consumerMisbehaviourSteps}, + "consumer-double-sign": {consumerDoubleSignSteps}, } dir := "tracehandler_testdata" @@ -131,7 +133,7 @@ func TestMarshalAndUnmarshalChainState(t *testing.T) { }, }, }}, - "consuemr removal proposal": {ChainState{ + "consumer removal proposal": {ChainState{ Proposals: &map[uint]Proposal{ 5: ConsumerRemovalProposal{ Deposit: 10000001, diff --git a/tests/e2e/tracehandler_testdata/changeover.json b/tests/e2e/tracehandler_testdata/changeover.json index d973b46fe4..d569e066a3 100644 --- a/tests/e2e/tracehandler_testdata/changeover.json +++ b/tests/e2e/tracehandler_testdata/changeover.json @@ -27,6 +27,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -56,6 +57,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -82,7 +84,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -101,6 +103,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -147,6 +150,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -188,6 +192,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -242,6 +247,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -291,6 +297,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -349,6 +356,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "sover": { @@ -367,6 +375,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -417,6 +426,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -446,6 +456,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "sover": { @@ -464,6 +475,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -493,6 +505,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -520,6 +533,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -549,6 +563,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "sover": { @@ -567,6 +582,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -596,6 +612,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } diff --git a/tests/e2e/tracehandler_testdata/consumer-double-sign.json b/tests/e2e/tracehandler_testdata/consumer-double-sign.json new file mode 100644 index 0000000000..d1a7ae065f --- /dev/null +++ b/tests/e2e/tracehandler_testdata/consumer-double-sign.json @@ -0,0 +1,465 @@ +[ + { + "ActionType": "main.StartChainAction", + "Action": { + "Chain": "provi", + "Validators": [ + { + "Id": "bob", + "Allocation": 10000000000, + "Stake": 500000000 + }, + { + "Id": "alice", + "Allocation": 10000000000, + "Stake": 500000000 + }, + { + "Id": "carol", + "Allocation": 10000000000, + "Stake": 500000000 + } + ], + "GenesisChanges": "", + "IsConsumer": false + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9500000000, + "carol": 9500000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.submitConsumerAdditionProposalAction", + "Action": { + "PreCCV": false, + "Chain": "provi", + "From": "alice", + "Deposit": 10000001, + "ConsumerChain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "DistributionChannel": "" + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9489999999, + "bob": 9500000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": { + "1": { + "RawProposal": { + "Deposit": 10000001, + "Chain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "Status": "PROPOSAL_STATUS_VOTING_PERIOD" + }, + "Type": "main.ConsumerAdditionProposal" + } + } + } + } + }, + { + "ActionType": "main.assignConsumerPubKeyAction", + "Action": { + "Chain": "consu", + "Validator": "carol", + "ConsumerPubkey": "{\"@type\":\"/cosmos.crypto.ed25519.PubKey\",\"key\":\"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is=\"}", + "ReconfigureNode": false, + "ExpectError": false, + "ExpectedError": "" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": { + "carol": "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk" + }, + "ProviderKeys": { + "carol": "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6" + }, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.assignConsumerPubKeyAction", + "Action": { + "Chain": "consu", + "Validator": "carol", + "ConsumerPubkey": "{\"@type\":\"/cosmos.crypto.ed25519.PubKey\",\"key\":\"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is=\"}", + "ReconfigureNode": false, + "ExpectError": true, + "ExpectedError": "a validator has assigned the consumer key already: consumer key is already in use by a validator" + }, + "State": {} + }, + { + "ActionType": "main.assignConsumerPubKeyAction", + "Action": { + "Chain": "consu", + "Validator": "bob", + "ConsumerPubkey": "{\"@type\":\"/cosmos.crypto.ed25519.PubKey\",\"key\":\"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is=\"}", + "ReconfigureNode": false, + "ExpectError": true, + "ExpectedError": "a validator has assigned the consumer key already: consumer key is already in use by a validator" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": { + "bob": "", + "carol": "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk" + }, + "ProviderKeys": { + "carol": "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6" + }, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.voteGovProposalAction", + "Action": { + "Chain": "provi", + "From": [ + "alice", + "bob", + "carol" + ], + "Vote": [ + "yes", + "yes", + "yes" + ], + "PropNumber": 1 + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9500000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": { + "1": { + "RawProposal": { + "Deposit": 10000001, + "Chain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "Status": "PROPOSAL_STATUS_PASSED" + }, + "Type": "main.ConsumerAdditionProposal" + } + } + } + } + }, + { + "ActionType": "main.startConsumerChainAction", + "Action": { + "ConsumerChain": "consu", + "ProviderChain": "provi", + "Validators": [ + { + "Id": "bob", + "Allocation": 10000000000, + "Stake": 500000000 + }, + { + "Id": "alice", + "Allocation": 10000000000, + "Stake": 500000000 + }, + { + "Id": "carol", + "Allocation": 10000000000, + "Stake": 500000000 + } + ], + "GenesisChanges": ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"" + }, + "State": { + "consu": { + "ValBalances": { + "alice": 10000000000, + "bob": 10000000000, + "carol": 10000000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9500000000, + "carol": 9500000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.addIbcConnectionAction", + "Action": { + "ChainA": "consu", + "ChainB": "provi", + "ClientA": 0, + "ClientB": 0 + }, + "State": {} + }, + { + "ActionType": "main.addIbcChannelAction", + "Action": { + "ChainA": "consu", + "ChainB": "provi", + "ConnectionA": 0, + "PortA": "consumer", + "PortB": "provider", + "Order": "ordered", + "Version": "" + }, + "State": {} + }, + { + "ActionType": "main.doublesignSlashAction", + "Action": { + "Validator": "bob", + "Chain": "consu" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 500, + "carol": 500 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 500, + "carol": 500 + }, + "StakedTokens": { + "alice": 500000000, + "bob": 500000000, + "carol": 500000000 + }, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.startConsumerEvidenceDetectorAction", + "Action": { + "Chain": "consu" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 500, + "carol": 500 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 0, + "carol": 500 + }, + "StakedTokens": { + "alice": 500000000, + "bob": 475000000, + "carol": 500000000 + }, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.relayPacketsAction", + "Action": { + "ChainA": "provi", + "ChainB": "consu", + "Port": "provider", + "Channel": 0 + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 0, + "carol": 500 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 0, + "carol": 500 + }, + "StakedTokens": { + "alice": 500000000, + "bob": 475000000, + "carol": 500000000 + }, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + } +] \ No newline at end of file diff --git a/tests/e2e/tracehandler_testdata/consumer-misbehaviour.json b/tests/e2e/tracehandler_testdata/consumer-misbehaviour.json new file mode 100644 index 0000000000..717761327d --- /dev/null +++ b/tests/e2e/tracehandler_testdata/consumer-misbehaviour.json @@ -0,0 +1,437 @@ +[ + { + "ActionType": "main.StartChainAction", + "Action": { + "Chain": "provi", + "Validators": [ + { + "Id": "alice", + "Allocation": 10000000000, + "Stake": 500000000 + }, + { + "Id": "bob", + "Allocation": 10000000000, + "Stake": 20000000 + } + ], + "GenesisChanges": "", + "IsConsumer": false + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9980000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.submitConsumerAdditionProposalAction", + "Action": { + "PreCCV": false, + "Chain": "provi", + "From": "alice", + "Deposit": 10000001, + "ConsumerChain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "DistributionChannel": "" + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9489999999, + "bob": 9980000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": { + "1": { + "RawProposal": { + "Deposit": 10000001, + "Chain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "Status": "PROPOSAL_STATUS_VOTING_PERIOD" + }, + "Type": "main.ConsumerAdditionProposal" + } + } + } + } + }, + { + "ActionType": "main.assignConsumerPubKeyAction", + "Action": { + "Chain": "consu", + "Validator": "alice", + "ConsumerPubkey": "{\"@type\":\"/cosmos.crypto.ed25519.PubKey\",\"key\":\"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes=\"}", + "ReconfigureNode": false, + "ExpectError": false, + "ExpectedError": "" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": { + "alice": "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe" + }, + "ProviderKeys": { + "alice": "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq" + }, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.voteGovProposalAction", + "Action": { + "Chain": "provi", + "From": [ + "alice", + "bob" + ], + "Vote": [ + "yes", + "yes" + ], + "PropNumber": 1 + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9980000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": { + "1": { + "RawProposal": { + "Deposit": 10000001, + "Chain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "Status": "PROPOSAL_STATUS_PASSED" + }, + "Type": "main.ConsumerAdditionProposal" + } + } + } + } + }, + { + "ActionType": "main.startConsumerChainAction", + "Action": { + "ConsumerChain": "consu", + "ProviderChain": "provi", + "Validators": [ + { + "Id": "alice", + "Allocation": 10000000000, + "Stake": 500000000 + } + ], + "GenesisChanges": ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"" + }, + "State": { + "consu": { + "ValBalances": { + "alice": 10000000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9980000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.addIbcConnectionAction", + "Action": { + "ChainA": "consu", + "ChainB": "provi", + "ClientA": 0, + "ClientB": 0 + }, + "State": {} + }, + { + "ActionType": "main.addIbcChannelAction", + "Action": { + "ChainA": "consu", + "ChainB": "provi", + "ConnectionA": 0, + "PortA": "consumer", + "PortB": "provider", + "Order": "ordered", + "Version": "" + }, + "State": {} + }, + { + "ActionType": "main.delegateTokensAction", + "Action": { + "Chain": "provi", + "From": "alice", + "To": "alice", + "Amount": 11000000 + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 20 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 511, + "bob": 20 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.relayPacketsAction", + "Action": { + "ChainA": "provi", + "ChainB": "consu", + "Port": "provider", + "Channel": 0 + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 511, + "bob": 20 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.forkConsumerChainAction", + "Action": { + "ConsumerChain": "consu", + "ProviderChain": "provi", + "Validator": "alice", + "RelayerConfig": "/root/.hermes/config_fork.toml" + }, + "State": {} + }, + { + "ActionType": "main.startRelayerAction", + "Action": {}, + "State": {} + }, + { + "ActionType": "main.startConsumerEvidenceDetectorAction", + "Action": { + "Chain": "consu" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 511, + "bob": 20 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 511, + "bob": 20 + }, + "StakedTokens": { + "alice": 511000000, + "bob": 20000000 + }, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.updateLightClientAction", + "Action": { + "Chain": "consu", + "HostChain": "provi", + "RelayerConfig": "/root/.hermes/config_fork.toml", + "ClientID": "07-tendermint-0" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 511, + "bob": 20 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 0, + "bob": 20 + }, + "StakedTokens": { + "alice": 485450000, + "bob": 20000000 + }, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerChainQueueSizes": null, + "GlobalSlashQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": { + "07-tendermint-0": { + "revision_height": 1 + } + }, + "Proposals": null + } + } + } +] \ No newline at end of file diff --git a/tests/e2e/tracehandler_testdata/democracy.json b/tests/e2e/tracehandler_testdata/democracy.json index 9a435b3a0b..f25ba46eb1 100644 --- a/tests/e2e/tracehandler_testdata/democracy.json +++ b/tests/e2e/tracehandler_testdata/democracy.json @@ -1,4 +1,3 @@ - [ { "ActionType": "main.StartChainAction", @@ -22,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -41,6 +40,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -75,6 +75,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -119,6 +120,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -163,6 +165,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -199,6 +202,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -257,6 +261,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -275,6 +280,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -341,6 +347,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -359,6 +366,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -387,6 +395,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -416,6 +425,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -444,6 +454,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -485,6 +496,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -525,6 +537,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -555,6 +568,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -587,7 +601,7 @@ "State": { "democ": { "ValBalances": { - "alice": 9889999998, + "alice": 9899999999, "bob": 9960000001 }, "ValPowers": null, @@ -606,6 +620,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -639,6 +654,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": [], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -663,6 +679,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": [], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -698,6 +715,7 @@ "RegisteredConsumerRewardDenoms": [ "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9" ], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -731,6 +749,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -758,6 +777,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -776,6 +796,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -805,6 +826,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -823,6 +845,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -852,6 +875,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -879,6 +903,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -897,6 +922,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -929,6 +955,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } diff --git a/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json b/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json index 355ff1fbb8..d6fa266929 100644 --- a/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json +++ b/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json @@ -21,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -40,6 +40,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -74,6 +75,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -118,6 +120,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -162,6 +165,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -198,6 +202,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -256,6 +261,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -274,6 +280,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -340,6 +347,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -358,6 +366,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -386,6 +395,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -415,6 +425,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -443,6 +454,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -484,6 +496,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -524,6 +537,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -554,6 +568,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -586,7 +601,7 @@ "State": { "democ": { "ValBalances": { - "alice": 9889999998, + "alice": 9899999999, "bob": 9960000001 }, "ValPowers": null, @@ -605,6 +620,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -638,6 +654,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": [], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -662,6 +679,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": [], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -697,6 +715,7 @@ "RegisteredConsumerRewardDenoms": [ "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9" ], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -730,6 +749,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -757,6 +777,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -775,6 +796,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -804,6 +826,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -822,6 +845,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -851,6 +875,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -878,6 +903,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -896,6 +922,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -928,6 +955,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } diff --git a/tests/e2e/tracehandler_testdata/happyPath.json b/tests/e2e/tracehandler_testdata/happyPath.json index f46a4e983b..f4b7200352 100644 --- a/tests/e2e/tracehandler_testdata/happyPath.json +++ b/tests/e2e/tracehandler_testdata/happyPath.json @@ -21,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -40,6 +40,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -74,6 +75,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -118,6 +120,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -162,6 +165,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -198,6 +202,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -256,6 +261,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -274,6 +280,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -326,6 +333,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -344,6 +352,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -372,6 +381,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -401,6 +411,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -429,6 +440,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -466,6 +478,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -484,6 +497,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -519,6 +533,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -537,6 +552,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -566,6 +582,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -584,6 +601,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -613,6 +631,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -642,6 +661,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -660,6 +680,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -689,6 +710,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -718,6 +740,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -736,6 +759,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -765,6 +789,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -795,6 +820,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -813,6 +839,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -842,6 +869,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -869,6 +897,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -887,6 +916,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -916,6 +946,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -934,6 +965,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -964,6 +996,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -982,6 +1015,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1011,6 +1045,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1038,6 +1073,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1056,6 +1092,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1085,6 +1122,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1103,6 +1141,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1132,6 +1171,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1159,6 +1199,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1177,6 +1218,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1206,6 +1248,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1233,6 +1276,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1251,6 +1295,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1280,6 +1325,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1307,6 +1353,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1325,6 +1372,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1354,72 +1402,11 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } }, - { - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-09-20T18:24:51.823193+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9500000000 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Title": "", - "Description": "", - "Deposit": 0, - "Status": "" - }, - "Type": "main.TextProposal" - } - } - } - } - }, { "ActionType": "main.doublesignSlashAction", "Action": { @@ -1443,6 +1430,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1461,6 +1449,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1490,6 +1479,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1508,321 +1498,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.doublesignSlashAction", - "Action": { - "Validator": "bob", - "Chain": "consu" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-09-20T18:24:51.823197+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9489999999 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_VOTING_PERIOD" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.voteGovProposalAction", - "Action": { - "Chain": "provi", - "From": [ - "alice", - "bob", - "carol" - ], - "Vote": [ - "yes", - "yes", - "yes" - ], - "PropNumber": 2 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_PASSED" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1858,8 +1534,9 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "3": { + "2": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1886,7 +1563,7 @@ "no", "no" ], - "PropNumber": 3 + "PropNumber": 2 }, "State": { "provi": { @@ -1905,8 +1582,9 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "3": { + "2": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1945,8 +1623,9 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "4": { + "3": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1973,7 +1652,7 @@ "yes", "yes" ], - "PropNumber": 4 + "PropNumber": 3 }, "State": { "provi": { @@ -1990,8 +1669,9 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "4": { + "3": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", diff --git a/tests/e2e/tracehandler_testdata/multipleConsumers.json b/tests/e2e/tracehandler_testdata/multipleConsumers.json index 4774a94ad8..af7f6a06a4 100644 --- a/tests/e2e/tracehandler_testdata/multipleConsumers.json +++ b/tests/e2e/tracehandler_testdata/multipleConsumers.json @@ -21,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -40,6 +40,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -74,6 +75,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -118,6 +120,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -162,6 +165,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -198,6 +202,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -256,6 +261,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -274,6 +280,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -331,6 +338,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "2": { "RawProposal": { @@ -375,6 +383,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -419,6 +428,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -455,6 +465,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "2": { "RawProposal": { @@ -513,6 +524,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -531,6 +543,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -583,6 +596,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -601,6 +615,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -619,6 +634,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -648,6 +664,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -666,6 +683,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -684,6 +702,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -713,6 +732,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -731,6 +751,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -749,6 +770,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -778,6 +800,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -796,6 +819,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -814,6 +838,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -843,6 +868,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -861,6 +887,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -879,6 +906,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -908,6 +936,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -926,6 +955,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -944,6 +974,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -974,6 +1005,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -992,6 +1024,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1010,6 +1043,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1039,6 +1073,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1057,6 +1092,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1075,6 +1111,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1104,6 +1141,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1122,6 +1160,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1140,6 +1179,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1167,6 +1207,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1185,6 +1226,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1203,6 +1245,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1232,6 +1275,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1250,6 +1294,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1268,6 +1313,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1297,6 +1343,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1315,6 +1362,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1344,6 +1392,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1362,6 +1411,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1389,6 +1439,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1407,6 +1458,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1425,6 +1477,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1454,6 +1507,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1472,6 +1526,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1490,6 +1545,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1519,6 +1575,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1537,6 +1594,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1555,6 +1613,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1582,6 +1641,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1600,6 +1660,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1618,6 +1679,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1647,6 +1709,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1665,6 +1728,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1683,6 +1747,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1712,6 +1777,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1730,6 +1796,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1748,6 +1815,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1775,6 +1843,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1793,6 +1862,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1811,6 +1881,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1840,6 +1911,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1858,6 +1930,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1876,6 +1949,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1905,6 +1979,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1923,6 +1998,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1941,6 +2017,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1968,6 +2045,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1986,6 +2064,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -2004,6 +2083,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -2033,6 +2113,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -2051,6 +2132,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -2069,6 +2151,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -2098,6 +2181,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -2116,6 +2200,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -2134,6 +2219,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -2161,6 +2247,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -2179,6 +2266,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -2197,6 +2285,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -2226,6 +2315,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -2244,6 +2334,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -2262,6 +2353,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -2291,6 +2383,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -2309,6 +2402,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -2327,6 +2421,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -2356,6 +2451,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -2374,6 +2470,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } diff --git a/tests/e2e/tracehandler_testdata/shorthappy.json b/tests/e2e/tracehandler_testdata/shorthappy.json index ee82bf619f..df0d68d245 100644 --- a/tests/e2e/tracehandler_testdata/shorthappy.json +++ b/tests/e2e/tracehandler_testdata/shorthappy.json @@ -21,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -40,6 +40,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -74,6 +75,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -118,6 +120,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -162,6 +165,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -198,6 +202,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -256,6 +261,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -274,6 +280,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -326,6 +333,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -344,6 +352,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -372,6 +381,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -401,6 +411,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -429,6 +440,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -458,6 +470,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -476,6 +489,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -505,6 +519,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -535,6 +550,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -553,6 +569,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -582,6 +599,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -609,6 +627,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -627,6 +646,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -656,6 +676,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -674,6 +695,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -703,6 +725,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -730,6 +753,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -748,6 +772,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -777,6 +802,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -804,6 +830,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -822,6 +849,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -851,6 +879,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -878,6 +907,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -896,6 +926,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -925,72 +956,11 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } }, - { - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-09-20T18:24:51.823231+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9500000000 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Title": "", - "Description": "", - "Deposit": 0, - "Status": "" - }, - "Type": "main.TextProposal" - } - } - } - } - }, { "ActionType": "main.doublesignSlashAction", "Action": { @@ -1014,6 +984,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1032,6 +1003,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1061,6 +1033,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1079,321 +1052,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.doublesignSlashAction", - "Action": { - "Validator": "bob", - "Chain": "consu" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-09-20T18:24:51.823235+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9489999999 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_VOTING_PERIOD" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.voteGovProposalAction", - "Action": { - "Chain": "provi", - "From": [ - "alice", - "bob", - "carol" - ], - "Vote": [ - "yes", - "yes", - "yes" - ], - "PropNumber": 2 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_PASSED" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, - "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1429,8 +1088,9 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "3": { + "2": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1457,7 +1117,7 @@ "no", "no" ], - "PropNumber": 3 + "PropNumber": 2 }, "State": { "provi": { @@ -1476,8 +1136,9 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "3": { + "2": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1516,8 +1177,9 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "4": { + "3": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1544,7 +1206,7 @@ "yes", "yes" ], - "PropNumber": 4 + "PropNumber": 3 }, "State": { "provi": { @@ -1561,8 +1223,9 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "4": { + "3": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", diff --git a/tests/e2e/tracehandler_testdata/slashThrottle.json b/tests/e2e/tracehandler_testdata/slashThrottle.json index ea8306fd4c..67459c197e 100644 --- a/tests/e2e/tracehandler_testdata/slashThrottle.json +++ b/tests/e2e/tracehandler_testdata/slashThrottle.json @@ -21,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -40,6 +40,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -74,6 +75,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -118,6 +120,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -162,6 +165,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -198,6 +202,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -256,6 +261,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -274,6 +280,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -326,6 +333,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -344,6 +352,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -372,6 +381,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -401,6 +411,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -429,6 +440,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -456,6 +468,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -474,6 +487,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -503,6 +517,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -523,6 +538,7 @@ }, "GlobalSlashQueueSize": 0, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -550,6 +566,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -568,6 +585,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -597,6 +615,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -617,6 +636,7 @@ }, "GlobalSlashQueueSize": 1, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -646,6 +666,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -666,6 +687,7 @@ }, "GlobalSlashQueueSize": 0, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -695,6 +717,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -715,6 +738,7 @@ }, "GlobalSlashQueueSize": 0, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -745,6 +769,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "2": { "RawProposal": { @@ -790,6 +815,7 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "2": { "RawProposal": { From 92a9d1768e3366bf7b4b897c92a6cc5854976f29 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 4 Oct 2023 13:23:52 +0200 Subject: [PATCH 41/82] fix democ tests --- tests/e2e/config.go | 5 ++-- tests/e2e/main.go | 6 ++++- tests/e2e/steps_democracy.go | 15 +++++++++++- tests/e2e/steps_reward_denom.go | 15 +++++++++++- .../e2e/tracehandler_testdata/democracy.json | 16 ++++++++++--- .../democracyRewardsSteps.json | 23 ++++++++++++++++--- .../e2e/tracehandler_testdata/happyPath.json | 4 ++-- .../e2e/tracehandler_testdata/shorthappy.json | 4 ++-- 8 files changed, 73 insertions(+), 15 deletions(-) diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 0a164c2cec..942bb338e7 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -243,11 +243,12 @@ func DefaultTestConfig() TestConfig { func DemocracyTestConfig(allowReward bool) TestConfig { consumerGenChanges := ".app_state.ccvconsumer.params.blocks_per_distribution_transmission = \"20\" | " + - ".app_state.gov.voting_params.voting_period = \"10s\" | " + + ".app_state.gov.params.voting_period = \"10s\" | " + ".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 = \"60s\" | " + - ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"" + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + + ".app_state.transfer.params.send_enabled = false" if allowReward { // This allows the consumer chain to send rewards in the stake denom diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 87c617be18..0be163f8db 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -173,8 +173,12 @@ func getTestFileUsageString() string { // Test runner selection builder.WriteString("Test runner selection:\nSelection of test runners to be executed:\n") + testConfigSet := map[string]struct{}{} for _, testConfig := range testConfigs { - builder.WriteString(fmt.Sprintf("- %s\n", testConfig.name)) + if _, ok := testConfigSet[testConfig.name]; !ok { + builder.WriteString(fmt.Sprintf("- %s\n", testConfig.name)) + testConfigSet[testConfig.name] = struct{}{} + } } builder.WriteString("\n") diff --git a/tests/e2e/steps_democracy.go b/tests/e2e/steps_democracy.go index a264f868a7..9605f8698a 100644 --- a/tests/e2e/steps_democracy.go +++ b/tests/e2e/steps_democracy.go @@ -77,6 +77,8 @@ func stepsDemocracy(consumerName string) []Step { ValidatorID("alice"): 9889999998, ValidatorID("bob"): 9960000001, }, + // Check that the "SendEnabled" transfer parameter is set to false + Params: &([]Param{{Subspace: "transfer", Key: "SendEnabled", Value: "false"}}), Proposals: &map[uint]Proposal{ 1: ParamsProposal{ Deposit: 10000001, @@ -99,10 +101,21 @@ func stepsDemocracy(consumerName string) []Step { }, State: State{ ChainID(consumerName): ChainState{ + // Check that alice gets the prop deposit refunded ValBalances: &map[ValidatorID]uint{ - ValidatorID("alice"): 9889999998, + ValidatorID("alice"): 9899999999, ValidatorID("bob"): 9960000001, }, + // Check that the prop passed + Proposals: &map[uint]Proposal{ + 1: ParamsProposal{ + Deposit: 10000001, + Status: "PROPOSAL_STATUS_PASSED", + Subspace: "transfer", + Key: "SendEnabled", + Value: "true", + }, + }, // Check that the parameter is changed on gov-consumer chain Params: &([]Param{{Subspace: "transfer", Key: "SendEnabled", Value: "true"}}), }, diff --git a/tests/e2e/steps_reward_denom.go b/tests/e2e/steps_reward_denom.go index 1a47accd07..d5b524fbd9 100644 --- a/tests/e2e/steps_reward_denom.go +++ b/tests/e2e/steps_reward_denom.go @@ -75,6 +75,8 @@ func stepsRewardDenomConsumer(consumerName string) []Step { ValidatorID("alice"): 9889999998, ValidatorID("bob"): 9960000001, }, + // Check that the "SendEnabled" transfer parameter is set to false + Params: &([]Param{{Subspace: "transfer", Key: "SendEnabled", Value: "false"}}), Proposals: &map[uint]Proposal{ 1: ParamsProposal{ Deposit: 10000001, @@ -97,10 +99,21 @@ func stepsRewardDenomConsumer(consumerName string) []Step { }, State: State{ ChainID(consumerName): ChainState{ + // Check that alice gets the prop deposit refunded ValBalances: &map[ValidatorID]uint{ - ValidatorID("alice"): 9889999998, + ValidatorID("alice"): 9899999999, ValidatorID("bob"): 9960000001, }, + // Check that the prop passed + Proposals: &map[uint]Proposal{ + 1: ParamsProposal{ + Deposit: 10000001, + Status: "PROPOSAL_STATUS_PASSED", + Subspace: "transfer", + Key: "SendEnabled", + Value: "true", + }, + }, // Check that the parameter is changed on gov-consumer chain Params: &([]Param{{Subspace: "transfer", Key: "SendEnabled", Value: "true"}}), }, diff --git a/tests/e2e/tracehandler_testdata/democracy.json b/tests/e2e/tracehandler_testdata/democracy.json index 9a435b3a0b..12993e4a38 100644 --- a/tests/e2e/tracehandler_testdata/democracy.json +++ b/tests/e2e/tracehandler_testdata/democracy.json @@ -1,4 +1,3 @@ - [ { "ActionType": "main.StartChainAction", @@ -587,7 +586,7 @@ "State": { "democ": { "ValBalances": { - "alice": 9889999998, + "alice": 9899999999, "bob": 9960000001 }, "ValPowers": null, @@ -606,7 +605,18 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, - "Proposals": null + "Proposals": { + "1": { + "RawProposal": { + "Deposit": 10000001, + "Status": "PROPOSAL_STATUS_PASSED", + "Subspace": "transfer", + "Key": "SendEnabled", + "Value": "true" + }, + "Type": "main.ParamsProposal" + } + } } } }, diff --git a/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json b/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json index 355ff1fbb8..0251309ef2 100644 --- a/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json +++ b/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json @@ -546,7 +546,13 @@ }, "ValPowers": null, "StakedTokens": null, - "Params": null, + "Params": [ + { + "Subspace": "transfer", + "Key": "SendEnabled", + "Value": "false" + } + ], "Rewards": null, "ConsumerChains": null, "AssignedKeys": null, @@ -586,7 +592,7 @@ "State": { "democ": { "ValBalances": { - "alice": 9889999998, + "alice": 9899999999, "bob": 9960000001 }, "ValPowers": null, @@ -605,7 +611,18 @@ "ConsumerChainQueueSizes": null, "GlobalSlashQueueSize": null, "RegisteredConsumerRewardDenoms": null, - "Proposals": null + "Proposals": { + "1": { + "RawProposal": { + "Deposit": 10000001, + "Status": "PROPOSAL_STATUS_PASSED", + "Subspace": "transfer", + "Key": "SendEnabled", + "Value": "true" + }, + "Type": "main.ParamsProposal" + } + } } } }, diff --git a/tests/e2e/tracehandler_testdata/happyPath.json b/tests/e2e/tracehandler_testdata/happyPath.json index f46a4e983b..1f5be74833 100644 --- a/tests/e2e/tracehandler_testdata/happyPath.json +++ b/tests/e2e/tracehandler_testdata/happyPath.json @@ -1363,7 +1363,7 @@ "Action": { "Chain": "consu", "Height": 10, - "Time": "2023-09-20T18:24:51.823193+02:00", + "Time": "2023-10-04T12:14:14.883367+02:00", "Power": 500, "Validator": "bob", "Deposit": 10000001, @@ -1656,7 +1656,7 @@ "Action": { "Chain": "consu", "Height": 10, - "Time": "2023-09-20T18:24:51.823197+02:00", + "Time": "2023-10-04T12:14:14.88337+02:00", "Power": 500, "Validator": "bob", "Deposit": 10000001, diff --git a/tests/e2e/tracehandler_testdata/shorthappy.json b/tests/e2e/tracehandler_testdata/shorthappy.json index ee82bf619f..697af97fef 100644 --- a/tests/e2e/tracehandler_testdata/shorthappy.json +++ b/tests/e2e/tracehandler_testdata/shorthappy.json @@ -934,7 +934,7 @@ "Action": { "Chain": "consu", "Height": 10, - "Time": "2023-09-20T18:24:51.823231+02:00", + "Time": "2023-10-04T12:14:14.883385+02:00", "Power": 500, "Validator": "bob", "Deposit": 10000001, @@ -1227,7 +1227,7 @@ "Action": { "Chain": "consu", "Height": 10, - "Time": "2023-09-20T18:24:51.823235+02:00", + "Time": "2023-10-04T12:14:14.883388+02:00", "Power": 500, "Validator": "bob", "Deposit": 10000001, From 00d4f2f67d0cadeac4171b83919d08c8641b697a Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 4 Oct 2023 14:34:02 +0200 Subject: [PATCH 42/82] update changelog --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4716179956..ca49aaf0fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,7 @@ Add an entry to the unreleased provider section whenever merging a PR to main that is not targeted at a specific release. These entries will eventually be included in a provider release. -* (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 +* (refactor) [#1294](https://github.com/cosmos/interchain-security/pull/1294) Remove the equivocation proposal handler. * (feature!) [#1280](https://github.com/cosmos/interchain-security/pull/1280) provider proposal for changing reward denoms * (feature!) [#1244](https://github.com/cosmos/interchain-security/pull/1244) Update the default consumer unbonding period to 2 weeks. * (deps) [#1259](https://github.com/cosmos/interchain-security/pull/1259) Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to [v0.47.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.5). From e29cb925f68606bfe64dd7bdecb1d10ef0b4eb59 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 4 Oct 2023 14:48:23 +0200 Subject: [PATCH 43/82] fix CHANGELOG.md --- CHANGELOG.md | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca49aaf0fa..b935231e16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,23 +95,7 @@ Upgrading a provider from `v1.1.0-multiden` to `v2.0.0` will require state migra Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state migrations. See the consumer module's `ConsensusVersion` in [module](./x/ccv/consumer/module.go) - -## v2.0.0-lsm - -Date: August 18th, 2023 - -* (deps!) [#1120](https://github.com/cosmos/interchain-security/pull/1120) Bump [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) to [v0.45.16-ics-lsm](https://github.com/cosmos/cosmos-sdk/tree/v0.45.16-ics-lsm). This requires adapting ICS to support this SDK release. Changes are state breaking. -* (fix) [#720](https://github.com/cosmos/interchain-security/issues/720) Fix the attribute `AttributeDistributionTotal` value in `FeeDistribution` event emit. - -## v2.0.0 - -Date: June 1st, 2023 - -Unlike prior releases, the ICS `v2.0.0` release will be based on the main branch. `v2.0.0` will contain all the accumulated PRs from the various releases below, along with other PRs that were merged, but not released to production. After `v2.0.0`, we plan to revamp release practices, and how we modularize the repo for consumer/provider. - -Upgrading a provider from `v1.1.0-multiden` to `v2.0.0` will require state migrations. See [migration.go](./x/ccv/provider/keeper/migration.go). See the provider module's `ConsensusVersion` in [module](./x/ccv/provider/module.go) - -Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state migrations. See the consumer module's `ConsensusVersion` in [module](./x/ccv/consumer/module.go) +Some PRs from v2.0.0 may reappear from other releases below. This is due to the fact that ICS v1.1.0 deviates from the commit ordering of the main branch, and other releases thereafter are based on v1.1.0. ### High level changes included in v2.0.0 @@ -155,7 +139,6 @@ Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state m * (api) Add consumer QueryParams [#746](https://github.com/cosmos/interchain-security/pull/746) * (feature) New validation for keeper fields [#740](https://github.com/cosmos/interchain-security/pull/740) - ## v1.2.0-multiden The first release candidate for a fix built on top of v1.2.0, intended for consumers. This release adds a list of denoms on the consumer that are allowed to be sent to the provider as rewards. This prevents a potential DOS attack that was discovered during the audit of Replicated Security performed by Oak Security and funded by the Cosmos Hub community through Proposal 687. In an effort to move quickly, this release also includes a multisig fix that is effective only for provider. It shouldn't affect the consumer module. From d35c98356140b2889e78d91db33022c812ce0ca9 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 4 Oct 2023 14:52:50 +0200 Subject: [PATCH 44/82] nits --- tests/difference/core/driver/setup.go | 25 ++++++++++++------------- tests/e2e/steps.go | 2 -- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/tests/difference/core/driver/setup.go b/tests/difference/core/driver/setup.go index 695648eca1..c1d1c272ff 100644 --- a/tests/difference/core/driver/setup.go +++ b/tests/difference/core/driver/setup.go @@ -177,17 +177,17 @@ func (b *Builder) getAppBytesAndSenders( require.NoError(b.suite.T(), err) validator := stakingtypes.Validator{ - OperatorAddress: sdk.ValAddress(val.Address).String(), - ConsensusPubkey: pkAny, - Jailed: false, - Status: status, - Tokens: tokens, - DelegatorShares: sumShares, - Description: stakingtypes.Description{}, - UnbondingHeight: int64(0), - UnbondingTime: time.Unix(0, 0).UTC(), - Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), - // MinSelfDelegation: sdk.ZeroInt(), + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: status, + Tokens: tokens, + DelegatorShares: sumShares, + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + MinSelfDelegation: sdk.ZeroInt(), } stakingValidators = append(stakingValidators, validator) @@ -433,8 +433,7 @@ func (b *Builder) addValidatorToStakingModule(privVal mock.PV) { coin, stakingtypes.Description{}, stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), - sdk.NewInt(0), - ) + sdk.ZeroInt()) b.suite.Require().NoError(err) providerStaking := b.providerStakingKeeper() pskServer := stakingkeeper.NewMsgServerImpl(&providerStaking) diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 498516bea8..1da7e6a698 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -24,7 +24,6 @@ var happyPathSteps = concatSteps( stepsRedelegate("consu"), stepsDowntime("consu"), stepsDoubleSignOnProvider("consu"), // carol double signs on provider - // TODO: add double sign on consumer stepsStartRelayer(), stepsConsumerRemovalPropNotPassing("consu", 2), // submit removal prop but vote no on it - chain should stay stepsStopChain("consu", 3), // stop chain @@ -37,7 +36,6 @@ var shortHappyPathSteps = concatSteps( stepsRedelegateShort("consu"), stepsDowntime("consu"), stepsDoubleSignOnProvider("consu"), // carol double signs on provider - // TODO: add double sign on consumer stepsStartRelayer(), stepsConsumerRemovalPropNotPassing("consu", 2), // submit removal prop but vote no on it - chain should stay stepsStopChain("consu", 3), // stop chain From 9688154ec8dbcb9cf062359b3df6498876c2ad1b Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 12 Oct 2023 14:05:17 +0200 Subject: [PATCH 45/82] Update docs/docs/features/slashing.md Co-authored-by: insumity --- docs/docs/features/slashing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/features/slashing.md b/docs/docs/features/slashing.md index 09ceeefd0b..cdf166da8c 100644 --- a/docs/docs/features/slashing.md +++ b/docs/docs/features/slashing.md @@ -21,7 +21,7 @@ Slash throttling (sometimes called jail throttling) mechanism ensures that only ::: # 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. +The Cryptographic verification of equivocation allows external agents to submit evidences of light client and double signing attacks observed on a consumer chain. When valid evidence is received, the malicious validators will be slashed, jailed, and tombstoned on the provider. The feature is outlined in [ADR-005](../adrs/adr-005-cryptographic-equivocation-verification.md) and [ADR-013](../adrs/adr-013-equivocation-slashing.md). From 108fe33acb13b75c1d25675f40866c125ef55891 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 12 Oct 2023 14:05:37 +0200 Subject: [PATCH 46/82] Update proto/interchain_security/ccv/provider/v1/tx.proto Co-authored-by: insumity --- proto/interchain_security/ccv/provider/v1/tx.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 27f10c0232..b031ee3fa6 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -35,7 +35,7 @@ message MsgAssignConsumerKeyResponse {} // MsgSubmitConsumerMisbehaviour defines a message that reports a light client attack, -// also known as a misbehaviour, observed on a consumer chain +// also known as a misbehaviour, observed on a consumer chain message MsgSubmitConsumerMisbehaviour { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; From 1aaea8858e535952d3d8dda7ed0701f5e4f0f795 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 12 Oct 2023 14:05:54 +0200 Subject: [PATCH 47/82] Update x/ccv/provider/client/cli/tx.go Co-authored-by: insumity --- x/ccv/provider/client/cli/tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 994bdd0c10..5f03591d85 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -134,7 +134,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), From 1d511b4b30003c6dd2a00361481c7f31b3f3aa7b Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 12 Oct 2023 14:06:22 +0200 Subject: [PATCH 48/82] Update x/ccv/provider/client/cli/tx.go Co-authored-by: insumity --- x/ccv/provider/client/cli/tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 5f03591d85..c386c0d12c 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -82,7 +82,7 @@ func NewSubmitConsumerMisbehaviourCmd() *cobra.Command { 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: +Example: %s tx provider submit-consumer-misbehaviour [path/to/misbehaviour.json] --from node0 --home ../node0 --chain-id $CID `, version.AppName)), Args: cobra.ExactArgs(1), From 991181a536c686cd397ce9a0a2af97ea20dc8cbd Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 12 Oct 2023 14:07:47 +0200 Subject: [PATCH 49/82] Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity --- x/ccv/provider/keeper/misbehaviour.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index fd7ec6bf0d..2f34e5d1c5 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -16,7 +16,7 @@ import ( ) // HandleConsumerMisbehaviour checks if the given IBC misbehaviour corresponds to an equivocation light client attack, -// and in this case, jails the Byzantine validators +// and in this case, slashes, jails, and tombstones the Byzantine validators func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { logger := k.Logger(ctx) From c858173161bfadc65053f3ec36bd5128cd3b0f54 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 12 Oct 2023 14:17:55 +0200 Subject: [PATCH 50/82] Update x/ccv/provider/keeper/punish_validator.go Co-authored-by: insumity --- x/ccv/provider/keeper/punish_validator.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go index 29664841c0..cda78ecf9b 100644 --- a/x/ccv/provider/keeper/punish_validator.go +++ b/x/ccv/provider/keeper/punish_validator.go @@ -39,7 +39,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 fb92af928ed2e07d1c477a77429761a9b30b526b Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 12 Oct 2023 14:19:18 +0200 Subject: [PATCH 51/82] Update x/ccv/provider/keeper/punish_validator.go Co-authored-by: insumity --- x/ccv/provider/keeper/punish_validator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go index cda78ecf9b..ab59407ff3 100644 --- a/x/ccv/provider/keeper/punish_validator.go +++ b/x/ccv/provider/keeper/punish_validator.go @@ -85,6 +85,7 @@ func (k Keeper) SlashValidator(ctx sdk.Context, providerAddr types.ProviderConsA return errorsmod.Wrapf(slashingtypes.ErrNoValidatorForAddress, "provider consensus address: %s", providerAddr.String()) } + // check if the validator is unbonded to prevent panicking when slashing (see cosmos/cosmos-sdk/blob/v0.47.5/x/staking/keeper/slash.go#L61) if validator.IsUnbonded() { return fmt.Errorf("validator is unbonded. provider consensus address: %s", providerAddr.String()) } From 28e0c14b34d5d15ea0eb19b694c74513667afe09 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 12 Oct 2023 14:19:34 +0200 Subject: [PATCH 52/82] Update x/ccv/provider/keeper/punish_validator.go Co-authored-by: insumity --- x/ccv/provider/keeper/punish_validator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go index ab59407ff3..5b93b490b1 100644 --- a/x/ccv/provider/keeper/punish_validator.go +++ b/x/ccv/provider/keeper/punish_validator.go @@ -90,6 +90,7 @@ func (k Keeper) SlashValidator(ctx sdk.Context, providerAddr types.ProviderConsA return fmt.Errorf("validator is unbonded. provider consensus address: %s", providerAddr.String()) } + // check if the validator is already tombstoned to avoid slashing a validator more than once if k.slashingKeeper.IsTombstoned(ctx, providerAddr.ToSdkConsAddr()) { return fmt.Errorf("validator is tombstoned. provider consensus address: %s", providerAddr.String()) } From 9f23165cf3fe9355f5da2b88e670a75600afadbe Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 13 Oct 2023 09:14:03 +0200 Subject: [PATCH 53/82] check verifying pubkey against validator address --- testutil/crypto/evidence.go | 36 +++++++++++++++++++++++ x/ccv/provider/client/proposal_handler.go | 5 ---- x/ccv/provider/keeper/double_vote.go | 9 ++++++ x/ccv/provider/keeper/double_vote_test.go | 24 +++++++++++++++ x/ccv/provider/keeper/punish_validator.go | 6 ++-- 5 files changed, 71 insertions(+), 9 deletions(-) diff --git a/testutil/crypto/evidence.go b/testutil/crypto/evidence.go index d6a8708d9d..f646ea3d30 100644 --- a/testutil/crypto/evidence.go +++ b/testutil/crypto/evidence.go @@ -54,3 +54,39 @@ 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/client/proposal_handler.go b/x/ccv/provider/client/proposal_handler.go index 0e18048bce..bfd04af4c2 100644 --- a/x/ccv/provider/client/proposal_handler.go +++ b/x/ccv/provider/client/proposal_handler.go @@ -439,9 +439,4 @@ func postConsumerRemovalProposalHandlerFn(clientCtx client.Context) http.Handler 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/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 3b459bb8c1..2b550c39aa 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -62,6 +62,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 d3f8e86e45..976ddeeff3 100644 --- a/x/ccv/provider/keeper/double_vote_test.go +++ b/x/ccv/provider/keeper/double_vote_test.go @@ -47,6 +47,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{ diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go index 5b93b490b1..06eac14a1f 100644 --- a/x/ccv/provider/keeper/punish_validator.go +++ b/x/ccv/provider/keeper/punish_validator.go @@ -34,8 +34,6 @@ func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.Pr k.stakingKeeper.Jail(ctx, providerAddr.ToSdkConsAddr()) } - // 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) // Tombstone the validator so that we cannot slash the validator more than once @@ -85,12 +83,12 @@ func (k Keeper) SlashValidator(ctx sdk.Context, providerAddr types.ProviderConsA return errorsmod.Wrapf(slashingtypes.ErrNoValidatorForAddress, "provider consensus address: %s", providerAddr.String()) } - // check if the validator is unbonded to prevent panicking when slashing (see cosmos/cosmos-sdk/blob/v0.47.5/x/staking/keeper/slash.go#L61) + // check if the validator is unbonded to prevent panicking when slashing (see cosmos/cosmos-sdk/blob/v0.47.5/x/staking/keeper/slash.go#L61) if validator.IsUnbonded() { return fmt.Errorf("validator is unbonded. provider consensus address: %s", providerAddr.String()) } - // check if the validator is already tombstoned to avoid slashing a validator more than once + // check if the validator is already tombstoned to avoid slashing a validator more than once if k.slashingKeeper.IsTombstoned(ctx, providerAddr.ToSdkConsAddr()) { return fmt.Errorf("validator is tombstoned. provider consensus address: %s", providerAddr.String()) } From 7092914018d65d8392a6906399e0ba734c9b15c8 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 13 Oct 2023 17:16:22 +0200 Subject: [PATCH 54/82] add tests --- testutil/keeper/expectations.go | 63 +++++ x/ccv/provider/keeper/double_vote.go | 5 +- x/ccv/provider/keeper/misbehaviour.go | 7 +- x/ccv/provider/keeper/punish_validator.go | 67 +++-- .../provider/keeper/punish_validator_test.go | 233 +++++++++--------- 5 files changed, 212 insertions(+), 163 deletions(-) diff --git a/testutil/keeper/expectations.go b/testutil/keeper/expectations.go index 7814fe0fcf..4702e688e2 100644 --- a/testutil/keeper/expectations.go +++ b/testutil/keeper/expectations.go @@ -3,6 +3,7 @@ package keeper import ( time "time" + math "cosmossdk.io/math" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" conntypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" @@ -156,3 +157,65 @@ func GetMocksForSendIBCPacket(ctx sdk.Context, mocks MockedKeepers, channelID st ).Return(uint64(888), nil).Times(times), } } + +func GetMocksForSlashValidator( + ctx sdk.Context, + mocks MockedKeepers, + validator stakingtypes.Validator, + consAddr sdk.ConsAddress, + undelegations []stakingtypes.UnbondingDelegation, + redelegations []stakingtypes.Redelegation, + powerReduction math.Int, + slashFraction math.LegacyDec, + currentPower, + expectedInfractionHeight, + expectedSlashPower int64, +) []*gomock.Call { + return []*gomock.Call{ + 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) math.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) math.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(). + SlashWithInfractionReason(ctx, consAddr, expectedInfractionHeight, expectedSlashPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN). + Times(1), + } + +} diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 2b550c39aa..9a160d412a 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -35,10 +35,7 @@ func (k Keeper) HandleConsumerDoubleVoting( types.NewConsumerConsAddress(sdk.ConsAddress(evidence.VoteA.ValidatorAddress.Bytes())), ) - if err := k.SlashValidator(ctx, providerAddr); err != nil { - return err - } - if err := k.JailAndTombstoneValidator(ctx, providerAddr); err != nil { + if err := k.PunishValidator(ctx, providerAddr); err != nil { return err } diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 2f34e5d1c5..c9a8a0849e 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -47,14 +47,11 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty misbehaviour.Header1.Header.ChainID, types.NewConsumerConsAddress(sdk.ConsAddress(v.Address.Bytes())), ) - err := k.SlashValidator(ctx, providerAddr) - if err != nil { - errors = append(errors, err) - } - err = k.JailAndTombstoneValidator(ctx, providerAddr) + err := k.PunishValidator(ctx, providerAddr) if err != nil { errors = append(errors, err) } + provAddrs = append(provAddrs, providerAddr) } diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go index 06eac14a1f..0f8b9fd8e6 100644 --- a/x/ccv/provider/keeper/punish_validator.go +++ b/x/ccv/provider/keeper/punish_validator.go @@ -14,8 +14,7 @@ import ( "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ) -// JailAndTombstoneValidator jails and tombstones the validator with the given provider consensus address -func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress) error { +func (k Keeper) PunishValidator(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()) @@ -29,20 +28,48 @@ func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.Pr return fmt.Errorf("validator is tombstoned. provider consensus address: %s", providerAddr.String()) } + k.SlashValidator(ctx, validator) + k.JailAndTombstoneValidator(ctx, validator) + + return nil +} + +// JailAndTombstoneValidator jails and tombstones the validator with the given provider consensus address +func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, validator stakingtypes.Validator) { + consAdrr, err := validator.GetConsAddr() + if err != nil { + panic(err) + } + // jail validator if not already if !validator.IsJailed() { - k.stakingKeeper.Jail(ctx, providerAddr.ToSdkConsAddr()) + k.stakingKeeper.Jail(ctx, consAdrr) } - k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime) + k.slashingKeeper.JailUntil(ctx, consAdrr, evidencetypes.DoubleSignJailEndTime) // Tombstone the validator so that we cannot slash the validator more than once // 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()) + k.slashingKeeper.Tombstone(ctx, consAdrr) +} - return nil +// SlashValidator slashes the given validator +func (k Keeper) SlashValidator(ctx sdk.Context, validator stakingtypes.Validator) { + 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) + + consAdrr, err := validator.GetConsAddr() + if err != nil { + panic(err) + } + + k.stakingKeeper.SlashWithInfractionReason(ctx, consAdrr, 0, totalPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN) } // ComputePowerToSlash computes the power to be slashed based on the tokens in non-matured `undelegations` and @@ -75,31 +102,3 @@ func (k Keeper) ComputePowerToSlash(ctx sdk.Context, validator stakingtypes.Vali 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()) - } - - // check if the validator is unbonded to prevent panicking when slashing (see cosmos/cosmos-sdk/blob/v0.47.5/x/staking/keeper/slash.go#L61) - if validator.IsUnbonded() { - return fmt.Errorf("validator is unbonded. provider consensus address: %s", providerAddr.String()) - } - - // check if the validator is already tombstoned to avoid slashing a validator more than once - 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.SlashWithInfractionReason(ctx, providerAddr.ToSdkConsAddr(), 0, totalPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN) - return nil -} diff --git a/x/ccv/provider/keeper/punish_validator_test.go b/x/ccv/provider/keeper/punish_validator_test.go index cce8749710..5c13e3a274 100644 --- a/x/ccv/provider/keeper/punish_validator_test.go +++ b/x/ccv/provider/keeper/punish_validator_test.go @@ -10,7 +10,6 @@ import ( "cosmossdk.io/math" - 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" @@ -18,19 +17,36 @@ import ( tmtypes "github.com/cometbft/cometbft/types" - cryptotestutil "github.com/cosmos/interchain-security/v3/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ) // 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() +func TestPunishValidator(t *testing.T) { + pubKey, _ := cryptocodec.FromTmPubKeyInterface(tmtypes.NewMockPV().PrivKey.PubKey()) + + // manually build a validator instead of using `stakingtypes.NewValidator` + // to guarantee that the validator is bonded is jailed for testing + + validator, err := stakingtypes.NewValidator( + sdk.ValAddress(pubKey.Address().Bytes()), + pubKey, + stakingtypes.NewDescription("", "", "", "", ""), + ) + require.NoError(t, err) + + slashFraction, _ := sdk.NewDecFromStr("0.5") + consAddr, _ := validator.GetConsAddr() + providerConsAddr := types.NewProviderConsAddress(consAddr) + + fmt.Println(validator.GetOperator()) + testCases := []struct { name string provAddr types.ProviderConsAddress expectedCalls func(sdk.Context, testkeeper.MockedKeepers, types.ProviderConsAddress) []*gomock.Call + expErr bool }{ { "unfound validator", @@ -47,6 +63,7 @@ func TestJailAndTombstoneValidator(t *testing.T) { ).Times(1), } }, + true, }, { "unbonded validator", @@ -62,6 +79,7 @@ func TestJailAndTombstoneValidator(t *testing.T) { ).Times(1), } }, + true, }, { "tombstoned validator", @@ -80,6 +98,7 @@ func TestJailAndTombstoneValidator(t *testing.T) { ).Times(1), } }, + true, }, { "jailed validator", @@ -87,23 +106,46 @@ func TestJailAndTombstoneValidator(t *testing.T) { func(ctx sdk.Context, mocks testkeeper.MockedKeepers, provAddr types.ProviderConsAddress, ) []*gomock.Call { - return []*gomock.Call{ + validator.Jailed = true + validator.Status = stakingtypes.Unbonding + gc := append([]*gomock.Call{ mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( ctx, providerConsAddr.ToSdkConsAddr()).Return( - stakingtypes.Validator{Jailed: true}, true, + validator, true, ).Times(1), mocks.MockSlashingKeeper.EXPECT().IsTombstoned( ctx, providerConsAddr.ToSdkConsAddr()).Return( false, ).Times(1), + }, + testkeeper.GetMocksForSlashValidator( + ctx, + mocks, + validator, + consAddr, + []stakingtypes.UnbondingDelegation{}, + []stakingtypes.Redelegation{}, + sdk.NewInt(2), + slashFraction, + int64(1), + int64(0), + int64(1), + )..., + ) + + gc = append( + gc, mocks.MockSlashingKeeper.EXPECT().JailUntil( ctx, providerConsAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime). Times(1), mocks.MockSlashingKeeper.EXPECT().Tombstone( ctx, providerConsAddr.ToSdkConsAddr()). Times(1), - } + ) + return gc + }, + false, }, { "bonded validator", @@ -111,15 +153,35 @@ func TestJailAndTombstoneValidator(t *testing.T) { func(ctx sdk.Context, mocks testkeeper.MockedKeepers, provAddr types.ProviderConsAddress, ) []*gomock.Call { - return []*gomock.Call{ + validator.Jailed = false + validator.Status = stakingtypes.Bonded + gc := append([]*gomock.Call{ mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( ctx, providerConsAddr.ToSdkConsAddr()).Return( - stakingtypes.Validator{Status: stakingtypes.Bonded}, true, + validator, true, ).Times(1), mocks.MockSlashingKeeper.EXPECT().IsTombstoned( ctx, providerConsAddr.ToSdkConsAddr()).Return( false, ).Times(1), + }, + testkeeper.GetMocksForSlashValidator( + ctx, + mocks, + validator, + consAddr, + []stakingtypes.UnbondingDelegation{}, + []stakingtypes.Redelegation{}, + sdk.NewInt(2), + slashFraction, + int64(1), + int64(0), + int64(1), + )..., + ) + + gc = append( + gc, mocks.MockStakingKeeper.EXPECT().Jail( ctx, providerConsAddr.ToSdkConsAddr()). Times(1), @@ -129,22 +191,29 @@ func TestJailAndTombstoneValidator(t *testing.T) { mocks.MockSlashingKeeper.EXPECT().Tombstone( ctx, providerConsAddr.ToSdkConsAddr()). Times(1), - } + ) + return gc }, + false, }, } 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.JailAndTombstoneValidator(ctx, tc.provAddr) - - ctrl.Finish() + t.Run(tc.name, func(t *testing.T) { + providerKeeper, ctx, _, 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 + err := providerKeeper.PunishValidator(ctx, tc.provAddr) + if tc.expErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) } } @@ -357,26 +426,16 @@ func TestSlashValidator(t *testing.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()), - MinSelfDelegation: math.OneInt(), - UnbondingOnHoldRefCount: 0, - } + + validator, err := stakingtypes.NewValidator( + sdk.ValAddress(pubKey.Address().Bytes()), + pubKey, + stakingtypes.NewDescription("", "", "", "", ""), + ) + require.NoError(t, err) + validator.Status = stakingtypes.Bonded 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 @@ -400,86 +459,20 @@ func TestSlashValidator(t *testing.T) { 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) math.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) math.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(). - SlashWithInfractionReason(ctx, consAddr, expectedInfractionHeight, expectedSlashPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN). - 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) + gomock.InOrder( + testkeeper.GetMocksForSlashValidator( + ctx, + mocks, + validator, + consAddr, + undelegations, + redelegations, + powerReduction, + slashFraction, + currentPower, + expectedInfractionHeight, + expectedSlashPower, + )..., + ) + keeper.SlashValidator(ctx, validator) } From f886a7b3c8dad8f2add3722d63e2c99021b0e1fd Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 8 Nov 2023 15:07:02 +0100 Subject: [PATCH 55/82] update checkMisbehaviour tests --- tests/integration/misbehaviour.go | 210 +++++++++++------- x/ccv/provider/keeper/misbehaviour.go | 15 +- .../provider/keeper/punish_validator_test.go | 1 - 3 files changed, 140 insertions(+), 86 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index f531f79178..9164ef1eba 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -85,84 +85,77 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { altTime := s.providerCtx().BlockTime().Add(time.Minute) + // Get the consumer client validator set clientHeight := s.consumerChain.LastHeader.TrustedHeight clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) clientSigners := s.consumerChain.Signers - // Create a validator set subset + // Create a subset of the consumer client validator set altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:3]) - altSigners := make(map[string]tmtypes.PrivValidator, 1) + altSigners := make(map[string]tmtypes.PrivValidator, 3) 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 + // create a consumer client header + clientHeader := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + testCases := []struct { name string - misbehaviour *ibctmtypes.Misbehaviour + getMisbehaviour func() *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, - ), + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + Header1: &ibctmtypes.Header{}, + Header2: clientHeader, + } }, 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{}, + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + Header1: clientHeader, + 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, - ), + "light client attack - lunatic attack", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + // the resulting header contains invalid fields + // i.e. ValidatorsHash, NextValidatorsHash. + 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 @@ -170,20 +163,35 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { 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( + "light client attack - equivocation", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + // the resulting header contains a different BlockID + 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, + }, + { + "light client attack - amnesia", + func() *ibctmtypes.Misbehaviour { + // create a valid header with a different hash + // and commit round + amnesiaHeader := s.consumerChain.CreateTMClientHeader( s.consumerChain.ChainID, int64(clientHeight.RevisionHeight+1), clientHeight, @@ -192,11 +200,18 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { clientTMValset, clientTMValset, clientSigners, - ), + ) + amnesiaHeader.Commit.Round = 2 + + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: amnesiaHeader, + } }, - // Expect to get the entire valset since - // all validators double-signed - clientTMValset.Validators, + // Expect no validators + // since amnesia attacks are dropped + []*tmtypes.Validator{}, true, }, } @@ -205,24 +220,26 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { s.Run(tc.name, func() { byzantineValidators, err := s.providerApp.GetProviderKeeper().GetByzantineValidators( s.providerCtx(), - *tc.misbehaviour, + *tc.getMisbehaviour(), ) 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(tc.expByzantineValidators), len(byzantineValidators)) - s.Equal(len(h2Valset.Validators), len(byzantineValidators)) + // For both lunatic and equivocation attacks all the validators + // who signed the bad header (Header2) should be in returned in the evidence + if len(tc.expByzantineValidators) > 0 { + equivocatingVals := tc.getMisbehaviour().Header2.ValidatorSet + s.Equal(len(equivocatingVals.Validators), len(byzantineValidators)) - vs, err := tmtypes.ValidatorSetFromProto(tc.misbehaviour.Header2.ValidatorSet) - s.NoError(err) + vs, err := tmtypes.ValidatorSetFromProto(equivocatingVals) + s.NoError(err) - for _, v := range tc.expByzantineValidators { - idx, _ := vs.GetByAddress(v.Address) - s.True(idx >= 0) + for _, v := range tc.expByzantineValidators { + idx, _ := vs.GetByAddress(v.Address) + s.True(idx >= 0) + } } - } else { s.Error(err) } @@ -253,6 +270,12 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { 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()] + + // create a validator set with only one out of the four validators from the trusted validator set + altValset2 := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:1]) + altSigners2 := make(map[string]tmtypes.PrivValidator, 1) + altSigners2[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] + testCases := []struct { name string misbehaviour *ibctmtypes.Misbehaviour @@ -329,6 +352,33 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { }, false, }, + { + "invalid misbehaviour without enough voting power from trusted valset - 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, + altValset2, + altValset2, + clientTMValset, + altSigners2, + ), + }, + false, + }, { "valid misbehaviour - should pass", &ibctmtypes.Misbehaviour{ diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index c9a8a0849e..4abeb4f5c1 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -145,19 +145,24 @@ 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 ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go + // see CheckForMisbehaviour in ibc-go/blob/v7.3.0/modules/light-clients/07-tendermint/misbehaviour_handle.go#L73 if !misbehaviour.Header1.GetHeight().EQ(misbehaviour.Header2.GetHeight()) { return errorsmod.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 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 + // CheckMisbehaviour verifies that the headers have both he same block height and + // different blockID hashes ok := clientState.CheckForMisbehaviour(ctx, k.cdc, clientStore, &misbehaviour) if !ok { return errorsmod.Wrapf(ibcclienttypes.ErrInvalidMisbehaviour, "invalid misbehaviour for client-id: %s", misbehaviour.ClientId) } + // VerifyClientMessage calls verifyMisbehaviour which verifies that the headers in the misbehaviour + // are valid against their respective trusted consensus states and that trustLevel of the validator set signed their commit. + // see checkMisbehaviourHeader in ibc-go/blob/v7.3.0/modules/light-clients/07-tendermint/misbehaviour_handle.go#L126 + if err := clientState.VerifyClientMessage(ctx, k.cdc, clientStore, &misbehaviour); err != nil { + return err + } + return nil } diff --git a/x/ccv/provider/keeper/punish_validator_test.go b/x/ccv/provider/keeper/punish_validator_test.go index 5c13e3a274..5955041e09 100644 --- a/x/ccv/provider/keeper/punish_validator_test.go +++ b/x/ccv/provider/keeper/punish_validator_test.go @@ -143,7 +143,6 @@ func TestPunishValidator(t *testing.T) { Times(1), ) return gc - }, false, }, From a290d760c230bed13c82dbf6e6a0d319ec64dedc Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 8 Nov 2023 15:21:05 +0100 Subject: [PATCH 56/82] save --- x/ccv/provider/keeper/misbehaviour.go | 32 ++++++++++++----------- x/ccv/provider/keeper/punish_validator.go | 17 +++++++++--- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 4abeb4f5c1..d06a8099cb 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -16,7 +16,7 @@ import ( ) // HandleConsumerMisbehaviour checks if the given IBC misbehaviour corresponds to an equivocation light client attack, -// and in this case, slashes, jails, and tombstones the Byzantine validators +// and in this case, slashes, jails, and tombstones func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { logger := k.Logger(ctx) @@ -39,7 +39,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( @@ -47,29 +46,32 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty misbehaviour.Header1.Header.ChainID, types.NewConsumerConsAddress(sdk.ConsAddress(v.Address.Bytes())), ) - err := k.PunishValidator(ctx, providerAddr) + err := k.SlashValidator(ctx, providerAddr) if err != nil { - errors = append(errors, err) + 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 fail if the malicious + // validator is either or both !found, unbonded and tombstoned. + if err != nil { + 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", provAddrs, + "byzantine validators slashed, jailed and tombstoned", 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 0f8b9fd8e6..ae4c4760e2 100644 --- a/x/ccv/provider/keeper/punish_validator.go +++ b/x/ccv/provider/keeper/punish_validator.go @@ -14,7 +14,8 @@ import ( "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ) -func (k Keeper) PunishValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress) error { +// 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()) @@ -28,8 +29,18 @@ func (k Keeper) PunishValidator(ctx sdk.Context, providerAddr types.ProviderCons return fmt.Errorf("validator is tombstoned. provider consensus address: %s", providerAddr.String()) } - k.SlashValidator(ctx, validator) - k.JailAndTombstoneValidator(ctx, validator) + // jail validator if not already + if !validator.IsJailed() { + k.stakingKeeper.Jail(ctx, providerAddr.ToSdkConsAddr()) + } + + k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime) + + // Tombstone the validator so that we cannot slash the validator more than once + // 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 } From 7dc78bbd34b0c1f07b8cf532609c8712613cdd08 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 8 Nov 2023 17:31:05 +0100 Subject: [PATCH 57/82] update all misbehaviour memory tests --- tests/integration/double_vote.go | 48 +++- tests/integration/misbehaviour.go | 2 +- testutil/crypto/evidence.go | 4 +- testutil/integration/debug_test.go | 4 + x/ccv/provider/keeper/double_vote.go | 10 +- x/ccv/provider/keeper/double_vote_test.go | 51 ++--- x/ccv/provider/keeper/misbehaviour.go | 28 ++- x/ccv/provider/keeper/punish_validator.go | 70 +++--- .../provider/keeper/punish_validator_test.go | 206 +++++++++--------- 9 files changed, 237 insertions(+), 186 deletions(-) diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index c5309b17fb..4eba0fde3b 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -210,9 +210,9 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { } } -// TestHandleConsumerDoubleVotingSlashesUndelegations verifies that handling a successful double voting +// TestHandleConsumerDoubleVotingSlashesUndelegationsAndRelegations 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 +225,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 +268,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,12 +297,34 @@ 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) + + // 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 were 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) - _, shares, _ = delegateByIdx(s, delAddr, sdk.NewInt(50000000), idx) - _ = undelegate(s, delAddr, valAddr, shares) + // 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, @@ -307,12 +336,15 @@ 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()) s.Require().True(len(ubds.Entries) > 0) for _, unb := range ubds.Entries { initialBalance := sdk.NewDecFromInt(unb.InitialBalance) currentBalance := sdk.NewDecFromInt(unb.Balance) 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))) }) } diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 9164ef1eba..fbcdecc0cc 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -158,7 +158,7 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { } }, // Expect to get only the validators - // who signed both headers are returned + // who signed both headers altValset.Validators, true, }, diff --git a/testutil/crypto/evidence.go b/testutil/crypto/evidence.go index f646ea3d30..9bd96bdca5 100644 --- a/testutil/crypto/evidence.go +++ b/testutil/crypto/evidence.go @@ -63,16 +63,16 @@ 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/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index a2132266e7..13d006f494 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -281,6 +281,10 @@ func TestHandleConsumerDoubleVoting(t *testing.T) { runCCVTestByName(t, "TestHandleConsumerDoubleVoting") } +func TestHandleConsumerDoubleVotingSlashesUndelegationsAndRelegations(t *testing.T) { + runCCVTestByName(t, "TestHandleConsumerDoubleVotingSlashesUndelegationsAndRelegations") +} + // Throttle retry tests // diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 9a160d412a..62f3a54f2a 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -24,7 +24,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 } @@ -35,7 +35,10 @@ func (k Keeper) HandleConsumerDoubleVoting( types.NewConsumerConsAddress(sdk.ConsAddress(evidence.VoteA.ValidatorAddress.Bytes())), ) - if err := k.PunishValidator(ctx, providerAddr); err != nil { + if err := k.SlashValidator(ctx, providerAddr); err != nil { + return err + } + if err := k.JailAndTombstoneValidator(ctx, providerAddr); err != nil { return err } @@ -50,7 +53,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, @@ -71,7 +73,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/double_vote_test.go b/x/ccv/provider/keeper/double_vote_test.go index 976ddeeff3..05ab238ccc 100644 --- a/x/ccv/provider/keeper/double_vote_test.go +++ b/x/ccv/provider/keeper/double_vote_test.go @@ -47,6 +47,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{ @@ -56,6 +80,7 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { ctx.BlockTime(), valSet, signer1, + signer2, chainID, ), testutil.MakeAndSignVoteWithForgedValAddress( @@ -64,6 +89,7 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { ctx.BlockTime(), valSet, signer1, + signer2, chainID, ), }, @@ -215,30 +241,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{ @@ -291,7 +293,6 @@ func TestVerifyDoubleVotingEvidence(t *testing.T) { for _, tc := range testCases { err = keeper.VerifyDoubleVotingEvidence( - ctx, tmtypes.DuplicateVoteEvidence{ VoteA: tc.votes[0], VoteB: tc.votes[1], diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index d06a8099cb..a4ca389cc5 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -1,6 +1,7 @@ package keeper import ( + "bytes" "fmt" ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" @@ -78,18 +79,26 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty // 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) { +func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (validators []*tmtypes.Validator, err error) { // construct the trusted and conflicted light blocks lightBlock1, err := headerToLightBlock(*misbehaviour.Header1) if err != nil { - return nil, err + return } lightBlock2, err := headerToLightBlock(*misbehaviour.Header2) if err != nil { - return nil, err + return } - var validators []*tmtypes.Validator + // Check if the misbehaviour corresponds to an Amnesia attack, + // meaning that the conflicting headers have both valid state transitions + // and different commit rounds. In this case, we return no validators as + // we can't identify the byzantine validators. + // + // Note that we cannot differentiate which of the headers is trusted or malicious, + if !headersStateTransitionsAreConflicting(*lightBlock1.Header, *lightBlock2.Header) && lightBlock1.Commit.Round != lightBlock2.Commit.Round { + return + } // compare the signatures of the headers // and return the intersection of validators who signed both @@ -168,3 +177,14 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe return nil } + +// Check if the given block headers have conflicting state transitions. +// Note that this method was copied from ConflictingHeaderIsInvalid in CometBFT, +// see https://github.com/cometbft/cometbft/blob/v0.34.27/types/evidence.go#L285 +func headersStateTransitionsAreConflicting(h1, h2 tmtypes.Header) bool { + return !bytes.Equal(h1.ValidatorsHash, h2.ValidatorsHash) || + !bytes.Equal(h1.NextValidatorsHash, h2.NextValidatorsHash) || + !bytes.Equal(h1.ConsensusHash, h2.ConsensusHash) || + !bytes.Equal(h1.AppHash, h2.AppHash) || + !bytes.Equal(h1.LastResultsHash, h2.LastResultsHash) +} diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go index ae4c4760e2..f991c7cc5f 100644 --- a/x/ccv/provider/keeper/punish_validator.go +++ b/x/ccv/provider/keeper/punish_validator.go @@ -45,44 +45,6 @@ func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.Pr return nil } -// JailAndTombstoneValidator jails and tombstones the validator with the given provider consensus address -func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, validator stakingtypes.Validator) { - consAdrr, err := validator.GetConsAddr() - if err != nil { - panic(err) - } - - // jail validator if not already - if !validator.IsJailed() { - k.stakingKeeper.Jail(ctx, consAdrr) - } - - k.slashingKeeper.JailUntil(ctx, consAdrr, evidencetypes.DoubleSignJailEndTime) - - // Tombstone the validator so that we cannot slash the validator more than once - // 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, consAdrr) -} - -// SlashValidator slashes the given validator -func (k Keeper) SlashValidator(ctx sdk.Context, validator stakingtypes.Validator) { - 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) - - consAdrr, err := validator.GetConsAddr() - if err != nil { - panic(err) - } - - k.stakingKeeper.SlashWithInfractionReason(ctx, consAdrr, 0, totalPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN) -} - // 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. @@ -113,3 +75,35 @@ func (k Keeper) ComputePowerToSlash(ctx sdk.Context, validator stakingtypes.Vali return power + undelegationsAndRedelegationsInPower } + +// SlashValidator slashes validator with given provider Address +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) + + consAdrr, err := validator.GetConsAddr() + if err != nil { + panic(err) + } + + k.stakingKeeper.SlashWithInfractionReason(ctx, consAdrr, 0, totalPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN) + + return nil +} diff --git a/x/ccv/provider/keeper/punish_validator_test.go b/x/ccv/provider/keeper/punish_validator_test.go index 5955041e09..8834d0b464 100644 --- a/x/ccv/provider/keeper/punish_validator_test.go +++ b/x/ccv/provider/keeper/punish_validator_test.go @@ -17,36 +17,19 @@ import ( tmtypes "github.com/cometbft/cometbft/types" + cryptotestutil "github.com/cosmos/interchain-security/v3/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ) // 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 TestPunishValidator(t *testing.T) { - pubKey, _ := cryptocodec.FromTmPubKeyInterface(tmtypes.NewMockPV().PrivKey.PubKey()) - - // manually build a validator instead of using `stakingtypes.NewValidator` - // to guarantee that the validator is bonded is jailed for testing - - validator, err := stakingtypes.NewValidator( - sdk.ValAddress(pubKey.Address().Bytes()), - pubKey, - stakingtypes.NewDescription("", "", "", "", ""), - ) - require.NoError(t, err) - - slashFraction, _ := sdk.NewDecFromStr("0.5") - consAddr, _ := validator.GetConsAddr() - providerConsAddr := types.NewProviderConsAddress(consAddr) - - fmt.Println(validator.GetOperator()) - +func TestJailAndTombstoneValidator(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 - expErr bool }{ { "unfound validator", @@ -63,7 +46,6 @@ func TestPunishValidator(t *testing.T) { ).Times(1), } }, - true, }, { "unbonded validator", @@ -79,7 +61,6 @@ func TestPunishValidator(t *testing.T) { ).Times(1), } }, - true, }, { "tombstoned validator", @@ -98,7 +79,6 @@ func TestPunishValidator(t *testing.T) { ).Times(1), } }, - true, }, { "jailed validator", @@ -106,45 +86,23 @@ func TestPunishValidator(t *testing.T) { func(ctx sdk.Context, mocks testkeeper.MockedKeepers, provAddr types.ProviderConsAddress, ) []*gomock.Call { - validator.Jailed = true - validator.Status = stakingtypes.Unbonding - gc := append([]*gomock.Call{ + return []*gomock.Call{ mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( ctx, providerConsAddr.ToSdkConsAddr()).Return( - validator, true, + stakingtypes.Validator{Jailed: true}, true, ).Times(1), mocks.MockSlashingKeeper.EXPECT().IsTombstoned( ctx, providerConsAddr.ToSdkConsAddr()).Return( false, ).Times(1), - }, - testkeeper.GetMocksForSlashValidator( - ctx, - mocks, - validator, - consAddr, - []stakingtypes.UnbondingDelegation{}, - []stakingtypes.Redelegation{}, - sdk.NewInt(2), - slashFraction, - int64(1), - int64(0), - int64(1), - )..., - ) - - gc = append( - gc, mocks.MockSlashingKeeper.EXPECT().JailUntil( ctx, providerConsAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime). Times(1), mocks.MockSlashingKeeper.EXPECT().Tombstone( ctx, providerConsAddr.ToSdkConsAddr()). Times(1), - ) - return gc + } }, - false, }, { "bonded validator", @@ -152,35 +110,15 @@ func TestPunishValidator(t *testing.T) { func(ctx sdk.Context, mocks testkeeper.MockedKeepers, provAddr types.ProviderConsAddress, ) []*gomock.Call { - validator.Jailed = false - validator.Status = stakingtypes.Bonded - gc := append([]*gomock.Call{ + return []*gomock.Call{ mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( ctx, providerConsAddr.ToSdkConsAddr()).Return( - validator, true, + stakingtypes.Validator{Status: stakingtypes.Bonded}, true, ).Times(1), mocks.MockSlashingKeeper.EXPECT().IsTombstoned( ctx, providerConsAddr.ToSdkConsAddr()).Return( false, ).Times(1), - }, - testkeeper.GetMocksForSlashValidator( - ctx, - mocks, - validator, - consAddr, - []stakingtypes.UnbondingDelegation{}, - []stakingtypes.Redelegation{}, - sdk.NewInt(2), - slashFraction, - int64(1), - int64(0), - int64(1), - )..., - ) - - gc = append( - gc, mocks.MockStakingKeeper.EXPECT().Jail( ctx, providerConsAddr.ToSdkConsAddr()). Times(1), @@ -190,29 +128,22 @@ func TestPunishValidator(t *testing.T) { mocks.MockSlashingKeeper.EXPECT().Tombstone( ctx, providerConsAddr.ToSdkConsAddr()). Times(1), - ) - return gc + } }, - false, }, } for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - providerKeeper, ctx, _, 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 - err := providerKeeper.PunishValidator(ctx, tc.provAddr) - if tc.expErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) + 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.JailAndTombstoneValidator(ctx, tc.provAddr) + + ctrl.Finish() } } @@ -435,6 +366,7 @@ func TestSlashValidator(t *testing.T) { validator.Status = stakingtypes.Bonded 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 @@ -458,20 +390,86 @@ func TestSlashValidator(t *testing.T) { expectedInfractionHeight := int64(0) expectedSlashPower := int64(3750) - gomock.InOrder( - testkeeper.GetMocksForSlashValidator( - ctx, - mocks, - validator, - consAddr, - undelegations, - redelegations, - powerReduction, - slashFraction, - currentPower, - expectedInfractionHeight, - expectedSlashPower, - )..., - ) - keeper.SlashValidator(ctx, validator) + 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(). + SlashWithInfractionReason(ctx, consAddr, expectedInfractionHeight, expectedSlashPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN). + 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) } From ecd796ffbf6e2c7f935ae54558dcc91d393bd206 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 9 Nov 2023 09:12:00 +0100 Subject: [PATCH 58/82] fix misb verify valset sigs test --- tests/integration/misbehaviour.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index fbcdecc0cc..62e0d9c8c1 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -368,7 +368,7 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { ), Header2: s.consumerChain.CreateTMClientHeader( s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+2), + int64(clientHeight.RevisionHeight+1), clientHeight, headerTs, altValset2, From 6769af572a80110b30a20fff4c045327ef3528c7 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 9 Nov 2023 09:16:02 +0100 Subject: [PATCH 59/82] revert misb check fix for ibc 7 --- tests/integration/misbehaviour.go | 57 +-------------------------- x/ccv/provider/keeper/misbehaviour.go | 10 +---- 2 files changed, 3 insertions(+), 64 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 62e0d9c8c1..0738d7e819 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -352,33 +352,7 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { }, false, }, - { - "invalid misbehaviour without enough voting power from trusted valset - 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+1), - clientHeight, - headerTs, - altValset2, - altValset2, - clientTMValset, - altSigners2, - ), - }, - false, - }, + // TODO: Add valset signature test case here { "valid misbehaviour - should pass", &ibctmtypes.Misbehaviour{ @@ -407,35 +381,6 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { }, 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 { diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index a4ca389cc5..fb692efafb 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -166,15 +166,9 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe ok := clientState.CheckForMisbehaviour(ctx, k.cdc, clientStore, &misbehaviour) if !ok { return errorsmod.Wrapf(ibcclienttypes.ErrInvalidMisbehaviour, "invalid misbehaviour for client-id: %s", misbehaviour.ClientId) - } - - // VerifyClientMessage calls verifyMisbehaviour which verifies that the headers in the misbehaviour - // are valid against their respective trusted consensus states and that trustLevel of the validator set signed their commit. - // see checkMisbehaviourHeader in ibc-go/blob/v7.3.0/modules/light-clients/07-tendermint/misbehaviour_handle.go#L126 - if err := clientState.VerifyClientMessage(ctx, k.cdc, clientStore, &misbehaviour); err != nil { - return err - } + + // TODO check misb valset signatures here return nil } From e070f3b1a48e5c3755f7b4346fd5943f10c3200b Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 9 Nov 2023 09:18:09 +0100 Subject: [PATCH 60/82] nit fix --- x/ccv/provider/keeper/misbehaviour.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index fb692efafb..51bfd5749a 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -166,7 +166,7 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe ok := clientState.CheckForMisbehaviour(ctx, k.cdc, clientStore, &misbehaviour) if !ok { return errorsmod.Wrapf(ibcclienttypes.ErrInvalidMisbehaviour, "invalid misbehaviour for client-id: %s", misbehaviour.ClientId) - + } // TODO check misb valset signatures here return nil From 80e9d2589e1b62ca14191ad9789e25665ebdb4e7 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 9 Nov 2023 10:30:55 +0100 Subject: [PATCH 61/82] fix nit --- tests/integration/misbehaviour.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 0738d7e819..d841cf7087 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -271,11 +271,6 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] - // create a validator set with only one out of the four validators from the trusted validator set - altValset2 := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:1]) - altSigners2 := make(map[string]tmtypes.PrivValidator, 1) - altSigners2[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] - testCases := []struct { name string misbehaviour *ibctmtypes.Misbehaviour From 310788e053a2b9f88f70bff46e266f7eb1515609 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 6 Nov 2023 16:10:41 +0100 Subject: [PATCH 62/82] fix: add equivocation proposal message back in protos (#1394) * add depreacted equiv prop msg def * add codec and content for equivo proposal msg * doc * proto --- .../ccv/provider/v1/provider.proto | 17 + x/ccv/provider/types/codec.go | 14 +- x/ccv/provider/types/proposal.go | 40 +- x/ccv/provider/types/provider.pb.go | 564 +++++++++++++----- x/ccv/provider/types/tx.pb.go | 3 +- 5 files changed, 496 insertions(+), 142 deletions(-) diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index 229c4af0b3..d7615137a1 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -12,6 +12,7 @@ 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"; // @@ -102,6 +103,22 @@ message ConsumerRemovalProposal { [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; } +// EquivocationProposal is a governance proposal on the provider chain to +// punish a validator for equivocation on a consumer chain. +// +// This type is only used internally to the consumer CCV module. +// WARNING: This message is deprecated now that equivocations can be submitted +// and verified automatically on the provider. (see SubmitConsumerDoubleVoting in proto/interchain-security/ccv/provider/v1/tx.proto). +message EquivocationProposal { + option deprecated = true; + // 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/x/ccv/provider/types/codec.go b/x/ccv/provider/types/codec.go index 59f3d51ee6..d1b9203efb 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -27,20 +27,22 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { (*sdk.Msg)(nil), &MsgAssignConsumerKey{}, ) + registry.RegisterImplementations( + (*govv1beta1.Content)(nil), + &EquivocationProposal{}, + ) + registry.RegisterImplementations( + (*govv1beta1.Content)(nil), + &ChangeRewardDenomsProposal{}, + ) registry.RegisterImplementations( (*sdk.Msg)(nil), &MsgSubmitConsumerMisbehaviour{}, ) - registry.RegisterImplementations( (*sdk.Msg)(nil), &MsgSubmitConsumerDoubleVoting{}, ) - registry.RegisterImplementations( - (*govv1beta1.Content)(nil), - &ChangeRewardDenomsProposal{}, - ) - msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/ccv/provider/types/proposal.go b/x/ccv/provider/types/proposal.go index fb954d83aa..aa15fffc81 100644 --- a/x/ccv/provider/types/proposal.go +++ b/x/ccv/provider/types/proposal.go @@ -1,6 +1,7 @@ package types import ( + "errors" "fmt" "strings" time "time" @@ -8,16 +9,17 @@ import ( clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" ) const ( ProposalTypeConsumerAddition = "ConsumerAddition" ProposalTypeConsumerRemoval = "ConsumerRemoval" + ProposalTypeEquivocation = "Equivocation" ProposalTypeChangeRewardDenoms = "ChangeRewardDenoms" ) @@ -25,12 +27,14 @@ var ( _ govv1beta1.Content = &ConsumerAdditionProposal{} _ govv1beta1.Content = &ConsumerRemovalProposal{} _ govv1beta1.Content = &ChangeRewardDenomsProposal{} + _ govv1beta1.Content = &EquivocationProposal{} ) func init() { govv1beta1.RegisterProposalType(ProposalTypeConsumerAddition) govv1beta1.RegisterProposalType(ProposalTypeConsumerRemoval) govv1beta1.RegisterProposalType(ProposalTypeChangeRewardDenoms) + govv1beta1.RegisterProposalType(ProposalTypeEquivocation) } // NewConsumerAdditionProposal creates a new consumer addition proposal. @@ -198,6 +202,40 @@ func (sccp *ConsumerRemovalProposal) ValidateBasic() error { return nil } +// NewEquivocationProposal creates a new equivocation proposal. +// [DEPRECATED]: do not use +func NewEquivocationProposal(title, description string, equivocations []*evidencetypes.Equivocation) govv1beta1.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 := govv1beta1.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, ) govv1beta1.Content { diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index 3bdf4a537e..5dc47420d0 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -6,13 +6,14 @@ package types import ( fmt "fmt" crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - types1 "github.com/cosmos/cosmos-sdk/types" + types2 "github.com/cosmos/cosmos-sdk/types" + types1 "github.com/cosmos/cosmos-sdk/x/evidence/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" types "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" _07_tendermint "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" - types2 "github.com/cosmos/interchain-security/v3/x/ccv/types" + types3 "github.com/cosmos/interchain-security/v3/x/ccv/types" _ "google.golang.org/protobuf/types/known/durationpb" _ "google.golang.org/protobuf/types/known/timestamppb" io "io" @@ -200,6 +201,77 @@ func (m *ConsumerRemovalProposal) GetStopTime() time.Time { return time.Time{} } +// EquivocationProposal is a governance proposal on the provider chain to +// punish a validator for equivocation on a consumer chain. +// +// This type is only used internally to the consumer CCV module. +// WARNING: This message is deprecated now that equivocations can be submitted +// and verified automatically on the provider. (see SubmitConsumerDoubleVoting in proto/interchain-security/ccv/provider/v1/tx.proto). +// +// Deprecated: Do not use. +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 { @@ -217,7 +289,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{2} + return fileDescriptor_f22ec409a72b7b72, []int{3} } func (m *ChangeRewardDenomsProposal) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -298,7 +370,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) @@ -381,14 +453,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 types1.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{4} + return fileDescriptor_f22ec409a72b7b72, []int{5} } func (m *Params) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -473,11 +545,11 @@ func (m *Params) GetMaxThrottledPackets() int64 { return 0 } -func (m *Params) GetConsumerRewardDenomRegistrationFee() types1.Coin { +func (m *Params) GetConsumerRewardDenomRegistrationFee() types2.Coin { if m != nil { return m.ConsumerRewardDenomRegistrationFee } - return types1.Coin{} + return types2.Coin{} } // SlashAcks contains cons addresses of consumer chain validators @@ -490,7 +562,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{5} + return fileDescriptor_f22ec409a72b7b72, []int{6} } func (m *SlashAcks) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -537,7 +609,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{6} + return fileDescriptor_f22ec409a72b7b72, []int{7} } func (m *ConsumerAdditionProposals) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -584,7 +656,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{7} + return fileDescriptor_f22ec409a72b7b72, []int{8} } func (m *ConsumerRemovalProposals) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -629,7 +701,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{8} + return fileDescriptor_f22ec409a72b7b72, []int{9} } func (m *AddressList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -674,7 +746,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{9} + return fileDescriptor_f22ec409a72b7b72, []int{10} } func (m *ChannelToChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -728,7 +800,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{10} + return fileDescriptor_f22ec409a72b7b72, []int{11} } func (m *VscUnbondingOps) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -783,7 +855,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{11} + return fileDescriptor_f22ec409a72b7b72, []int{12} } func (m *UnbondingOp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -835,7 +907,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{12} + return fileDescriptor_f22ec409a72b7b72, []int{13} } func (m *InitTimeoutTimestamp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -887,7 +959,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{13} + return fileDescriptor_f22ec409a72b7b72, []int{14} } func (m *VscSendTimestamp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -932,14 +1004,14 @@ func (m *VscSendTimestamp) GetTimestamp() time.Time { // ValidatorSetChangePackets is a pb list of ccv.ValidatorSetChangePacketData. type ValidatorSetChangePackets struct { - List []types2.ValidatorSetChangePacketData `protobuf:"bytes,1,rep,name=list,proto3" json:"list"` + List []types3.ValidatorSetChangePacketData `protobuf:"bytes,1,rep,name=list,proto3" json:"list"` } func (m *ValidatorSetChangePackets) Reset() { *m = ValidatorSetChangePackets{} } func (m *ValidatorSetChangePackets) String() string { return proto.CompactTextString(m) } func (*ValidatorSetChangePackets) ProtoMessage() {} func (*ValidatorSetChangePackets) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{14} + return fileDescriptor_f22ec409a72b7b72, []int{15} } func (m *ValidatorSetChangePackets) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -968,7 +1040,7 @@ func (m *ValidatorSetChangePackets) XXX_DiscardUnknown() { var xxx_messageInfo_ValidatorSetChangePackets proto.InternalMessageInfo -func (m *ValidatorSetChangePackets) GetList() []types2.ValidatorSetChangePacketData { +func (m *ValidatorSetChangePackets) GetList() []types3.ValidatorSetChangePacketData { if m != nil { return m.List } @@ -985,7 +1057,7 @@ func (m *MaturedUnbondingOps) Reset() { *m = MaturedUnbondingOps{} } func (m *MaturedUnbondingOps) String() string { return proto.CompactTextString(m) } func (*MaturedUnbondingOps) ProtoMessage() {} func (*MaturedUnbondingOps) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{15} + return fileDescriptor_f22ec409a72b7b72, []int{16} } func (m *MaturedUnbondingOps) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1031,7 +1103,7 @@ func (m *ExportedVscSendTimestamp) Reset() { *m = ExportedVscSendTimesta func (m *ExportedVscSendTimestamp) String() string { return proto.CompactTextString(m) } func (*ExportedVscSendTimestamp) ProtoMessage() {} func (*ExportedVscSendTimestamp) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{16} + return fileDescriptor_f22ec409a72b7b72, []int{17} } func (m *ExportedVscSendTimestamp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1084,7 +1156,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{17} + return fileDescriptor_f22ec409a72b7b72, []int{18} } func (m *KeyAssignmentReplacement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1147,7 +1219,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{18} + return fileDescriptor_f22ec409a72b7b72, []int{19} } func (m *ValidatorConsumerPubKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1210,7 +1282,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{19} + return fileDescriptor_f22ec409a72b7b72, []int{20} } func (m *ValidatorByConsumerAddr) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1272,7 +1344,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{20} + return fileDescriptor_f22ec409a72b7b72, []int{21} } func (m *ConsumerAddrsToPrune) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1325,6 +1397,7 @@ 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") @@ -1351,112 +1424,115 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1668 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcb, 0x6f, 0x1b, 0xc7, - 0x19, 0xd7, 0x8a, 0x94, 0x2c, 0x7e, 0xd4, 0xcb, 0x2b, 0x3b, 0xa6, 0x5c, 0x95, 0x92, 0x37, 0x7d, - 0xa8, 0x08, 0xb2, 0x1b, 0xc9, 0x28, 0x10, 0x18, 0x0d, 0x02, 0x89, 0x4a, 0x62, 0xc5, 0x4d, 0xac, - 0xac, 0x54, 0x19, 0x6d, 0x0f, 0x8b, 0xe1, 0xcc, 0x98, 0x1c, 0x68, 0x77, 0x67, 0x33, 0x33, 0x5c, - 0x9b, 0x97, 0x9e, 0x7b, 0x4c, 0x6f, 0x41, 0x4f, 0x69, 0xff, 0x81, 0xfe, 0x1b, 0x39, 0xe6, 0xd8, - 0x53, 0x52, 0xd8, 0x87, 0x1e, 0xfa, 0x4f, 0x14, 0x33, 0xfb, 0x24, 0xf5, 0x28, 0x8d, 0xa0, 0xb7, - 0xd9, 0x6f, 0xbe, 0xef, 0xf7, 0xbd, 0xe6, 0x7b, 0x90, 0xb0, 0xcf, 0x62, 0x45, 0x05, 0x1e, 0x22, - 0x16, 0x07, 0x92, 0xe2, 0x91, 0x60, 0x6a, 0xec, 0x61, 0x9c, 0x7a, 0x89, 0xe0, 0x29, 0x23, 0x54, - 0x78, 0xe9, 0x5e, 0x79, 0x76, 0x13, 0xc1, 0x15, 0xb7, 0xdf, 0xbe, 0x42, 0xc6, 0xc5, 0x38, 0x75, - 0x4b, 0xbe, 0x74, 0xef, 0xfe, 0x7b, 0xd7, 0x01, 0xa7, 0x7b, 0x9e, 0x1c, 0x22, 0x41, 0x49, 0x80, - 0x79, 0x2c, 0x47, 0x51, 0x01, 0x7b, 0xff, 0xe7, 0x37, 0x48, 0xbc, 0x60, 0x82, 0xe6, 0x6c, 0x77, - 0x06, 0x7c, 0xc0, 0xcd, 0xd1, 0xd3, 0xa7, 0x9c, 0xba, 0x3d, 0xe0, 0x7c, 0x10, 0x52, 0xcf, 0x7c, - 0xf5, 0x47, 0xcf, 0x3d, 0xc5, 0x22, 0x2a, 0x15, 0x8a, 0x92, 0x9c, 0xa1, 0x3b, 0xcd, 0x40, 0x46, - 0x02, 0x29, 0xc6, 0xe3, 0x02, 0x80, 0xf5, 0xb1, 0x87, 0xb9, 0xa0, 0x1e, 0x0e, 0x19, 0x8d, 0x95, - 0xd6, 0x9a, 0x9d, 0x72, 0x06, 0x4f, 0x33, 0x84, 0x6c, 0x30, 0x54, 0x19, 0x59, 0x7a, 0x8a, 0xc6, - 0x84, 0x8a, 0x88, 0x65, 0xcc, 0xd5, 0x57, 0x2e, 0xb0, 0x55, 0xbb, 0xc7, 0x62, 0x9c, 0x28, 0xee, - 0x5d, 0xd0, 0xb1, 0x2c, 0xec, 0xc1, 0x5c, 0x46, 0x5c, 0x7a, 0x7d, 0x24, 0xa9, 0x97, 0xee, 0xf5, - 0xa9, 0x42, 0x7b, 0x1e, 0xe6, 0x2c, 0xb7, 0xc7, 0xf9, 0x61, 0x11, 0x3a, 0xbd, 0x3c, 0x40, 0x07, - 0x84, 0x30, 0x6d, 0xea, 0x89, 0xe0, 0x09, 0x97, 0x28, 0xb4, 0xef, 0xc0, 0x82, 0x62, 0x2a, 0xa4, - 0x1d, 0x6b, 0xc7, 0xda, 0x6d, 0xf9, 0xd9, 0x87, 0xbd, 0x03, 0x6d, 0x42, 0x25, 0x16, 0x2c, 0xd1, - 0xcc, 0x9d, 0x79, 0x73, 0x57, 0x27, 0xd9, 0x9b, 0xb0, 0x94, 0xc5, 0x97, 0x91, 0x4e, 0xc3, 0x5c, - 0xdf, 0x32, 0xdf, 0xc7, 0xc4, 0xfe, 0x04, 0x56, 0x59, 0xcc, 0x14, 0x43, 0x61, 0x30, 0xa4, 0xda, - 0xcb, 0x4e, 0x73, 0xc7, 0xda, 0x6d, 0xef, 0xdf, 0x77, 0x59, 0x1f, 0xbb, 0x3a, 0x30, 0x6e, 0x1e, - 0x8e, 0x74, 0xcf, 0x7d, 0x6c, 0x38, 0x0e, 0x9b, 0xdf, 0x7e, 0xbf, 0x3d, 0xe7, 0xaf, 0xe4, 0x72, - 0x19, 0xd1, 0x7e, 0x00, 0xcb, 0x03, 0x1a, 0x53, 0xc9, 0x64, 0x30, 0x44, 0x72, 0xd8, 0x59, 0xd8, - 0xb1, 0x76, 0x97, 0xfd, 0x76, 0x4e, 0x7b, 0x8c, 0xe4, 0xd0, 0xde, 0x86, 0x76, 0x9f, 0xc5, 0x48, - 0x8c, 0x33, 0x8e, 0x45, 0xc3, 0x01, 0x19, 0xc9, 0x30, 0xf4, 0x00, 0x64, 0x82, 0x5e, 0xc4, 0x81, - 0xce, 0x62, 0xe7, 0x56, 0x6e, 0x48, 0x96, 0x41, 0xb7, 0xc8, 0xa0, 0x7b, 0x56, 0xa4, 0xf8, 0x70, - 0x49, 0x1b, 0xf2, 0xd5, 0x0f, 0xdb, 0x96, 0xdf, 0x32, 0x72, 0xfa, 0xc6, 0xfe, 0x1c, 0xd6, 0x47, - 0x71, 0x9f, 0xc7, 0x84, 0xc5, 0x83, 0x20, 0xa1, 0x82, 0x71, 0xd2, 0x59, 0x32, 0x50, 0x9b, 0x97, - 0xa0, 0x8e, 0xf2, 0xc7, 0x90, 0x21, 0x7d, 0xad, 0x91, 0xd6, 0x4a, 0xe1, 0x13, 0x23, 0x6b, 0x7f, - 0x01, 0x36, 0xc6, 0xa9, 0x31, 0x89, 0x8f, 0x54, 0x81, 0xd8, 0x9a, 0x1d, 0x71, 0x1d, 0xe3, 0xf4, - 0x2c, 0x93, 0xce, 0x21, 0xff, 0x08, 0xf7, 0x94, 0x40, 0xb1, 0x7c, 0x4e, 0xc5, 0x34, 0x2e, 0xcc, - 0x8e, 0x7b, 0xb7, 0xc0, 0x98, 0x04, 0x7f, 0x0c, 0x3b, 0x45, 0x85, 0x05, 0x82, 0x12, 0x26, 0x95, - 0x60, 0xfd, 0x91, 0x96, 0x0d, 0x9e, 0x0b, 0x84, 0xcd, 0x1b, 0x69, 0x9b, 0x47, 0xd0, 0x2d, 0xf8, - 0xfc, 0x09, 0xb6, 0x8f, 0x73, 0x2e, 0xfb, 0x29, 0xfc, 0xac, 0x1f, 0x72, 0x7c, 0x21, 0xb5, 0x71, - 0xc1, 0x04, 0x92, 0x51, 0x1d, 0x31, 0x29, 0x35, 0xda, 0xf2, 0x8e, 0xb5, 0xdb, 0xf0, 0x1f, 0x64, - 0xbc, 0x27, 0x54, 0x1c, 0xd5, 0x38, 0xcf, 0x6a, 0x8c, 0xf6, 0xbb, 0x60, 0x0f, 0x99, 0x54, 0x5c, - 0x30, 0x8c, 0xc2, 0x80, 0xc6, 0x4a, 0x30, 0x2a, 0x3b, 0x2b, 0x46, 0xfc, 0x76, 0x75, 0xf3, 0x51, - 0x76, 0x61, 0x7f, 0x0a, 0x0f, 0xae, 0x55, 0x1a, 0xe0, 0x21, 0x8a, 0x63, 0x1a, 0x76, 0x56, 0x8d, - 0x2b, 0xdb, 0xe4, 0x1a, 0x9d, 0xbd, 0x8c, 0xed, 0xd1, 0xd2, 0x9f, 0xbf, 0xd9, 0x9e, 0xfb, 0xfa, - 0x9b, 0xed, 0x39, 0xe7, 0x1f, 0x16, 0xdc, 0xeb, 0x95, 0x8e, 0x47, 0x3c, 0x45, 0xe1, 0xff, 0xb3, - 0xc0, 0x0e, 0xa0, 0x25, 0x15, 0x4f, 0xb2, 0x27, 0xdd, 0x7c, 0x83, 0x27, 0xbd, 0xa4, 0xc5, 0xf4, - 0x85, 0xf3, 0x77, 0x0b, 0xee, 0x6b, 0x3f, 0x06, 0xd4, 0xa7, 0x2f, 0x90, 0x20, 0x47, 0x34, 0xe6, - 0x91, 0xfc, 0xd1, 0x46, 0x3b, 0xb0, 0x42, 0x0c, 0x52, 0xa0, 0x78, 0x80, 0x88, 0xb6, 0xbc, 0x91, - 0xf1, 0x68, 0xe2, 0x19, 0x3f, 0x20, 0xc4, 0xde, 0x85, 0xf5, 0x8a, 0x47, 0xe8, 0x68, 0x69, 0x27, - 0x34, 0xdb, 0x6a, 0xc1, 0x66, 0x62, 0x48, 0x9d, 0xff, 0x58, 0xb0, 0xfe, 0x49, 0xc8, 0xfb, 0x28, - 0x3c, 0x0d, 0x91, 0x1c, 0xea, 0x1c, 0x8e, 0xb5, 0xf3, 0x82, 0xe6, 0xc5, 0x63, 0xcc, 0x9b, 0xd9, - 0x79, 0x2d, 0x66, 0xca, 0xf9, 0x43, 0xb8, 0x5d, 0x3e, 0xe7, 0x32, 0xc6, 0xc6, 0x9b, 0xc3, 0x8d, - 0x57, 0xdf, 0x6f, 0xaf, 0x15, 0xa9, 0xec, 0x99, 0x78, 0x1f, 0xf9, 0x6b, 0x78, 0x82, 0x40, 0xec, - 0x2e, 0xb4, 0x59, 0x1f, 0x07, 0x92, 0x7e, 0x19, 0xc4, 0xa3, 0xc8, 0xa4, 0xa7, 0xe9, 0xb7, 0x58, - 0x1f, 0x9f, 0xd2, 0x2f, 0x3f, 0x1f, 0x45, 0xf6, 0x43, 0x78, 0xab, 0x18, 0x60, 0x41, 0x8a, 0x42, - 0x33, 0x9e, 0x74, 0x38, 0x84, 0xc9, 0xd6, 0xb2, 0xbf, 0x51, 0xdc, 0x9e, 0xa3, 0x50, 0x2b, 0x3b, - 0x20, 0x44, 0x38, 0xff, 0x5e, 0x80, 0xc5, 0x13, 0x24, 0x50, 0x24, 0xed, 0x33, 0x58, 0x53, 0x34, - 0x4a, 0x42, 0xa4, 0x68, 0x90, 0xb5, 0xca, 0xdc, 0xd3, 0x77, 0x4c, 0x0b, 0xad, 0x8f, 0x0e, 0xb7, - 0x36, 0x2c, 0xd2, 0x3d, 0xb7, 0x67, 0xa8, 0xa7, 0x0a, 0x29, 0xea, 0xaf, 0x16, 0x18, 0x19, 0xd1, - 0x7e, 0x1f, 0x3a, 0x4a, 0x8c, 0xa4, 0xaa, 0x9a, 0x58, 0x55, 0xbd, 0x59, 0x2e, 0xdf, 0x2a, 0xee, - 0xb3, 0xba, 0x2f, 0xab, 0xf6, 0xea, 0x7e, 0xd5, 0xf8, 0x31, 0xfd, 0xea, 0x14, 0x36, 0x74, 0xb3, - 0x9f, 0xc6, 0x6c, 0xce, 0x8e, 0x79, 0x5b, 0xcb, 0x4f, 0x82, 0x7e, 0x01, 0x76, 0x2a, 0xf1, 0x34, - 0xe6, 0xc2, 0x1b, 0xd8, 0x99, 0x4a, 0x3c, 0x09, 0x49, 0x60, 0x4b, 0xea, 0xc7, 0x17, 0x44, 0x54, - 0x99, 0xee, 0x97, 0x84, 0x34, 0x66, 0x72, 0x58, 0x80, 0x2f, 0xce, 0x0e, 0xbe, 0x69, 0x80, 0x3e, - 0xd3, 0x38, 0x7e, 0x01, 0x93, 0x6b, 0xe9, 0x41, 0xf7, 0x6a, 0x2d, 0x65, 0x82, 0x6e, 0x99, 0x04, - 0xfd, 0xe4, 0x0a, 0x88, 0x32, 0x4b, 0xfb, 0x70, 0x37, 0x42, 0x2f, 0x03, 0x35, 0x14, 0x5c, 0xa9, - 0x90, 0x92, 0x20, 0x41, 0xf8, 0x82, 0x2a, 0x69, 0x46, 0x55, 0xc3, 0xdf, 0x88, 0xd0, 0xcb, 0xb3, - 0xe2, 0xee, 0x24, 0xbb, 0xb2, 0x25, 0xfc, 0xa2, 0xd6, 0xd9, 0x75, 0x27, 0x08, 0x4c, 0x11, 0x06, - 0x82, 0x0e, 0x74, 0xfb, 0x43, 0x59, 0x93, 0xa7, 0xb4, 0x9c, 0x4e, 0xd9, 0xb2, 0xe1, 0xea, 0x65, - 0xc3, 0xcd, 0x97, 0x0d, 0xb7, 0xc7, 0x59, 0x9c, 0x8f, 0x70, 0xa7, 0x1a, 0x00, 0x65, 0x5f, 0xf1, - 0x6b, 0x58, 0x1f, 0x53, 0xea, 0xfc, 0x0a, 0x5a, 0xa6, 0xa0, 0x0f, 0xf0, 0x85, 0xb4, 0xb7, 0xa0, - 0xa5, 0x2b, 0x83, 0x4a, 0x49, 0x65, 0xc7, 0x32, 0x7d, 0xa0, 0x22, 0x38, 0x0a, 0x36, 0xaf, 0x5b, - 0x5d, 0xa4, 0xfd, 0x0c, 0x6e, 0x25, 0xd4, 0xcc, 0x55, 0x23, 0xd8, 0xde, 0xff, 0xc0, 0x9d, 0x61, - 0x9f, 0x74, 0xaf, 0x03, 0xf4, 0x0b, 0x34, 0x47, 0x54, 0x0b, 0xd3, 0x54, 0x3b, 0x97, 0xf6, 0xf9, - 0xb4, 0xd2, 0xdf, 0xbc, 0x91, 0xd2, 0x29, 0xbc, 0x4a, 0xe7, 0x3b, 0xd0, 0x3e, 0xc8, 0xdc, 0xfe, - 0x2d, 0x93, 0xea, 0x72, 0x58, 0x96, 0xeb, 0x61, 0xf9, 0x14, 0x56, 0xf3, 0x29, 0x74, 0xc6, 0x4d, - 0x53, 0xb2, 0x7f, 0x0a, 0x90, 0x8f, 0x2f, 0xdd, 0xcc, 0xb2, 0xb6, 0xdd, 0xca, 0x29, 0xc7, 0x64, - 0x62, 0x9a, 0xcc, 0x4f, 0x4c, 0x13, 0xc7, 0x87, 0xb5, 0x73, 0x89, 0x7f, 0x57, 0xac, 0x28, 0x4f, - 0x13, 0x69, 0xdf, 0x85, 0x45, 0x5d, 0x47, 0x39, 0x50, 0xd3, 0x5f, 0x48, 0x25, 0x3e, 0x36, 0x9d, - 0xbb, 0x5a, 0x83, 0x78, 0x12, 0x30, 0x22, 0x3b, 0xf3, 0x3b, 0x8d, 0xdd, 0xa6, 0xbf, 0x3a, 0xaa, - 0xc4, 0x8f, 0x89, 0x74, 0x7e, 0x0f, 0xed, 0x1a, 0xa0, 0xbd, 0x0a, 0xf3, 0x25, 0xd6, 0x3c, 0x23, - 0xf6, 0x23, 0xd8, 0xac, 0x80, 0x26, 0x5b, 0x71, 0x86, 0xd8, 0xf2, 0xef, 0x95, 0x0c, 0x13, 0xdd, - 0x58, 0x3a, 0x4f, 0xe1, 0xce, 0x71, 0x55, 0xf8, 0x65, 0xa3, 0x9f, 0xf0, 0xd0, 0x9a, 0x9c, 0x97, - 0x5b, 0xd0, 0x2a, 0x77, 0x78, 0xe3, 0x7d, 0xd3, 0xaf, 0x08, 0x4e, 0x04, 0xeb, 0xe7, 0x12, 0x9f, - 0xd2, 0x98, 0x54, 0x60, 0xd7, 0x04, 0xe0, 0x70, 0x1a, 0x68, 0xe6, 0x5d, 0xb2, 0x52, 0xc7, 0x61, - 0xf3, 0x1c, 0x85, 0x8c, 0x20, 0xc5, 0xc5, 0x29, 0x55, 0xd9, 0x10, 0x2e, 0xca, 0xd1, 0x87, 0x66, - 0xc8, 0xa4, 0xca, 0x5f, 0xd6, 0xfb, 0xd7, 0xbe, 0xac, 0x74, 0xcf, 0xbd, 0x0e, 0xe4, 0x08, 0x29, - 0x94, 0xd7, 0xa2, 0xc1, 0x72, 0x7e, 0x09, 0x1b, 0x9f, 0x21, 0x35, 0x12, 0x94, 0x4c, 0xe4, 0x78, - 0x1d, 0x1a, 0x3a, 0x7f, 0x96, 0xc9, 0x9f, 0x3e, 0xea, 0x9d, 0xa0, 0xf3, 0xd1, 0xcb, 0x84, 0x0b, - 0x45, 0xc9, 0xa5, 0x88, 0xdc, 0x10, 0xde, 0x0b, 0xd8, 0xd0, 0xc1, 0x92, 0x34, 0x26, 0x41, 0xe9, - 0x67, 0x96, 0xc7, 0xf6, 0xfe, 0xaf, 0x67, 0xaa, 0x8e, 0x69, 0x75, 0xb9, 0x03, 0xb7, 0xd3, 0x29, - 0xba, 0x74, 0xfe, 0x62, 0x41, 0xe7, 0x09, 0x1d, 0x1f, 0x48, 0xc9, 0x06, 0x71, 0x44, 0x63, 0xa5, - 0xfb, 0x20, 0xc2, 0x54, 0x1f, 0xed, 0xb7, 0x61, 0xa5, 0x9c, 0xbb, 0x66, 0xdc, 0x5a, 0x66, 0xdc, - 0x2e, 0x17, 0x44, 0x5d, 0x60, 0xf6, 0x23, 0x80, 0x44, 0xd0, 0x34, 0xc0, 0xc1, 0x05, 0x1d, 0xe7, - 0x59, 0xdc, 0xaa, 0x8f, 0xd1, 0xec, 0x17, 0x96, 0x7b, 0x32, 0xea, 0x87, 0x0c, 0x3f, 0xa1, 0x63, - 0x7f, 0x49, 0xf3, 0xf7, 0x9e, 0xd0, 0xb1, 0xde, 0x8b, 0x12, 0xfe, 0x82, 0x0a, 0x33, 0xfb, 0x1a, - 0x7e, 0xf6, 0xe1, 0xfc, 0xd5, 0x82, 0x7b, 0x65, 0x3a, 0x8a, 0xe7, 0x7a, 0x32, 0xea, 0x6b, 0x89, - 0x1b, 0xe2, 0x76, 0xc9, 0xda, 0xf9, 0x2b, 0xac, 0xfd, 0x10, 0x96, 0xcb, 0x02, 0xd1, 0xf6, 0x36, - 0x66, 0xb0, 0xb7, 0x5d, 0x48, 0x3c, 0xa1, 0x63, 0xe7, 0x4f, 0x35, 0xdb, 0x0e, 0xc7, 0xb5, 0xde, - 0x27, 0xfe, 0x87, 0x6d, 0xa5, 0xda, 0xba, 0x6d, 0xb8, 0x2e, 0x7f, 0xc9, 0x81, 0xc6, 0x65, 0x07, - 0x9c, 0xbf, 0x59, 0x70, 0xa7, 0xae, 0x55, 0x9e, 0xf1, 0x13, 0x31, 0x8a, 0xe9, 0x4d, 0xda, 0xab, - 0xf2, 0x9b, 0xaf, 0x97, 0xdf, 0x33, 0x58, 0x9d, 0x30, 0x4a, 0xe6, 0xd1, 0x78, 0x6f, 0xa6, 0x37, - 0x56, 0xeb, 0xae, 0xfe, 0x4a, 0xdd, 0x0f, 0x79, 0xf8, 0xec, 0xdb, 0x57, 0x5d, 0xeb, 0xbb, 0x57, - 0x5d, 0xeb, 0x5f, 0xaf, 0xba, 0xd6, 0x57, 0xaf, 0xbb, 0x73, 0xdf, 0xbd, 0xee, 0xce, 0xfd, 0xf3, - 0x75, 0x77, 0xee, 0x0f, 0x1f, 0x0c, 0x98, 0x1a, 0x8e, 0xfa, 0x2e, 0xe6, 0x91, 0x97, 0xff, 0xcc, - 0xae, 0x74, 0xbd, 0x5b, 0xfe, 0xb7, 0x90, 0x3e, 0xf4, 0x5e, 0x4e, 0xfe, 0xd7, 0xa1, 0xc6, 0x09, - 0x95, 0xfd, 0x45, 0xd3, 0x15, 0x1e, 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x67, 0x5a, 0xbb, - 0x1c, 0x11, 0x00, 0x00, + // 1727 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4b, 0x73, 0xdc, 0xc6, + 0x11, 0x26, 0xb8, 0x4b, 0x8a, 0xdb, 0xcb, 0x97, 0x40, 0xca, 0x02, 0x15, 0x66, 0x49, 0xc1, 0xb1, + 0xc3, 0x94, 0xcb, 0x58, 0x93, 0xaa, 0x54, 0xb9, 0x54, 0x71, 0xb9, 0xc8, 0xa5, 0x6c, 0xd1, 0x8c, + 0x2d, 0x1a, 0x64, 0xa8, 0x4a, 0x72, 0x40, 0xcd, 0x0e, 0x46, 0xbb, 0x53, 0x04, 0x30, 0xd0, 0xcc, + 0x00, 0xd2, 0x5e, 0x72, 0xce, 0xd1, 0xb9, 0xb9, 0x72, 0x89, 0x93, 0x3f, 0x90, 0xbf, 0xe1, 0xa3, + 0x8f, 0x39, 0xd9, 0x29, 0xe9, 0x90, 0x43, 0xfe, 0x44, 0x6a, 0x06, 0xcf, 0x5d, 0x3e, 0xb2, 0x2a, + 0x27, 0x37, 0x6c, 0x4f, 0xf7, 0xd7, 0x3d, 0xfd, 0xf8, 0x7a, 0x48, 0xd8, 0xa3, 0x91, 0x24, 0x1c, + 0x0f, 0x11, 0x8d, 0x3c, 0x41, 0x70, 0xc2, 0xa9, 0x1c, 0x75, 0x31, 0x4e, 0xbb, 0x31, 0x67, 0x29, + 0xf5, 0x09, 0xef, 0xa6, 0xbb, 0xe5, 0xb7, 0x13, 0x73, 0x26, 0x99, 0xf9, 0xf6, 0x15, 0x36, 0x0e, + 0xc6, 0xa9, 0x53, 0xea, 0xa5, 0xbb, 0xf7, 0x3e, 0xb8, 0x0e, 0x38, 0xdd, 0xed, 0x8a, 0x21, 0xe2, + 0xc4, 0xf7, 0x30, 0x8b, 0x44, 0x12, 0x16, 0xb0, 0xf7, 0xde, 0xb9, 0xc1, 0xe2, 0x05, 0xe5, 0x24, + 0x57, 0x5b, 0x1f, 0xb0, 0x01, 0xd3, 0x9f, 0x5d, 0xf5, 0x95, 0x4b, 0xb7, 0x06, 0x8c, 0x0d, 0x02, + 0xd2, 0xd5, 0xbf, 0xfa, 0xc9, 0xb3, 0xae, 0xa4, 0x21, 0x11, 0x12, 0x85, 0x71, 0xae, 0xd0, 0x99, + 0x54, 0xf0, 0x13, 0x8e, 0x24, 0x65, 0x51, 0x01, 0x40, 0xfb, 0xb8, 0x8b, 0x19, 0x27, 0x5d, 0x1c, + 0x50, 0x12, 0x49, 0xe5, 0x35, 0xfb, 0xca, 0x15, 0xba, 0x4a, 0x21, 0xa0, 0x83, 0xa1, 0xcc, 0xc4, + 0xa2, 0x2b, 0x49, 0xe4, 0x13, 0x1e, 0xd2, 0x4c, 0xb9, 0xfa, 0x95, 0x1b, 0x6c, 0xd6, 0xce, 0x31, + 0x1f, 0xc5, 0x92, 0x75, 0x2f, 0xc8, 0x48, 0xe4, 0xa7, 0xef, 0x62, 0x26, 0x42, 0x26, 0xba, 0x44, + 0x65, 0x2c, 0xc2, 0xa4, 0x9b, 0xee, 0xf6, 0x89, 0x44, 0xbb, 0xa5, 0xa0, 0x88, 0x3b, 0xd7, 0xeb, + 0x23, 0x51, 0xe9, 0x60, 0x46, 0xf3, 0xb8, 0xed, 0x1f, 0xe6, 0xc1, 0xea, 0xe5, 0x89, 0xdc, 0xf7, + 0x7d, 0xaa, 0xae, 0x74, 0xc2, 0x59, 0xcc, 0x04, 0x0a, 0xcc, 0x75, 0x98, 0x93, 0x54, 0x06, 0xc4, + 0x32, 0xb6, 0x8d, 0x9d, 0x96, 0x9b, 0xfd, 0x30, 0xb7, 0xa1, 0xed, 0x13, 0x81, 0x39, 0x8d, 0x95, + 0xb2, 0x35, 0xab, 0xcf, 0xea, 0x22, 0x73, 0x03, 0x16, 0xb2, 0x3a, 0x50, 0xdf, 0x6a, 0xe8, 0xe3, + 0x5b, 0xfa, 0xf7, 0x91, 0x6f, 0x7e, 0x0a, 0xcb, 0x34, 0xa2, 0x92, 0xa2, 0xc0, 0x1b, 0x12, 0x95, + 0x0d, 0xab, 0xb9, 0x6d, 0xec, 0xb4, 0xf7, 0xee, 0x39, 0xb4, 0x8f, 0x1d, 0x95, 0x40, 0x27, 0x4f, + 0x5b, 0xba, 0xeb, 0x3c, 0xd6, 0x1a, 0x07, 0xcd, 0x6f, 0xbf, 0xdf, 0x9a, 0x71, 0x97, 0x72, 0xbb, + 0x4c, 0x68, 0xde, 0x87, 0xc5, 0x01, 0x89, 0x88, 0xa0, 0xc2, 0x1b, 0x22, 0x31, 0xb4, 0xe6, 0xb6, + 0x8d, 0x9d, 0x45, 0xb7, 0x9d, 0xcb, 0x1e, 0x23, 0x31, 0x34, 0xb7, 0xa0, 0xdd, 0xa7, 0x11, 0xe2, + 0xa3, 0x4c, 0x63, 0x5e, 0x6b, 0x40, 0x26, 0xd2, 0x0a, 0x3d, 0x00, 0x11, 0xa3, 0x17, 0x91, 0xa7, + 0xaa, 0x6d, 0xdd, 0xca, 0x03, 0xc9, 0x2a, 0xed, 0x14, 0x95, 0x76, 0xce, 0x8a, 0x56, 0x38, 0x58, + 0x50, 0x81, 0x7c, 0xf5, 0xc3, 0x96, 0xe1, 0xb6, 0xb4, 0x9d, 0x3a, 0x31, 0xbf, 0x80, 0xd5, 0x24, + 0xea, 0xb3, 0xc8, 0xa7, 0xd1, 0xc0, 0x8b, 0x09, 0xa7, 0xcc, 0xb7, 0x16, 0x34, 0xd4, 0xc6, 0x25, + 0xa8, 0xc3, 0xbc, 0x69, 0x32, 0xa4, 0xaf, 0x15, 0xd2, 0x4a, 0x69, 0x7c, 0xa2, 0x6d, 0xcd, 0x2f, + 0xc1, 0xc4, 0x38, 0xd5, 0x21, 0xb1, 0x44, 0x16, 0x88, 0xad, 0xe9, 0x11, 0x57, 0x31, 0x4e, 0xcf, + 0x32, 0xeb, 0x1c, 0xf2, 0xf7, 0x70, 0x57, 0x72, 0x14, 0x89, 0x67, 0x84, 0x4f, 0xe2, 0xc2, 0xf4, + 0xb8, 0x77, 0x0a, 0x8c, 0x71, 0xf0, 0xc7, 0xb0, 0x5d, 0x4c, 0xa2, 0xc7, 0x89, 0x4f, 0x85, 0xe4, + 0xb4, 0x9f, 0x28, 0x5b, 0xef, 0x19, 0x47, 0x58, 0xf7, 0x48, 0x5b, 0x37, 0x41, 0xa7, 0xd0, 0x73, + 0xc7, 0xd4, 0x3e, 0xc9, 0xb5, 0xcc, 0x27, 0xf0, 0xb3, 0x7e, 0xc0, 0xf0, 0x85, 0x50, 0xc1, 0x79, + 0x63, 0x48, 0xda, 0x75, 0x48, 0x85, 0x50, 0x68, 0x8b, 0xdb, 0xc6, 0x4e, 0xc3, 0xbd, 0x9f, 0xe9, + 0x9e, 0x10, 0x7e, 0x58, 0xd3, 0x3c, 0xab, 0x29, 0x9a, 0xef, 0x83, 0x39, 0xa4, 0x42, 0x32, 0x4e, + 0x31, 0x0a, 0x3c, 0x12, 0x49, 0x4e, 0x89, 0xb0, 0x96, 0xb4, 0xf9, 0xed, 0xea, 0xe4, 0x51, 0x76, + 0x60, 0x7e, 0x06, 0xf7, 0xaf, 0x75, 0xea, 0xe1, 0x21, 0x8a, 0x22, 0x12, 0x58, 0xcb, 0xfa, 0x2a, + 0x5b, 0xfe, 0x35, 0x3e, 0x7b, 0x99, 0xda, 0xc3, 0x85, 0x3f, 0x7e, 0xb3, 0x35, 0xf3, 0xf5, 0x37, + 0x5b, 0x33, 0xf6, 0xdf, 0x0d, 0xb8, 0xdb, 0x2b, 0x2f, 0x1e, 0xb2, 0x14, 0x05, 0xff, 0xcf, 0x01, + 0xdb, 0x87, 0x96, 0x90, 0x2c, 0xce, 0x5a, 0xba, 0xf9, 0x06, 0x2d, 0xbd, 0xa0, 0xcc, 0xd4, 0x81, + 0xfd, 0x17, 0x03, 0xd6, 0x1f, 0x3d, 0x4f, 0x68, 0xca, 0x30, 0xfa, 0x9f, 0xf0, 0xc1, 0x31, 0x2c, + 0x91, 0x1a, 0x9e, 0xb0, 0x1a, 0xdb, 0x8d, 0x9d, 0xf6, 0xde, 0x3b, 0x4e, 0x46, 0x4e, 0x4e, 0xc9, + 0x59, 0x39, 0x41, 0x39, 0x75, 0xef, 0xee, 0xb8, 0xed, 0xc3, 0x59, 0xcb, 0xb0, 0xff, 0x66, 0xc0, + 0x3d, 0x95, 0xe9, 0x01, 0x71, 0xc9, 0x0b, 0xc4, 0xfd, 0x43, 0x12, 0xb1, 0x50, 0xfc, 0xe8, 0x38, + 0x6d, 0x58, 0xf2, 0x35, 0x92, 0x27, 0x99, 0x87, 0x7c, 0x5f, 0xc7, 0xa9, 0x75, 0x94, 0xf0, 0x8c, + 0xed, 0xfb, 0xbe, 0xb9, 0x03, 0xab, 0x95, 0x0e, 0x57, 0xf5, 0x54, 0x69, 0x56, 0x6a, 0xcb, 0x85, + 0x9a, 0xae, 0x32, 0xb1, 0xff, 0x6d, 0xc0, 0xea, 0xa7, 0x01, 0xeb, 0xa3, 0xe0, 0x34, 0x40, 0x62, + 0xa8, 0xba, 0x6c, 0xa4, 0xca, 0xc3, 0x49, 0x3e, 0xde, 0x3a, 0xbc, 0xa9, 0xcb, 0xa3, 0xcc, 0x34, + 0xe1, 0x7c, 0x0c, 0xb7, 0xcb, 0x81, 0x2b, 0xbb, 0x40, 0xdf, 0xe6, 0x60, 0xed, 0xd5, 0xf7, 0x5b, + 0x2b, 0x45, 0xb3, 0xf5, 0x74, 0x47, 0x1c, 0xba, 0x2b, 0x78, 0x4c, 0xe0, 0x9b, 0x1d, 0x68, 0xd3, + 0x3e, 0xf6, 0x04, 0x79, 0xee, 0x45, 0x49, 0xa8, 0x1b, 0xa8, 0xe9, 0xb6, 0x68, 0x1f, 0x9f, 0x92, + 0xe7, 0x5f, 0x24, 0xa1, 0xf9, 0x00, 0xde, 0x2a, 0x56, 0xb1, 0x97, 0xa2, 0x40, 0x2f, 0x5a, 0x95, + 0x0e, 0xae, 0xfb, 0x69, 0xd1, 0x5d, 0x2b, 0x4e, 0xcf, 0x51, 0xa0, 0x9c, 0xed, 0xfb, 0x3e, 0xb7, + 0xff, 0x35, 0x07, 0xf3, 0x27, 0x88, 0xa3, 0x50, 0x98, 0x67, 0xb0, 0x22, 0x49, 0x18, 0x07, 0x48, + 0x12, 0x2f, 0x23, 0xf3, 0xfc, 0xa6, 0xef, 0x69, 0x92, 0xaf, 0x2f, 0x41, 0xa7, 0xb6, 0xf6, 0xd2, + 0x5d, 0xa7, 0xa7, 0xa5, 0xa7, 0x12, 0x49, 0xe2, 0x2e, 0x17, 0x18, 0x99, 0xd0, 0xfc, 0x10, 0x2c, + 0xc9, 0x13, 0x21, 0x2b, 0x9a, 0xad, 0xf8, 0x25, 0xab, 0xe5, 0x5b, 0xc5, 0x79, 0xc6, 0x4c, 0x25, + 0xaf, 0x5c, 0xcd, 0xa8, 0x8d, 0x1f, 0xc3, 0xa8, 0xa7, 0xb0, 0xa6, 0xd6, 0xd1, 0x24, 0x66, 0x73, + 0x7a, 0xcc, 0xdb, 0xca, 0x7e, 0x1c, 0xf4, 0x4b, 0x30, 0x53, 0x81, 0x27, 0x31, 0xe7, 0xde, 0x20, + 0xce, 0x54, 0xe0, 0x71, 0x48, 0x1f, 0x36, 0x85, 0x6a, 0x3e, 0x2f, 0x24, 0x52, 0xf3, 0x73, 0x1c, + 0x90, 0x88, 0x8a, 0x61, 0x01, 0x3e, 0x3f, 0x3d, 0xf8, 0x86, 0x06, 0xfa, 0x5c, 0xe1, 0xb8, 0x05, + 0x4c, 0xee, 0xa5, 0x07, 0x9d, 0xab, 0xbd, 0x94, 0x05, 0xba, 0xa5, 0x0b, 0xf4, 0x93, 0x2b, 0x20, + 0xca, 0x2a, 0xed, 0xc1, 0x9d, 0x10, 0xbd, 0xf4, 0xe4, 0x90, 0x33, 0x29, 0x03, 0xe2, 0x7b, 0x31, + 0xc2, 0x17, 0x44, 0x0a, 0xbd, 0x4c, 0x1b, 0xee, 0x5a, 0x88, 0x5e, 0x9e, 0x15, 0x67, 0x27, 0xd9, + 0x91, 0x29, 0xe0, 0xdd, 0xda, 0xee, 0x51, 0x4c, 0xe0, 0xe9, 0x21, 0xf4, 0x38, 0x19, 0x28, 0x82, + 0x46, 0xd9, 0x1a, 0x22, 0xa4, 0xdc, 0x9f, 0x39, 0xe3, 0xa8, 0xe7, 0x50, 0xc9, 0x36, 0x3d, 0x46, + 0xa3, 0xfc, 0x91, 0x61, 0x57, 0x2b, 0xaa, 0xe4, 0x15, 0xb7, 0x86, 0xf5, 0x09, 0x21, 0xf6, 0x2f, + 0xa0, 0xa5, 0x07, 0x7a, 0x1f, 0x5f, 0x08, 0x73, 0x13, 0x5a, 0x6a, 0x32, 0x88, 0x10, 0x44, 0x58, + 0x86, 0xe6, 0x81, 0x4a, 0x60, 0x4b, 0xd8, 0xb8, 0xee, 0x71, 0x25, 0xcc, 0xa7, 0x70, 0x2b, 0x26, + 0x7a, 0xf3, 0x6b, 0xc3, 0xf6, 0xde, 0x47, 0xce, 0x14, 0x2f, 0x63, 0xe7, 0x3a, 0x40, 0xb7, 0x40, + 0xb3, 0x79, 0xf5, 0xa4, 0x9b, 0x58, 0x38, 0xc2, 0x3c, 0x9f, 0x74, 0xfa, 0xab, 0x37, 0x72, 0x3a, + 0x81, 0x57, 0xf9, 0x7c, 0x0f, 0xda, 0xfb, 0xd9, 0xb5, 0x7f, 0x4d, 0x85, 0xbc, 0x9c, 0x96, 0xc5, + 0x7a, 0x5a, 0x3e, 0x83, 0xe5, 0x7c, 0x4f, 0x9e, 0x31, 0x4d, 0x4a, 0xe6, 0x4f, 0x01, 0xf2, 0x05, + 0xab, 0xc8, 0x2c, 0xa3, 0xed, 0x56, 0x2e, 0x39, 0xf2, 0xc7, 0xf6, 0xdd, 0xec, 0xd8, 0xbe, 0xb3, + 0x5d, 0x58, 0x39, 0x17, 0xf8, 0x37, 0xc5, 0x23, 0xea, 0x49, 0x2c, 0xcc, 0x3b, 0x30, 0xaf, 0xe6, + 0x28, 0x07, 0x6a, 0xba, 0x73, 0xa9, 0xc0, 0x47, 0x9a, 0xb9, 0xab, 0x87, 0x1a, 0x8b, 0x3d, 0xea, + 0x0b, 0x6b, 0x76, 0xbb, 0xb1, 0xd3, 0x74, 0x97, 0x93, 0xca, 0xfc, 0xc8, 0x17, 0xf6, 0x6f, 0xa1, + 0x5d, 0x03, 0x34, 0x97, 0x61, 0xb6, 0xc4, 0x9a, 0xa5, 0xbe, 0xf9, 0x10, 0x36, 0x2a, 0xa0, 0x71, + 0x2a, 0xce, 0x10, 0x5b, 0xee, 0xdd, 0x52, 0x61, 0x8c, 0x8d, 0x85, 0xfd, 0x04, 0xd6, 0x8f, 0xaa, + 0xc1, 0x2f, 0x89, 0x7e, 0xec, 0x86, 0xc6, 0xf8, 0x46, 0xdf, 0x84, 0x56, 0xf9, 0xd7, 0x88, 0xbe, + 0x7d, 0xd3, 0xad, 0x04, 0x76, 0x08, 0xab, 0xe7, 0x02, 0x9f, 0x92, 0xc8, 0xaf, 0xc0, 0xae, 0x49, + 0xc0, 0xc1, 0x24, 0xd0, 0xd4, 0xaf, 0xdd, 0xca, 0x1d, 0x83, 0x8d, 0x73, 0x14, 0x50, 0x1f, 0x49, + 0xc6, 0x4f, 0x89, 0xcc, 0x96, 0x70, 0x31, 0x8e, 0x2e, 0x34, 0x03, 0x2a, 0x64, 0xde, 0x59, 0x1f, + 0x5e, 0xdb, 0x59, 0xe9, 0xae, 0x73, 0x1d, 0xc8, 0x21, 0x92, 0x28, 0x9f, 0x45, 0x8d, 0x65, 0xff, + 0x1c, 0xd6, 0x3e, 0x47, 0x32, 0xe1, 0xc4, 0x1f, 0xab, 0xf1, 0x2a, 0x34, 0x54, 0xfd, 0x0c, 0x5d, + 0x3f, 0xf5, 0xa9, 0xde, 0x04, 0xd6, 0xa3, 0x97, 0x31, 0xe3, 0x92, 0xf8, 0x97, 0x32, 0x72, 0x43, + 0x7a, 0x2f, 0x60, 0x4d, 0x25, 0x4b, 0x90, 0xc8, 0xf7, 0xca, 0x7b, 0x66, 0x75, 0x6c, 0xef, 0xfd, + 0x72, 0xaa, 0xe9, 0x98, 0x74, 0x97, 0x5f, 0xe0, 0x76, 0x3a, 0x21, 0x17, 0xf6, 0x9f, 0x0c, 0xb0, + 0x8e, 0xc9, 0x68, 0x5f, 0x08, 0x3a, 0x88, 0x42, 0x12, 0x49, 0xc5, 0x83, 0x08, 0x13, 0xf5, 0x69, + 0xbe, 0x0d, 0x4b, 0xe5, 0xde, 0xd5, 0xeb, 0xd6, 0xd0, 0xeb, 0x76, 0xb1, 0x10, 0xaa, 0x01, 0x33, + 0x1f, 0x02, 0xc4, 0x9c, 0xa4, 0x1e, 0xf6, 0x2e, 0xc8, 0x28, 0xaf, 0xe2, 0x66, 0x7d, 0x8d, 0x66, + 0x7f, 0x2b, 0x3a, 0x27, 0x49, 0x3f, 0xa0, 0xf8, 0x98, 0x8c, 0xdc, 0x05, 0xa5, 0xdf, 0x3b, 0x26, + 0x23, 0xf5, 0x2e, 0x8a, 0xd9, 0x0b, 0xc2, 0xf5, 0xee, 0x6b, 0xb8, 0xd9, 0x0f, 0xfb, 0xcf, 0x06, + 0xdc, 0x2d, 0xcb, 0x51, 0xb4, 0xeb, 0x49, 0xd2, 0x57, 0x16, 0x37, 0xe4, 0xed, 0x52, 0xb4, 0xb3, + 0x57, 0x44, 0xfb, 0x31, 0x2c, 0x96, 0x03, 0xa2, 0xe2, 0x6d, 0x4c, 0x11, 0x6f, 0xbb, 0xb0, 0x38, + 0x26, 0x23, 0xfb, 0x0f, 0xb5, 0xd8, 0x0e, 0x46, 0x35, 0xee, 0xe3, 0xff, 0x25, 0xb6, 0xd2, 0x6d, + 0x3d, 0x36, 0x5c, 0xb7, 0xbf, 0x74, 0x81, 0xc6, 0xe5, 0x0b, 0xd8, 0x7f, 0x35, 0x60, 0xbd, 0xee, + 0x55, 0x9c, 0xb1, 0x13, 0x9e, 0x44, 0xe4, 0x26, 0xef, 0xd5, 0xf8, 0xcd, 0xd6, 0xc7, 0xef, 0x29, + 0x2c, 0x8f, 0x05, 0x25, 0xf2, 0x6c, 0x7c, 0x30, 0x55, 0x8f, 0xd5, 0xd8, 0xd5, 0x5d, 0xaa, 0xdf, + 0x43, 0x1c, 0x3c, 0xfd, 0xf6, 0x55, 0xc7, 0xf8, 0xee, 0x55, 0xc7, 0xf8, 0xe7, 0xab, 0x8e, 0xf1, + 0xd5, 0xeb, 0xce, 0xcc, 0x77, 0xaf, 0x3b, 0x33, 0xff, 0x78, 0xdd, 0x99, 0xf9, 0xdd, 0x47, 0x03, + 0x2a, 0x87, 0x49, 0xdf, 0xc1, 0x2c, 0xec, 0xe6, 0xff, 0x08, 0xa8, 0x7c, 0xbd, 0x5f, 0xfe, 0x97, + 0x24, 0x7d, 0xd0, 0x7d, 0x39, 0xfe, 0x5f, 0x1b, 0x39, 0x8a, 0x89, 0xe8, 0xcf, 0x6b, 0x56, 0x78, + 0xf0, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2a, 0xef, 0x9e, 0x7d, 0xe6, 0x11, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -1635,6 +1711,57 @@ 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) @@ -2561,6 +2688,29 @@ 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 @@ -3588,6 +3738,154 @@ 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 @@ -5221,7 +5519,7 @@ func (m *ValidatorSetChangePackets) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.List = append(m.List, types2.ValidatorSetChangePacketData{}) + m.List = append(m.List, types3.ValidatorSetChangePacketData{}) if err := m.List[len(m.List)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index bf66515c9a..efd9d263f0 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -114,8 +114,7 @@ func (m *MsgAssignConsumerKeyResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgAssignConsumerKeyResponse proto.InternalMessageInfo // MsgSubmitConsumerMisbehaviour defines a message that reports a light client attack, -// -// also known as a misbehaviour, observed on a consumer chain +// also known as a misbehaviour, observed on a consumer chain type MsgSubmitConsumerMisbehaviour struct { Submitter string `protobuf:"bytes,1,opt,name=submitter,proto3" json:"submitter,omitempty"` // The Misbehaviour of the consumer chain wrapping From f2c1a3fb0789961d6723978130fe16d6c0133a08 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 13 Nov 2023 17:50:31 +0100 Subject: [PATCH 63/82] fix!: verify the signatures of byzantine validators in misbehaviour handling (#1422) * update byzantine validators extraction * nits * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity * last changes * udpdate changelog --------- Co-authored-by: insumity --- CHANGELOG.md | 4 +- tests/integration/misbehaviour.go | 131 +++++++++++++++++- testutil/crypto/evidence.go | 45 +++++- testutil/keeper/expectations.go | 4 +- x/ccv/provider/keeper/misbehaviour.go | 42 +++++- .../provider/keeper/punish_validator_test.go | 4 +- x/ccv/provider/types/proposal.go | 3 +- 7 files changed, 217 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b935231e16..5890240b97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ Add an entry to the unreleased provider section whenever merging a PR to main that is not targeted at a specific release. These entries will eventually be included in a provider release. -* (refactor) [#1294](https://github.com/cosmos/interchain-security/pull/1294) Remove the equivocation proposal handler. * (feature!) [#1280](https://github.com/cosmos/interchain-security/pull/1280) provider proposal for changing reward denoms * (feature!) [#1244](https://github.com/cosmos/interchain-security/pull/1244) Update the default consumer unbonding period to 2 weeks. * (deps) [#1259](https://github.com/cosmos/interchain-security/pull/1259) Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to [v0.47.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.5). @@ -12,6 +11,9 @@ Add an entry to the unreleased provider section whenever merging a PR to main th * (deps) [#1258](https://github.com/cosmos/interchain-security/pull/1258) Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to [v0.47.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.4). * (deps!) [#1196](https://github.com/cosmos/interchain-security/pull/1196) Bump [ibc-go](https://github.com/cosmos/ibc-go) to [v7.2.0](https://github.com/cosmos/ibc-go/releases/tag/v7.2.0). * `[x/ccv/provider]` (fix) [#1076](https://github.com/cosmos/interchain-security/pull/1076) Add `InitTimeoutTimestamps` and `ExportedVscSendTimestamps` to exported genesis. +* (fix!) [#1422](https://github.com/cosmos/interchain-security/pull/1422) Fix the misbehaviour handling by verifying the signatures of byzantine validators. + +## v2.2.0-provider-lsm ### 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). diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index d841cf7087..e12d12664c 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -9,6 +9,7 @@ import ( tmtypes "github.com/cometbft/cometbft/types" + testutil "github.com/cosmos/interchain-security/v3/testutil/crypto" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ) @@ -137,6 +138,119 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { nil, false, }, + { + "incorrect valset - shouldn't pass", + func() *ibctmtypes.Misbehaviour { + clientHeader := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Minute), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + clientHeaderWithCorruptedValset := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Hour), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + // change a validator public key in one the second header + testutil.CorruptValidatorPubkeyInHeader(clientHeaderWithCorruptedValset, clientTMValset.Validators[0].Address) + + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: clientHeaderWithCorruptedValset, + } + }, + []*tmtypes.Validator{}, + false, + }, + { + "incorrect valset 2 - shouldn't pass", + func() *ibctmtypes.Misbehaviour { + clientHeader := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Minute), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + clientHeaderWithCorruptedSigs := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Hour), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + // change the valset in the header + vs, _ := altValset.ToProto() + clientHeader.ValidatorSet.Validators = vs.Validators[:3] + clientHeaderWithCorruptedSigs.ValidatorSet.Validators = vs.Validators[:3] + + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: clientHeaderWithCorruptedSigs, + } + }, + []*tmtypes.Validator{}, + false, + }, + { + "incorrect signatures - shouldn't pass", + func() *ibctmtypes.Misbehaviour { + clientHeader := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Minute), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + clientHeaderWithCorruptedSigs := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Hour), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + // change the signature of one of the validator in the header + testutil.CorruptCommitSigsInHeader(clientHeaderWithCorruptedSigs, clientTMValset.Validators[0].Address) + + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: clientHeaderWithCorruptedSigs, + } + }, + []*tmtypes.Validator{}, + false, + }, { "light client attack - lunatic attack", func() *ibctmtypes.Misbehaviour { @@ -226,8 +340,8 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { s.NoError(err) s.Equal(len(tc.expByzantineValidators), len(byzantineValidators)) - // For both lunatic and equivocation attacks all the validators - // who signed the bad header (Header2) should be in returned in the evidence + // For both lunatic and equivocation attacks, all the validators + // who signed both headers if len(tc.expByzantineValidators) > 0 { equivocatingVals := tc.getMisbehaviour().Header2.ValidatorSet s.Equal(len(equivocatingVals.Validators), len(byzantineValidators)) @@ -267,7 +381,7 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { // 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 := make(map[string]tmtypes.PrivValidator, 2) altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] @@ -347,7 +461,16 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { }, false, }, - // TODO: Add valset signature test case here + // TODO: should pass after 1401 is merged + // { + // "one header of the misbehaviour has insufficient voting power - shouldn't pass", + // &ibctmtypes.Misbehaviour{ + // ClientId: s.path.EndpointA.ClientID, + // Header1: clientHeader, + // Header2: clientHeader2, + // }, + // false, + // }, { "valid misbehaviour - should pass", &ibctmtypes.Misbehaviour{ diff --git a/testutil/crypto/evidence.go b/testutil/crypto/evidence.go index 9bd96bdca5..653f20824e 100644 --- a/testutil/crypto/evidence.go +++ b/testutil/crypto/evidence.go @@ -3,7 +3,10 @@ package crypto import ( "time" + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + "github.com/cometbft/cometbft/crypto/tmhash" + "github.com/cometbft/cometbft/libs/bytes" tmtypes "github.com/cometbft/cometbft/types" ) @@ -66,7 +69,6 @@ func MakeAndSignVoteWithForgedValAddress( valAddressSigner tmtypes.PrivValidator, chainID string, ) *tmtypes.Vote { - // create the vote using a different key than the signing key vote, err := tmtypes.MakeVote( blockHeight, @@ -90,3 +92,44 @@ func MakeAndSignVoteWithForgedValAddress( vote.Signature = v.Signature return vote } + +// CorruptCommitSigsInHeader corrupts the header by changing the value +// of the commit signature for given validator address. +// Note that this method is solely used for testing purposes +func CorruptCommitSigsInHeader(header *ibctmtypes.Header, valAddress bytes.HexBytes) { + commit, err := tmtypes.CommitFromProto(header.Commit) + if err != nil { + panic(err) + } + + for idx, sig := range commit.Signatures { + if sig.ValidatorAddress.String() == valAddress.String() { + sig.Signature = []byte("randomsig") + commit.Signatures[idx] = sig + } + } + // update the commit in client the header + header.SignedHeader.Commit = commit.ToProto() +} + +// CorruptValidatorPubkeyInHeader corrupts the header by changing the validator pubkey +// of the given validator address in the validator set. +// Note that this method is solely used for testing purposes +func CorruptValidatorPubkeyInHeader(header *ibctmtypes.Header, valAddress bytes.HexBytes) { + valset, err := tmtypes.ValidatorSetFromProto(header.ValidatorSet) + if err != nil { + panic(err) + } + + for _, v := range valset.Validators { + if v.Address.String() == valAddress.String() { + v.PubKey = tmtypes.NewMockPV().PrivKey.PubKey() + } + } + + vs, err := valset.ToProto() + if err != nil { + panic(err) + } + header.ValidatorSet = vs +} diff --git a/testutil/keeper/expectations.go b/testutil/keeper/expectations.go index 4702e688e2..9494b66936 100644 --- a/testutil/keeper/expectations.go +++ b/testutil/keeper/expectations.go @@ -3,7 +3,6 @@ package keeper import ( time "time" - math "cosmossdk.io/math" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" conntypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" @@ -12,6 +11,8 @@ import ( "github.com/golang/mock/gomock" extra "github.com/oxyno-zeta/gomock-extra-matcher" + math "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -217,5 +218,4 @@ func GetMocksForSlashValidator( SlashWithInfractionReason(ctx, consAddr, expectedInfractionHeight, expectedSlashPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN). Times(1), } - } diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 51bfd5749a..23b0caeb5a 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -104,20 +104,28 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. // 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 { + header1Signers := map[string]int{} + for idx, sign := range lightBlock1.Commit.Signatures { if sign.Absent() { continue } - header1Signers[sign.ValidatorAddress.String()] = struct{}{} + header1Signers[sign.ValidatorAddress.String()] = idx } // iterate over the header2 signers and check if they signed header1 - for _, sign := range lightBlock2.Commit.Signatures { + for sigIdxHeader2, sign := range lightBlock2.Commit.Signatures { if sign.Absent() { continue } - if _, ok := header1Signers[sign.ValidatorAddress.String()]; ok { + if sigIdxHeader1, ok := header1Signers[sign.ValidatorAddress.String()]; ok { + if err := verifyLightBlockCommitSig(*lightBlock1, sigIdxHeader1); err != nil { + return nil, err + } + + if err := verifyLightBlockCommitSig(*lightBlock2, sigIdxHeader2); err != nil { + return nil, err + } + _, val := lightBlock1.ValidatorSet.GetByAddress(sign.ValidatorAddress) validators = append(validators, val) } @@ -182,3 +190,27 @@ func headersStateTransitionsAreConflicting(h1, h2 tmtypes.Header) bool { !bytes.Equal(h1.AppHash, h2.AppHash) || !bytes.Equal(h1.LastResultsHash, h2.LastResultsHash) } + +func verifyLightBlockCommitSig(lightBlock tmtypes.LightBlock, sigIdx int) error { + // get signature + sig := lightBlock.Commit.Signatures[sigIdx] + + // get validator + idx, val := lightBlock.ValidatorSet.GetByAddress(sig.ValidatorAddress) + if idx == -1 { + return fmt.Errorf("incorrect signature: validator address %s isn't part of the validator set", sig.ValidatorAddress.String()) + } + + // verify validator pubkey corresponds to signature validator address + if !bytes.Equal(val.PubKey.Address(), sig.ValidatorAddress) { + return fmt.Errorf("validator public key doesn't correspond to signature validator address: %s!= %s", val.PubKey.Address(), sig.ValidatorAddress) + } + + // validate signature + voteSignBytes := lightBlock.Commit.VoteSignBytes(lightBlock.ChainID, int32(sigIdx)) + if !val.PubKey.VerifySignature(voteSignBytes, sig.Signature) { + return fmt.Errorf("wrong signature (#%d): %X", sigIdx, sig.Signature) + } + + return nil +} diff --git a/x/ccv/provider/keeper/punish_validator_test.go b/x/ccv/provider/keeper/punish_validator_test.go index 8834d0b464..63e7ab9e86 100644 --- a/x/ccv/provider/keeper/punish_validator_test.go +++ b/x/ccv/provider/keeper/punish_validator_test.go @@ -412,7 +412,7 @@ func TestSlashValidator(t *testing.T) { mocks.MockStakingKeeper.EXPECT(). SlashUnbondingDelegation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn( - func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) sdk.Int { + func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) math.Int { sum := sdk.NewInt(0) for _, r := range undelegation.Entries { if r.IsMature(ctx.BlockTime()) { @@ -425,7 +425,7 @@ func TestSlashValidator(t *testing.T) { 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 { + func(_ sdk.Context, _ stakingtypes.Validator, redelegation stakingtypes.Redelegation, _ int64, _ sdk.Dec) math.Int { sum := sdk.NewInt(0) for _, r := range redelegation.Entries { if r.IsMature(ctx.BlockTime()) { diff --git a/x/ccv/provider/types/proposal.go b/x/ccv/provider/types/proposal.go index aa15fffc81..a7fcbbdd73 100644 --- a/x/ccv/provider/types/proposal.go +++ b/x/ccv/provider/types/proposal.go @@ -9,10 +9,11 @@ import ( clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" ) From 84955b9ddf08ed25e08814641c909fd84dc135da Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 15 Nov 2023 17:28:09 +0100 Subject: [PATCH 64/82] feat: update misbehaviour handling using IBC-Go 7 (#1401) * nit fix * improve doc * super picky nit * fix!: drop nil votes in misbehaviour handling (#1404) * update CHANGELOG for final release * save * update test to extract byzantine validators * improve testing * nits * nits * Update tests/integration/misbehaviour.go Co-authored-by: insumity * Update testutil/crypto/evidence.go Co-authored-by: insumity * update util func * doc * check misb client ID * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity * nits --------- Co-authored-by: insumity * fix comments * update changelog * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity * improve tests * lint * udpate traces * silly bug --------- Co-authored-by: insumity --- ...ic-verification-of-equivocation-feature.md | 3 + ...ic-verification-of-equivocation-feature.md | 3 + .../consumer-double-sign.json | 39 +- .../consumer-misbehaviour.json | 39 +- .../e2e/tracehandler_testdata/democracy.json | 3 - .../democracyRewardsSteps.json | 3 - .../e2e/tracehandler_testdata/happyPath.json | 369 ------------------ .../e2e/tracehandler_testdata/shorthappy.json | 369 ------------------ .../tracehandler_testdata/slashThrottle.json | 2 + tests/integration/misbehaviour.go | 137 ++++--- x/ccv/provider/keeper/misbehaviour.go | 33 +- 11 files changed, 131 insertions(+), 869 deletions(-) create mode 100644 .changelog/unreleased/features/provider/1340-add-cryptographic-verification-of-equivocation-feature.md create mode 100644 .changelog/unreleased/state-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md diff --git a/.changelog/unreleased/features/provider/1340-add-cryptographic-verification-of-equivocation-feature.md b/.changelog/unreleased/features/provider/1340-add-cryptographic-verification-of-equivocation-feature.md new file mode 100644 index 0000000000..e34b5fff28 --- /dev/null +++ b/.changelog/unreleased/features/provider/1340-add-cryptographic-verification-of-equivocation-feature.md @@ -0,0 +1,3 @@ +- Add the cryptographic verification of equivocation feature to the provider + (cf. ADR-005 & ADR-013). ([\#1340](https://github.com/cosmos/interchain- + security/pull/1340)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md b/.changelog/unreleased/state-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md new file mode 100644 index 0000000000..e34b5fff28 --- /dev/null +++ b/.changelog/unreleased/state-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md @@ -0,0 +1,3 @@ +- Add the cryptographic verification of equivocation feature to the provider + (cf. ADR-005 & ADR-013). ([\#1340](https://github.com/cosmos/interchain- + security/pull/1340)) \ No newline at end of file diff --git a/tests/e2e/tracehandler_testdata/consumer-double-sign.json b/tests/e2e/tracehandler_testdata/consumer-double-sign.json index d1a7ae065f..d6d1fc7086 100644 --- a/tests/e2e/tracehandler_testdata/consumer-double-sign.json +++ b/tests/e2e/tracehandler_testdata/consumer-double-sign.json @@ -37,8 +37,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -72,8 +71,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": { @@ -117,8 +115,7 @@ "ProviderKeys": { "carol": "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6" }, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -162,8 +159,7 @@ "ProviderKeys": { "carol": "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6" }, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -199,8 +195,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": { @@ -258,8 +253,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -277,8 +271,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -328,8 +321,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -351,8 +343,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -378,8 +369,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -401,8 +391,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -431,8 +420,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -454,8 +442,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null diff --git a/tests/e2e/tracehandler_testdata/consumer-misbehaviour.json b/tests/e2e/tracehandler_testdata/consumer-misbehaviour.json index 717761327d..028637934f 100644 --- a/tests/e2e/tracehandler_testdata/consumer-misbehaviour.json +++ b/tests/e2e/tracehandler_testdata/consumer-misbehaviour.json @@ -31,8 +31,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -66,8 +65,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": { @@ -111,8 +109,7 @@ "ProviderKeys": { "alice": "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq" }, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -146,8 +143,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": { @@ -193,8 +189,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -211,8 +206,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -263,8 +257,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -281,8 +274,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -310,8 +302,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -351,8 +342,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -372,8 +362,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -401,8 +390,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": null, "Proposals": null @@ -422,8 +410,7 @@ "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, - "ConsumerChainQueueSizes": null, - "GlobalSlashQueueSize": null, + "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, "ClientsFrozenHeights": { "07-tendermint-0": { diff --git a/tests/e2e/tracehandler_testdata/democracy.json b/tests/e2e/tracehandler_testdata/democracy.json index 729dd35005..3a9ab84a96 100644 --- a/tests/e2e/tracehandler_testdata/democracy.json +++ b/tests/e2e/tracehandler_testdata/democracy.json @@ -610,10 +610,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, -<<<<<<< HEAD "ClientsFrozenHeights": null, -======= ->>>>>>> main "Proposals": { "1": { "RawProposal": { diff --git a/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json b/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json index c9e36b15b6..74df5293b9 100644 --- a/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json +++ b/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json @@ -610,10 +610,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, -<<<<<<< HEAD "ClientsFrozenHeights": null, -======= ->>>>>>> main "Proposals": { "1": { "RawProposal": { diff --git a/tests/e2e/tracehandler_testdata/happyPath.json b/tests/e2e/tracehandler_testdata/happyPath.json index ee62446065..7c26f5c274 100644 --- a/tests/e2e/tracehandler_testdata/happyPath.json +++ b/tests/e2e/tracehandler_testdata/happyPath.json @@ -1358,69 +1358,6 @@ } }, { -<<<<<<< HEAD -======= - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-10-04T12:14:14.883367+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9500000000 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Title": "", - "Description": "", - "Deposit": 0, - "Status": "" - }, - "Type": "main.TextProposal" - } - } - } - } - }, - { ->>>>>>> main "ActionType": "main.doublesignSlashAction", "Action": { "Validator": "carol", @@ -1507,313 +1444,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, -<<<<<<< HEAD "ClientsFrozenHeights": null, -======= - "Proposals": null - } - } - }, - { - "ActionType": "main.doublesignSlashAction", - "Action": { - "Validator": "bob", - "Chain": "consu" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-10-04T12:14:14.88337+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9489999999 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_VOTING_PERIOD" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.voteGovProposalAction", - "Action": { - "Chain": "provi", - "From": [ - "alice", - "bob", - "carol" - ], - "Vote": [ - "yes", - "yes", - "yes" - ], - "PropNumber": 2 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_PASSED" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, ->>>>>>> main "Proposals": null } } diff --git a/tests/e2e/tracehandler_testdata/shorthappy.json b/tests/e2e/tracehandler_testdata/shorthappy.json index a9dc42e18c..85e96aeed1 100644 --- a/tests/e2e/tracehandler_testdata/shorthappy.json +++ b/tests/e2e/tracehandler_testdata/shorthappy.json @@ -929,69 +929,6 @@ } }, { -<<<<<<< HEAD -======= - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-10-04T12:14:14.883385+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9500000000 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Title": "", - "Description": "", - "Deposit": 0, - "Status": "" - }, - "Type": "main.TextProposal" - } - } - } - } - }, - { ->>>>>>> main "ActionType": "main.doublesignSlashAction", "Action": { "Validator": "carol", @@ -1078,313 +1015,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, -<<<<<<< HEAD "ClientsFrozenHeights": null, -======= - "Proposals": null - } - } - }, - { - "ActionType": "main.doublesignSlashAction", - "Action": { - "Validator": "bob", - "Chain": "consu" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-10-04T12:14:14.883388+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9489999999 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_VOTING_PERIOD" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.voteGovProposalAction", - "Action": { - "Chain": "provi", - "From": [ - "alice", - "bob", - "carol" - ], - "Vote": [ - "yes", - "yes", - "yes" - ], - "PropNumber": 2 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_PASSED" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, ->>>>>>> main "Proposals": null } } diff --git a/tests/e2e/tracehandler_testdata/slashThrottle.json b/tests/e2e/tracehandler_testdata/slashThrottle.json index 181fb4716e..dbca351d49 100644 --- a/tests/e2e/tracehandler_testdata/slashThrottle.json +++ b/tests/e2e/tracehandler_testdata/slashThrottle.json @@ -657,6 +657,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -678,6 +679,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": 1, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index e12d12664c..97aef5f2aa 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -379,33 +379,75 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) clientSigners := s.consumerChain.Signers + // create a valid client header + clientHeader := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + // 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, 2) altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] + // create a conflicting client with different block ID using + // to alternative validator set + clientHeaderWithDiffBlockID := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, // trusted valset stays the same + altSigners, + ) + + // create an alternative validator set using less than 1/3 of the trusted validator set + altValset2 := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:1]) + altSigners2 := make(map[string]tmtypes.PrivValidator, 1) + altSigners2[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] + + // create a conflicting client header with insufficient voting power + clientHeaderWithInsufficientVotingPower := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + // use a different block time to change the header BlockID + headerTs.Add(time.Hour), + altValset2, + altValset2, + clientTMValset, + altSigners2, + ) + testCases := []struct { name string misbehaviour *ibctmtypes.Misbehaviour expPass bool }{ { - "client state not found - shouldn't pass", + "identical headers - shouldn't pass", &ibctmtypes.Misbehaviour{ - ClientId: "clientID", + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: clientHeader, + }, + false, + }, + { + "misbehaviour isn't for a consumer chain - 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, + "aChainID", int64(clientHeight.RevisionHeight+1), clientHeight, headerTs, @@ -414,23 +456,16 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { clientTMValset, altSigners, ), + Header2: clientHeader, }, false, }, { - "invalid misbehaviour with empty header1 - shouldn't pass", + "client ID doesn't correspond to the client ID of consumer chain - 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, - ), + ClientId: "clientID", + Header1: clientHeader, + Header2: clientHeaderWithDiffBlockID, }, false, }, @@ -438,16 +473,7 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { "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, - ), + Header1: clientHeader, Header2: s.consumerChain.CreateTMClientHeader( s.consumerChain.ChainID, int64(clientHeight.RevisionHeight+2), @@ -461,41 +487,22 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { }, false, }, - // TODO: should pass after 1401 is merged - // { - // "one header of the misbehaviour has insufficient voting power - shouldn't pass", - // &ibctmtypes.Misbehaviour{ - // ClientId: s.path.EndpointA.ClientID, - // Header1: clientHeader, - // Header2: clientHeader2, - // }, - // false, - // }, + { + "one header of the misbehaviour has insufficient voting power - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: clientHeaderWithInsufficientVotingPower, + }, + 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, - ), + Header1: clientHeader, // create header using a different validator set - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - headerTs, - altValset, - altValset, - clientTMValset, - altSigners, - ), + Header2: clientHeaderWithDiffBlockID, }, true, }, diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 23b0caeb5a..e019477f4f 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -83,11 +83,11 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. // construct the trusted and conflicted light blocks lightBlock1, err := headerToLightBlock(*misbehaviour.Header1) if err != nil { - return + return validators, err } lightBlock2, err := headerToLightBlock(*misbehaviour.Header2) if err != nil { - return + return validators, err } // Check if the misbehaviour corresponds to an Amnesia attack, @@ -97,7 +97,7 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. // // Note that we cannot differentiate which of the headers is trusted or malicious, if !headersStateTransitionsAreConflicting(*lightBlock1.Header, *lightBlock2.Header) && lightBlock1.Commit.Round != lightBlock2.Commit.Round { - return + return validators, nil } // compare the signatures of the headers @@ -153,9 +153,21 @@ 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 +// a valid light client attack from an ICS consumer chain and that the light client isn't expired func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { - clientState, found := k.clientKeeper.GetClientState(ctx, misbehaviour.ClientId) + // check that the misbehaviour is for an ICS consumer chain + clientId, found := k.GetConsumerClientId(ctx, misbehaviour.Header1.Header.ChainID) + if !found { + return fmt.Errorf("incorrect misbehaviour with conflicting headers from a non-existent consumer chain: %s", misbehaviour.Header1.Header.ChainID) + } else if misbehaviour.ClientId != clientId { + return fmt.Errorf("incorrect misbehaviour: expected client ID for consumer chain %s is %s got %s", + misbehaviour.Header1.Header.ChainID, + clientId, + misbehaviour.ClientId, + ) + } + + clientState, found := k.clientKeeper.GetClientState(ctx, clientId) if !found { return errorsmod.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot check misbehaviour for client with ID %s", misbehaviour.ClientId) } @@ -169,14 +181,19 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe return errorsmod.Wrap(ibcclienttypes.ErrInvalidMisbehaviour, "headers are not at same height") } - // CheckMisbehaviour verifies that the headers have both he same block height and - // different blockID hashes + // CheckForMisbehaviour verifies that the headers have different blockID hashes ok := clientState.CheckForMisbehaviour(ctx, k.cdc, clientStore, &misbehaviour) if !ok { return errorsmod.Wrapf(ibcclienttypes.ErrInvalidMisbehaviour, "invalid misbehaviour for client-id: %s", misbehaviour.ClientId) } - // TODO check misb valset signatures here + // VerifyClientMessage calls verifyMisbehaviour which verifies that the headers in the misbehaviour + // are valid against their respective trusted consensus states and that at least a TrustLevel of the validator set signed their commit, + // see checkMisbehaviourHeader in ibc-go/blob/v7.3.0/modules/light-clients/07-tendermint/misbehaviour_handle.go#L126 + if err := clientState.VerifyClientMessage(ctx, k.cdc, clientStore, &misbehaviour); err != nil { + return err + } + return nil } From 12c1bda1d0f7ae9c32f3a59ead9e4b71d3898d52 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 16 Nov 2023 09:43:35 +0100 Subject: [PATCH 65/82] nit --- ...tion-feature.md => 1340-cryptographic-equivocation-feature.md} | 0 ...tion-feature.md => 1340-cryptographic-equivocation-feature.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .changelog/unreleased/features/provider/{1340-add-cryptographic-verification-of-equivocation-feature.md => 1340-cryptographic-equivocation-feature.md} (100%) rename .changelog/unreleased/state-breaking/provider/{1340-add-cryptographic-verification-of-equivocation-feature.md => 1340-cryptographic-equivocation-feature.md} (100%) diff --git a/.changelog/unreleased/features/provider/1340-add-cryptographic-verification-of-equivocation-feature.md b/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md similarity index 100% rename from .changelog/unreleased/features/provider/1340-add-cryptographic-verification-of-equivocation-feature.md rename to .changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md diff --git a/.changelog/unreleased/state-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md b/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md similarity index 100% rename from .changelog/unreleased/state-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md rename to .changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md From c72fa097d06907713c6cb60decc40f3dd3513388 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 16 Nov 2023 14:40:39 +0100 Subject: [PATCH 66/82] Update x/ccv/provider/keeper/double_vote.go Co-authored-by: insumity --- x/ccv/provider/keeper/double_vote.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 62f3a54f2a..198768571c 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -16,7 +16,7 @@ import ( ) // 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. +// and a public key and, if successful, executes the slashing, jailing, and tombstoning of the malicious validator. func (k Keeper) HandleConsumerDoubleVoting( ctx sdk.Context, evidence *tmtypes.DuplicateVoteEvidence, From 74a331402630435e7cf986b24146e0bade05212a Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 16 Nov 2023 14:40:59 +0100 Subject: [PATCH 67/82] Update x/ccv/provider/keeper/double_vote.go Co-authored-by: insumity --- x/ccv/provider/keeper/double_vote.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 198768571c..98d5307831 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -79,7 +79,7 @@ func (k Keeper) VerifyDoubleVotingEvidence( evidence.VoteA.Type != evidence.VoteB.Type { return errorsmod.Wrapf( ccvtypes.ErrInvalidDoubleVotingEvidence, - "h/r/s does not match: %d/%d/%v vs %d/%d/%v", + "height/round/type are not the same: %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) } From 481f0ccbad0db74a7705b0efe600e1c83d6098fe Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 16 Nov 2023 14:41:10 +0100 Subject: [PATCH 68/82] Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity --- x/ccv/provider/keeper/misbehaviour.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index e019477f4f..ac70a1e40b 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -169,7 +169,7 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe clientState, found := k.clientKeeper.GetClientState(ctx, clientId) if !found { - return errorsmod.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot check misbehaviour for client with ID %s", misbehaviour.ClientId) + return errorsmod.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot find client state for client with ID %s", clientId) } clientStore := k.clientKeeper.ClientStore(ctx, misbehaviour.ClientId) From eabf6673a7a432614a91bd4006dc725a598d4f98 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 16 Nov 2023 14:41:19 +0100 Subject: [PATCH 69/82] Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity --- x/ccv/provider/keeper/misbehaviour.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index ac70a1e40b..da1d73936f 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -172,7 +172,7 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe return errorsmod.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot find client state for client with ID %s", clientId) } - clientStore := k.clientKeeper.ClientStore(ctx, misbehaviour.ClientId) + clientStore := k.clientKeeper.ClientStore(ctx, clientId) // 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, From 6c3d1b5f507f16bb81f87ede95d16cefe4f70ced Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 16 Nov 2023 14:41:46 +0100 Subject: [PATCH 70/82] Update tests/integration/double_vote.go Co-authored-by: insumity --- tests/integration/double_vote.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index 4eba0fde3b..a4aa535859 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -12,7 +12,7 @@ import ( ) // TestHandleConsumerDoubleVoting verifies that handling a double voting evidence -// of a consumer chain results in the expected tombstoning and jailing the misbehaved validator +// of a consumer chain results in the expected tombstoning, jailing, and slashing of the misbehaved validator func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 From 9037e3019a201aa4c9fb6014f8b4be97f801be5c Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 16 Nov 2023 14:42:34 +0100 Subject: [PATCH 71/82] Update x/ccv/provider/client/cli/tx.go Co-authored-by: insumity --- x/ccv/provider/client/cli/tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index c386c0d12c..f6ef988299 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -131,7 +131,7 @@ func NewSubmitConsumerDoubleVotingCmd() *cobra.Command { 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 + 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. Example: From d458edab70e0b9d6a662f3c80e3752df28f6edeb Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 16 Nov 2023 14:43:23 +0100 Subject: [PATCH 72/82] Update x/ccv/provider/client/cli/tx.go Co-authored-by: insumity --- x/ccv/provider/client/cli/tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index f6ef988299..7349f0b06b 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -128,7 +128,7 @@ 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 with + fmt.Sprintf(`Submit a Tendermint duplicate 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 From af531f3869604805d04a81921a5574c33bac8ad5 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 16 Nov 2023 15:19:23 +0100 Subject: [PATCH 73/82] address comments --- ...0-add-cryptographic-verification-of-equivocation-feature.md | 3 +++ .../provider/1340-cryptographic-equivocation-feature.md | 3 +-- .../provider/1340-cryptographic-equivocation-feature.md | 3 +-- tests/e2e/steps.go | 1 - tests/integration/double_vote.go | 2 +- x/ccv/provider/keeper/misbehaviour.go | 2 +- x/ccv/provider/types/proposal.go | 3 ++- x/ccv/types/events.go | 1 - 8 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 .changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md diff --git a/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md b/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md new file mode 100644 index 0000000000..b6a2d31dce --- /dev/null +++ b/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md @@ -0,0 +1,3 @@ +- Add the cryptographic verification of equivocation feature to the + provider (cf. ADR-005 & ADR-013) and remove the equivocation proposal. + ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file diff --git a/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md b/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md index e34b5fff28..6d84b9c8e4 100644 --- a/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md +++ b/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md @@ -1,3 +1,2 @@ - Add the cryptographic verification of equivocation feature to the provider - (cf. ADR-005 & ADR-013). ([\#1340](https://github.com/cosmos/interchain- - security/pull/1340)) \ No newline at end of file + (cf. ADR-005 & ADR-013). ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md b/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md index e34b5fff28..6d84b9c8e4 100644 --- a/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md +++ b/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md @@ -1,3 +1,2 @@ - Add the cryptographic verification of equivocation feature to the provider - (cf. ADR-005 & ADR-013). ([\#1340](https://github.com/cosmos/interchain- - security/pull/1340)) \ No newline at end of file + (cf. ADR-005 & ADR-013). ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 1da7e6a698..783d976a3a 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -111,7 +111,6 @@ var consumerMisbehaviourSteps = concatSteps( 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/integration/double_vote.go b/tests/integration/double_vote.go index a4aa535859..3fae17adf5 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -38,7 +38,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { 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 + // see VoteSignBytes in https://github.com/cometbft/cometbft/blob/v0.37.2/types/vote.go#L93 // create two votes using the consumer validator key consuVote := testutil.MakeAndSignVote( diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index da1d73936f..9a09f0682d 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -153,7 +153,7 @@ func headerToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { } // CheckMisbehaviour checks that headers in the given misbehaviour forms -// a valid light client attack from an ICS consumer chain and that the light client isn't expired +// a valid light client attack on a light client that tracks an ICS consumer chain func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { // check that the misbehaviour is for an ICS consumer chain clientId, found := k.GetConsumerClientId(ctx, misbehaviour.Header1.Header.ChainID) diff --git a/x/ccv/provider/types/proposal.go b/x/ccv/provider/types/proposal.go index a7fcbbdd73..5f6677f2a4 100644 --- a/x/ccv/provider/types/proposal.go +++ b/x/ccv/provider/types/proposal.go @@ -204,7 +204,8 @@ func (sccp *ConsumerRemovalProposal) ValidateBasic() error { } // NewEquivocationProposal creates a new equivocation proposal. -// [DEPRECATED]: do not use +// [DEPRECATED]: do not use since that equivocations can be submitted +// and verified automatically on the provider. func NewEquivocationProposal(title, description string, equivocations []*evidencetypes.Equivocation) govv1beta1.Content { return &EquivocationProposal{ Title: title, diff --git a/x/ccv/types/events.go b/x/ccv/types/events.go index 1252747507..4c597ded56 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -20,7 +20,6 @@ const ( AttributeKeyAckSuccess = "success" AttributeKeyAck = "acknowledgement" AttributeKeyAckError = "error" - AttributeValidatorConsumerAddress = "validator_consumer_address" AttributeInfractionHeight = "infraction_height" AttributeConsumerHeight = "consumer_height" AttributeTimestamp = "timestamp" From 26c1b11a139ef5cb29fc82138086838f3fcd138c Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 16 Nov 2023 15:21:08 +0100 Subject: [PATCH 74/82] address comments --- x/ccv/provider/keeper/double_vote.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 98d5307831..d2ea4aba46 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -70,8 +70,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. + // Note the age of the evidence isn't checked. // height/round/type must be the same if evidence.VoteA.Height != evidence.VoteB.Height || From 7440b43405e19f31d92e0b4cd5506388c0632d70 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 21 Nov 2023 08:51:15 +0100 Subject: [PATCH 75/82] Update x/ccv/provider/types/proposal.go Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> --- x/ccv/provider/types/proposal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/types/proposal.go b/x/ccv/provider/types/proposal.go index 5f6677f2a4..d1755852ef 100644 --- a/x/ccv/provider/types/proposal.go +++ b/x/ccv/provider/types/proposal.go @@ -204,7 +204,7 @@ func (sccp *ConsumerRemovalProposal) ValidateBasic() error { } // NewEquivocationProposal creates a new equivocation proposal. -// [DEPRECATED]: do not use since that equivocations can be submitted +// [DEPRECATED]: do not use because equivocations can be submitted // and verified automatically on the provider. func NewEquivocationProposal(title, description string, equivocations []*evidencetypes.Equivocation) govv1beta1.Content { return &EquivocationProposal{ From d61f474264d1800cac14ca3aed527cc4e377619d Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 21 Nov 2023 08:51:39 +0100 Subject: [PATCH 76/82] Update tests/integration/double_vote.go Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> --- tests/integration/double_vote.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index 3fae17adf5..2af7162d77 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -343,7 +343,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegationsAndRele currentBalance := sdk.NewDecFromInt(unb.Balance) s.Require().True(initialBalance.Sub(initialBalance.Mul(slashFraction)).Equal(currentBalance)) } - +// check that redelegations are slashed delegations := s.providerApp.GetTestStakingKeeper().Delegation(s.providerCtx(), delAddr, validator2.GetOperator()) s.Require().Equal(delegations.GetShares(), delShares.Add(redelShares).Sub(redelShares.Mul(slashFraction))) }) From 1895f87d37066aca8861d1f1c883a3c8c5496922 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 21 Nov 2023 09:48:09 +0100 Subject: [PATCH 77/82] Port: (feat!) [#1435](https://github.com/cosmos/interchain-security/pull/1435) Add height-base filter for consumer equivocation evidence. --- tests/integration/double_vote.go | 49 +- tests/integration/misbehaviour.go | 26 + .../provider/keeper/consumer_equivocation.go | 480 ++++++++++++++++++ ..._test.go => consumer_equivocation_test.go} | 315 ++++++++++++ x/ccv/provider/keeper/double_vote.go | 117 ----- x/ccv/provider/keeper/double_vote_test.go | 312 ------------ x/ccv/provider/keeper/misbehaviour.go | 233 --------- x/ccv/provider/keeper/punish_validator.go | 109 ---- x/ccv/provider/types/keys.go | 10 + x/ccv/provider/types/keys_test.go | 2 + 10 files changed, 880 insertions(+), 773 deletions(-) create mode 100644 x/ccv/provider/keeper/consumer_equivocation.go rename x/ccv/provider/keeper/{punish_validator_test.go => consumer_equivocation_test.go} (72%) delete mode 100644 x/ccv/provider/keeper/double_vote.go delete mode 100644 x/ccv/provider/keeper/double_vote_test.go delete mode 100644 x/ccv/provider/keeper/misbehaviour.go delete mode 100644 x/ccv/provider/keeper/punish_validator.go diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go index 2af7162d77..af05c745df 100644 --- a/tests/integration/double_vote.go +++ b/tests/integration/double_vote.go @@ -37,6 +37,14 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { blockID1 := testutil.MakeBlockID([]byte("blockhash"), 1000, []byte("partshash")) blockID2 := testutil.MakeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) + // Set the equivocation evidence min height to the previous block height + equivocationEvidenceMinHeight := uint64(s.consumerCtx().BlockHeight() - 1) + s.providerApp.GetProviderKeeper().SetEquivocationEvidenceMinHeight( + s.providerCtx(), + s.consumerChain.ChainID, + equivocationEvidenceMinHeight, + ) + // Note that votes are signed along with the chain ID // see VoteSignBytes in https://github.com/cometbft/cometbft/blob/v0.37.2/types/vote.go#L93 @@ -78,6 +86,17 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { s.consumerChain.ChainID, ) + // create a vote using the consumer validator key + // with block height that is smaller than the equivocation evidence min height + consuVoteOld := testutil.MakeAndSignVote( + blockID1, + int64(equivocationEvidenceMinHeight-1), + s.consumerCtx().BlockTime(), + consuValSet, + consuSigner, + s.consumerChain.ChainID, + ) + testCases := []struct { name string ev *tmtypes.DuplicateVoteEvidence @@ -86,7 +105,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { expPass bool }{ { - "invalid consumer chain id - shouldn't pass", + "cannot find consumer chain for the given chain ID - shouldn't pass", &tmtypes.DuplicateVoteEvidence{ VoteA: consuVote, VoteB: consuBadVote, @@ -98,6 +117,32 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { consuVal.PubKey, false, }, + { + "evidence is older than equivocation evidence min height - shouldn't pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: consuVoteOld, + VoteB: consuBadVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + consuVal.PubKey, + false, + }, + { + "the votes in the evidence are for different height - shouldn't pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: consuVote, + VoteB: consuVoteOld, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + consuVal.PubKey, + false, + }, { "wrong public key - shouldn't pass", &tmtypes.DuplicateVoteEvidence{ @@ -343,7 +388,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegationsAndRele currentBalance := sdk.NewDecFromInt(unb.Balance) s.Require().True(initialBalance.Sub(initialBalance.Mul(slashFraction)).Equal(currentBalance)) } -// check that redelegations are slashed + // check that redelegations are slashed delegations := s.providerApp.GetTestStakingKeeper().Delegation(s.providerCtx(), delAddr, validator2.GetOperator()) s.Require().Equal(delegations.GetShares(), delShares.Add(redelShares).Sub(redelShares.Mul(slashFraction))) }) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 97aef5f2aa..c0f2cf10b9 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -428,6 +428,14 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { altSigners2, ) + // Set the equivocation evidence min height to the previous block height + equivocationEvidenceMinHeight := clientHeight.RevisionHeight + 1 + s.providerApp.GetProviderKeeper().SetEquivocationEvidenceMinHeight( + s.providerCtx(), + s.consumerChain.ChainID, + equivocationEvidenceMinHeight, + ) + testCases := []struct { name string misbehaviour *ibctmtypes.Misbehaviour @@ -487,6 +495,24 @@ func (s *CCVTestSuite) TestCheckMisbehaviour() { }, false, }, + { + "invalid misbehaviour older than the min equivocation evidence height - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(equivocationEvidenceMinHeight-1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + Header2: clientHeader, + }, + false, + }, { "one header of the misbehaviour has insufficient voting power - shouldn't pass", &ibctmtypes.Misbehaviour{ diff --git a/x/ccv/provider/keeper/consumer_equivocation.go b/x/ccv/provider/keeper/consumer_equivocation.go new file mode 100644 index 0000000000..53232b4205 --- /dev/null +++ b/x/ccv/provider/keeper/consumer_equivocation.go @@ -0,0 +1,480 @@ +package keeper + +import ( + "bytes" + "encoding/binary" + "fmt" + + ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + 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" + + tmtypes "github.com/cometbft/cometbft/types" + + "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" +) + +// +// Double Voting section +// + +// HandleConsumerDoubleVoting verifies a double voting evidence for a given a consumer chain ID +// and a public key and, if successful, executes the slashing, jailing, and tombstoning 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(*evidence, chainID, pubkey); err != nil { + return err + } + + // get the validator's consensus address on the provider + providerAddr := k.GetProviderAddrFromConsumerAddr( + ctx, + chainID, + types.NewConsumerConsAddress(sdk.ConsAddress(evidence.VoteA.ValidatorAddress.Bytes())), + ) + + 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", + "byzantine validator address", providerAddr.String(), + ) + + return nil +} + +// VerifyDoubleVotingEvidence verifies a double voting evidence +// for a given chain id and a validator public key +func (k Keeper) VerifyDoubleVotingEvidence( + evidence tmtypes.DuplicateVoteEvidence, + chainID string, + pubkey cryptotypes.PubKey, +) error { + if pubkey == nil { + 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 the age of the evidence isn't checked. + + // 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 { + return errorsmod.Wrapf( + ccvtypes.ErrInvalidDoubleVotingEvidence, + "height/round/type are not the same: %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) + } + + // Addresses must be the same + if !bytes.Equal(evidence.VoteA.ValidatorAddress, evidence.VoteB.ValidatorAddress) { + return errorsmod.Wrapf( + ccvtypes.ErrInvalidDoubleVotingEvidence, + "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 errorsmod.Wrapf( + ccvtypes.ErrInvalidDoubleVotingEvidence, + "block IDs are the same (%v) - not a real duplicate vote", + evidence.VoteA.BlockID, + ) + } + + va := evidence.VoteA.ToProto() + vb := evidence.VoteB.ToProto() + + // signatures must be valid + if !pubkey.VerifySignature(tmtypes.VoteSignBytes(chainID, va), evidence.VoteA.Signature) { + return fmt.Errorf("verifying VoteA: %w", tmtypes.ErrVoteInvalidSignature) + } + if !pubkey.VerifySignature(tmtypes.VoteSignBytes(chainID, vb), evidence.VoteB.Signature) { + return fmt.Errorf("verifying VoteB: %w", tmtypes.ErrVoteInvalidSignature) + } + + return nil +} + +// +// Light Client Attack (IBC misbehavior) section +// + +// HandleConsumerMisbehaviour checks if the given IBC misbehaviour corresponds to an equivocation light client attack, +// and in this case, slashes, jails, and tombstones +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 + } + + provAddrs := make([]types.ProviderConsAddress, len(byzantineValidators)) + + // 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())), + ) + 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 fail if the malicious + // validator is either or both !found, unbonded and tombstoned. + if err != nil { + 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 slashed, jailed and tombstoned", provAddrs, + ) + + 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) (validators []*tmtypes.Validator, err error) { + // construct the trusted and conflicted light blocks + lightBlock1, err := headerToLightBlock(*misbehaviour.Header1) + if err != nil { + return validators, err + } + lightBlock2, err := headerToLightBlock(*misbehaviour.Header2) + if err != nil { + return validators, err + } + + // Check if the misbehaviour corresponds to an Amnesia attack, + // meaning that the conflicting headers have both valid state transitions + // and different commit rounds. In this case, we return no validators as + // we can't identify the byzantine validators. + // + // Note that we cannot differentiate which of the headers is trusted or malicious, + if !headersStateTransitionsAreConflicting(*lightBlock1.Header, *lightBlock2.Header) && lightBlock1.Commit.Round != lightBlock2.Commit.Round { + return validators, nil + } + + // 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]int{} + for idx, sign := range lightBlock1.Commit.Signatures { + if sign.Absent() { + continue + } + header1Signers[sign.ValidatorAddress.String()] = idx + } + + // iterate over the header2 signers and check if they signed header1 + for sigIdxHeader2, sign := range lightBlock2.Commit.Signatures { + if sign.Absent() { + continue + } + if sigIdxHeader1, ok := header1Signers[sign.ValidatorAddress.String()]; ok { + if err := verifyLightBlockCommitSig(*lightBlock1, sigIdxHeader1); err != nil { + return nil, err + } + + if err := verifyLightBlockCommitSig(*lightBlock2, sigIdxHeader2); err != nil { + return nil, err + } + + _, val := lightBlock1.ValidatorSet.GetByAddress(sign.ValidatorAddress) + validators = append(validators, val) + } + } + + 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 on a light client that tracks an ICS consumer chain +func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { + // check that the misbehaviour is for an ICS consumer chain + clientId, found := k.GetConsumerClientId(ctx, misbehaviour.Header1.Header.ChainID) + if !found { + return fmt.Errorf("incorrect misbehaviour with conflicting headers from a non-existent consumer chain: %s", misbehaviour.Header1.Header.ChainID) + } else if misbehaviour.ClientId != clientId { + return fmt.Errorf("incorrect misbehaviour: expected client ID for consumer chain %s is %s got %s", + misbehaviour.Header1.Header.ChainID, + clientId, + misbehaviour.ClientId, + ) + } + + clientState, found := k.clientKeeper.GetClientState(ctx, clientId) + if !found { + return errorsmod.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot find client state for client with ID %s", clientId) + } + + clientStore := k.clientKeeper.ClientStore(ctx, clientId) + + // 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 CheckForMisbehaviour in ibc-go/blob/v7.3.0/modules/light-clients/07-tendermint/misbehaviour_handle.go#L73 + if !misbehaviour.Header1.GetHeight().EQ(misbehaviour.Header2.GetHeight()) { + return errorsmod.Wrap(ibcclienttypes.ErrInvalidMisbehaviour, "headers are not at same height") + } + + // CheckForMisbehaviour verifies that the headers have different blockID hashes + ok := clientState.CheckForMisbehaviour(ctx, k.cdc, clientStore, &misbehaviour) + if !ok { + return errorsmod.Wrapf(ibcclienttypes.ErrInvalidMisbehaviour, "invalid misbehaviour for client-id: %s", misbehaviour.ClientId) + } + + // VerifyClientMessage calls verifyMisbehaviour which verifies that the headers in the misbehaviour + // are valid against their respective trusted consensus states and that at least a TrustLevel of the validator set signed their commit, + // see checkMisbehaviourHeader in ibc-go/blob/v7.3.0/modules/light-clients/07-tendermint/misbehaviour_handle.go#L126 + if err := clientState.VerifyClientMessage(ctx, k.cdc, clientStore, &misbehaviour); err != nil { + return err + } + + return nil +} + +// Check if the given block headers have conflicting state transitions. +// Note that this method was copied from ConflictingHeaderIsInvalid in CometBFT, +// see https://github.com/cometbft/cometbft/blob/v0.34.27/types/evidence.go#L285 +func headersStateTransitionsAreConflicting(h1, h2 tmtypes.Header) bool { + return !bytes.Equal(h1.ValidatorsHash, h2.ValidatorsHash) || + !bytes.Equal(h1.NextValidatorsHash, h2.NextValidatorsHash) || + !bytes.Equal(h1.ConsensusHash, h2.ConsensusHash) || + !bytes.Equal(h1.AppHash, h2.AppHash) || + !bytes.Equal(h1.LastResultsHash, h2.LastResultsHash) +} + +func verifyLightBlockCommitSig(lightBlock tmtypes.LightBlock, sigIdx int) error { + // get signature + sig := lightBlock.Commit.Signatures[sigIdx] + + // get validator + idx, val := lightBlock.ValidatorSet.GetByAddress(sig.ValidatorAddress) + if idx == -1 { + return fmt.Errorf("incorrect signature: validator address %s isn't part of the validator set", sig.ValidatorAddress.String()) + } + + // verify validator pubkey corresponds to signature validator address + if !bytes.Equal(val.PubKey.Address(), sig.ValidatorAddress) { + return fmt.Errorf("validator public key doesn't correspond to signature validator address: %s!= %s", val.PubKey.Address(), sig.ValidatorAddress) + } + + // validate signature + voteSignBytes := lightBlock.Commit.VoteSignBytes(lightBlock.ChainID, int32(sigIdx)) + if !val.PubKey.VerifySignature(voteSignBytes, sig.Signature) { + return fmt.Errorf("wrong signature (#%d): %X", sigIdx, sig.Signature) + } + + return nil +} + +// +// Punish Validator section +// + +// 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()) + } + + 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()) + } + + // jail validator if not already + if !validator.IsJailed() { + k.stakingKeeper.Jail(ctx, providerAddr.ToSdkConsAddr()) + } + + k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime) + + // Tombstone the validator so that we cannot slash the validator more than once + // 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 math.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 given provider Address +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) + + consAdrr, err := validator.GetConsAddr() + if err != nil { + panic(err) + } + + k.stakingKeeper.SlashWithInfractionReason(ctx, consAdrr, 0, totalPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN) + + return nil +} + +// +// CRUD section +// + +// SetEquivocationEvidenceMinHeight sets the the minimum height +// of a valid consumer equivocation evidence for a given consumer chain ID +func (k Keeper) SetEquivocationEvidenceMinHeight(ctx sdk.Context, chainID string, height uint64) { + store := ctx.KVStore(k.storeKey) + heightBytes := make([]byte, 8) + binary.BigEndian.PutUint64(heightBytes, height) + + store.Set(types.EquivocationEvidenceMinHeightKey(chainID), heightBytes) +} + +// GetEquivocationEvidenceMinHeight returns the the minimum height +// of a valid consumer equivocation evidence for a given consumer chain ID +func (k Keeper) GetEquivocationEvidenceMinHeight(ctx sdk.Context, chainID string) uint64 { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.EquivocationEvidenceMinHeightKey(chainID)) + if bz == nil { + return 0 + } + + return binary.BigEndian.Uint64(bz) +} + +// DeleteEquivocationEvidenceMinHeight deletes the the minimum height +// of a valid consumer equivocation evidence for a given consumer chain ID +func (k Keeper) DeleteEquivocationEvidenceMinHeight(ctx sdk.Context, chainID string) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.EquivocationEvidenceMinHeightKey(chainID)) +} diff --git a/x/ccv/provider/keeper/punish_validator_test.go b/x/ccv/provider/keeper/consumer_equivocation_test.go similarity index 72% rename from x/ccv/provider/keeper/punish_validator_test.go rename to x/ccv/provider/keeper/consumer_equivocation_test.go index 63e7ab9e86..f7a736dff1 100644 --- a/x/ccv/provider/keeper/punish_validator_test.go +++ b/x/ccv/provider/keeper/consumer_equivocation_test.go @@ -11,6 +11,7 @@ import ( "cosmossdk.io/math" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" 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" @@ -22,6 +23,302 @@ import ( "github.com/cosmos/interchain-security/v3/x/ccv/provider/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 := cryptotestutil.MakeBlockID([]byte("blockhash"), 1000, []byte("partshash")) + blockID2 := cryptotestutil.MakeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) + + ctx = ctx.WithBlockTime(time.Now()) + + valPubkey1, err := cryptocodec.FromTmPubKeyInterface(val1.PubKey) + require.NoError(t, err) + + valPubkey2, err := cryptocodec.FromTmPubKeyInterface(val2.PubKey) + require.NoError(t, err) + + testCases := []struct { + name string + votes []*tmtypes.Vote + chainID string + pubkey cryptotypes.PubKey + expPass bool + }{ + { + "invalid verifying public key - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + nil, + false, + }, + { + "verifying public key doesn't correspond to validator address", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVoteWithForgedValAddress( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + signer2, + chainID, + ), + cryptotestutil.MakeAndSignVoteWithForgedValAddress( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + signer2, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "evidence has votes with different block height - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight()+1, + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "evidence has votes with different validator address - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer2, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "evidence has votes with same block IDs - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.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{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.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{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + "WrongChainID", + ), + cryptotestutil.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{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + "WrongChainID", + ), + }, + chainID, + valPubkey1, + false, + }, + { + "wrong public key - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey2, + false, + }, + { + "valid double voting evidence should pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey1, + true, + }, + } + + for _, tc := range testCases { + err = keeper.VerifyDoubleVotingEvidence( + 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) + } + } +} + // 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) { @@ -473,3 +770,21 @@ func TestSlashValidatorDoesNotSlashIfValidatorIsUnbonded(t *testing.T) { gomock.InOrder(expectedCalls...) keeper.SlashValidator(ctx, providerAddr) } + +func TestEquivocationEvidenceMinHeightCRUD(t *testing.T) { + chainID := consumer + expMinHeight := uint64(12) + keeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + height := keeper.GetEquivocationEvidenceMinHeight(ctx, chainID) + require.Zero(t, height, "equivocation evidence min height should be 0") + + keeper.SetEquivocationEvidenceMinHeight(ctx, chainID, expMinHeight) + height = keeper.GetEquivocationEvidenceMinHeight(ctx, chainID) + require.Equal(t, height, expMinHeight) + + keeper.DeleteEquivocationEvidenceMinHeight(ctx, chainID) + height = keeper.GetEquivocationEvidenceMinHeight(ctx, chainID) + require.Zero(t, height, "equivocation evidence min height should be 0") +} diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go deleted file mode 100644 index d2ea4aba46..0000000000 --- a/x/ccv/provider/keeper/double_vote.go +++ /dev/null @@ -1,117 +0,0 @@ -package keeper - -import ( - "bytes" - "fmt" - - errorsmod "cosmossdk.io/errors" - - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - tmtypes "github.com/cometbft/cometbft/types" - - "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" - ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" -) - -// HandleConsumerDoubleVoting verifies a double voting evidence for a given a consumer chain ID -// and a public key and, if successful, executes the slashing, jailing, and tombstoning 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(*evidence, chainID, pubkey); err != nil { - return err - } - - // get the validator's consensus address on the provider - providerAddr := k.GetProviderAddrFromConsumerAddr( - ctx, - chainID, - types.NewConsumerConsAddress(sdk.ConsAddress(evidence.VoteA.ValidatorAddress.Bytes())), - ) - - 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", - "byzantine validator address", providerAddr.String(), - ) - - return nil -} - -// VerifyDoubleVotingEvidence verifies a double voting evidence -// for a given chain id and a validator public key -func (k Keeper) VerifyDoubleVotingEvidence( - evidence tmtypes.DuplicateVoteEvidence, - chainID string, - pubkey cryptotypes.PubKey, -) error { - if pubkey == nil { - 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 the age of the evidence isn't checked. - - // 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 { - return errorsmod.Wrapf( - ccvtypes.ErrInvalidDoubleVotingEvidence, - "height/round/type are not the same: %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) - } - - // Addresses must be the same - if !bytes.Equal(evidence.VoteA.ValidatorAddress, evidence.VoteB.ValidatorAddress) { - return errorsmod.Wrapf( - ccvtypes.ErrInvalidDoubleVotingEvidence, - "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 errorsmod.Wrapf( - ccvtypes.ErrInvalidDoubleVotingEvidence, - "block IDs are the same (%v) - not a real duplicate vote", - evidence.VoteA.BlockID, - ) - } - - va := evidence.VoteA.ToProto() - vb := evidence.VoteB.ToProto() - - // signatures must be valid - if !pubkey.VerifySignature(tmtypes.VoteSignBytes(chainID, va), evidence.VoteA.Signature) { - return fmt.Errorf("verifying VoteA: %w", tmtypes.ErrVoteInvalidSignature) - } - if !pubkey.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 deleted file mode 100644 index 05ab238ccc..0000000000 --- a/x/ccv/provider/keeper/double_vote_test.go +++ /dev/null @@ -1,312 +0,0 @@ -package keeper_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - - tmtypes "github.com/cometbft/cometbft/types" - - testutil "github.com/cosmos/interchain-security/v3/testutil/crypto" - testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" -) - -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 := cryptocodec.FromTmPubKeyInterface(val1.PubKey) - require.NoError(t, err) - - valPubkey2, err := cryptocodec.FromTmPubKeyInterface(val2.PubKey) - require.NoError(t, err) - - testCases := []struct { - name string - votes []*tmtypes.Vote - chainID string - 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{ - testutil.MakeAndSignVoteWithForgedValAddress( - blockID1, - ctx.BlockHeight(), - ctx.BlockTime(), - valSet, - signer1, - signer2, - chainID, - ), - testutil.MakeAndSignVoteWithForgedValAddress( - blockID2, - ctx.BlockHeight(), - ctx.BlockTime(), - valSet, - signer1, - signer2, - chainID, - ), - }, - chainID, - valPubkey1, - false, - }, - { - "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, - }, - { - "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( - 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 deleted file mode 100644 index 9a09f0682d..0000000000 --- a/x/ccv/provider/keeper/misbehaviour.go +++ /dev/null @@ -1,233 +0,0 @@ -package keeper - -import ( - "bytes" - "fmt" - - ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" - - errorsmod "cosmossdk.io/errors" - - sdk "github.com/cosmos/cosmos-sdk/types" - - tmtypes "github.com/cometbft/cometbft/types" - - "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" -) - -// HandleConsumerMisbehaviour checks if the given IBC misbehaviour corresponds to an equivocation light client attack, -// and in this case, slashes, jails, and tombstones -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 - } - - provAddrs := make([]types.ProviderConsAddress, len(byzantineValidators)) - - // 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())), - ) - 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 fail if the malicious - // validator is either or both !found, unbonded and tombstoned. - if err != nil { - 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 slashed, jailed and tombstoned", provAddrs, - ) - - 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) (validators []*tmtypes.Validator, err error) { - // construct the trusted and conflicted light blocks - lightBlock1, err := headerToLightBlock(*misbehaviour.Header1) - if err != nil { - return validators, err - } - lightBlock2, err := headerToLightBlock(*misbehaviour.Header2) - if err != nil { - return validators, err - } - - // Check if the misbehaviour corresponds to an Amnesia attack, - // meaning that the conflicting headers have both valid state transitions - // and different commit rounds. In this case, we return no validators as - // we can't identify the byzantine validators. - // - // Note that we cannot differentiate which of the headers is trusted or malicious, - if !headersStateTransitionsAreConflicting(*lightBlock1.Header, *lightBlock2.Header) && lightBlock1.Commit.Round != lightBlock2.Commit.Round { - return validators, nil - } - - // 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]int{} - for idx, sign := range lightBlock1.Commit.Signatures { - if sign.Absent() { - continue - } - header1Signers[sign.ValidatorAddress.String()] = idx - } - - // iterate over the header2 signers and check if they signed header1 - for sigIdxHeader2, sign := range lightBlock2.Commit.Signatures { - if sign.Absent() { - continue - } - if sigIdxHeader1, ok := header1Signers[sign.ValidatorAddress.String()]; ok { - if err := verifyLightBlockCommitSig(*lightBlock1, sigIdxHeader1); err != nil { - return nil, err - } - - if err := verifyLightBlockCommitSig(*lightBlock2, sigIdxHeader2); err != nil { - return nil, err - } - - _, val := lightBlock1.ValidatorSet.GetByAddress(sign.ValidatorAddress) - validators = append(validators, val) - } - } - - 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 on a light client that tracks an ICS consumer chain -func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { - // check that the misbehaviour is for an ICS consumer chain - clientId, found := k.GetConsumerClientId(ctx, misbehaviour.Header1.Header.ChainID) - if !found { - return fmt.Errorf("incorrect misbehaviour with conflicting headers from a non-existent consumer chain: %s", misbehaviour.Header1.Header.ChainID) - } else if misbehaviour.ClientId != clientId { - return fmt.Errorf("incorrect misbehaviour: expected client ID for consumer chain %s is %s got %s", - misbehaviour.Header1.Header.ChainID, - clientId, - misbehaviour.ClientId, - ) - } - - clientState, found := k.clientKeeper.GetClientState(ctx, clientId) - if !found { - return errorsmod.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot find client state for client with ID %s", clientId) - } - - clientStore := k.clientKeeper.ClientStore(ctx, clientId) - - // 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 CheckForMisbehaviour in ibc-go/blob/v7.3.0/modules/light-clients/07-tendermint/misbehaviour_handle.go#L73 - if !misbehaviour.Header1.GetHeight().EQ(misbehaviour.Header2.GetHeight()) { - return errorsmod.Wrap(ibcclienttypes.ErrInvalidMisbehaviour, "headers are not at same height") - } - - // CheckForMisbehaviour verifies that the headers have different blockID hashes - ok := clientState.CheckForMisbehaviour(ctx, k.cdc, clientStore, &misbehaviour) - if !ok { - return errorsmod.Wrapf(ibcclienttypes.ErrInvalidMisbehaviour, "invalid misbehaviour for client-id: %s", misbehaviour.ClientId) - } - - // VerifyClientMessage calls verifyMisbehaviour which verifies that the headers in the misbehaviour - // are valid against their respective trusted consensus states and that at least a TrustLevel of the validator set signed their commit, - // see checkMisbehaviourHeader in ibc-go/blob/v7.3.0/modules/light-clients/07-tendermint/misbehaviour_handle.go#L126 - if err := clientState.VerifyClientMessage(ctx, k.cdc, clientStore, &misbehaviour); err != nil { - return err - } - - return nil -} - -// Check if the given block headers have conflicting state transitions. -// Note that this method was copied from ConflictingHeaderIsInvalid in CometBFT, -// see https://github.com/cometbft/cometbft/blob/v0.34.27/types/evidence.go#L285 -func headersStateTransitionsAreConflicting(h1, h2 tmtypes.Header) bool { - return !bytes.Equal(h1.ValidatorsHash, h2.ValidatorsHash) || - !bytes.Equal(h1.NextValidatorsHash, h2.NextValidatorsHash) || - !bytes.Equal(h1.ConsensusHash, h2.ConsensusHash) || - !bytes.Equal(h1.AppHash, h2.AppHash) || - !bytes.Equal(h1.LastResultsHash, h2.LastResultsHash) -} - -func verifyLightBlockCommitSig(lightBlock tmtypes.LightBlock, sigIdx int) error { - // get signature - sig := lightBlock.Commit.Signatures[sigIdx] - - // get validator - idx, val := lightBlock.ValidatorSet.GetByAddress(sig.ValidatorAddress) - if idx == -1 { - return fmt.Errorf("incorrect signature: validator address %s isn't part of the validator set", sig.ValidatorAddress.String()) - } - - // verify validator pubkey corresponds to signature validator address - if !bytes.Equal(val.PubKey.Address(), sig.ValidatorAddress) { - return fmt.Errorf("validator public key doesn't correspond to signature validator address: %s!= %s", val.PubKey.Address(), sig.ValidatorAddress) - } - - // validate signature - voteSignBytes := lightBlock.Commit.VoteSignBytes(lightBlock.ChainID, int32(sigIdx)) - if !val.PubKey.VerifySignature(voteSignBytes, sig.Signature) { - return fmt.Errorf("wrong signature (#%d): %X", sigIdx, sig.Signature) - } - - return nil -} diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go deleted file mode 100644 index f991c7cc5f..0000000000 --- a/x/ccv/provider/keeper/punish_validator.go +++ /dev/null @@ -1,109 +0,0 @@ -package keeper - -import ( - "fmt" - - errorsmod "cosmossdk.io/errors" - "cosmossdk.io/math" - - 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/v3/x/ccv/provider/types" -) - -// 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()) - } - - 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()) - } - - // jail validator if not already - if !validator.IsJailed() { - k.stakingKeeper.Jail(ctx, providerAddr.ToSdkConsAddr()) - } - - k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime) - - // Tombstone the validator so that we cannot slash the validator more than once - // 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 math.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 given provider Address -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) - - consAdrr, err := validator.GetConsAddr() - if err != nil { - panic(err) - } - - k.stakingKeeper.SlashWithInfractionReason(ctx, consAdrr, 0, totalPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN) - - return nil -} diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index ee4c11015b..95b5b4b820 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -138,6 +138,10 @@ const ( // handled in the current block VSCMaturedHandledThisBlockBytePrefix + // EquivocationEvidenceMinHeightBytePrefix is the byte prefix storing the mapping from consumer chain IDs + // to the minimum height of a valid consumer equivocation evidence + EquivocationEvidenceMinHeightBytePrefix + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -377,6 +381,12 @@ func ConsumerRewardDenomsKey(denom string) []byte { return append([]byte{ConsumerRewardDenomsBytePrefix}, []byte(denom)...) } +// EquivocationEvidenceMinHeightKey returns the key storing the minimum height +// of a valid consumer equivocation evidence for a given consumer chain ID +func EquivocationEvidenceMinHeightKey(consumerChainID string) []byte { + return append([]byte{EquivocationEvidenceMinHeightBytePrefix}, []byte(consumerChainID)...) +} + // NOTE: DO NOT ADD FULLY DEFINED KEY FUNCTIONS WITHOUT ADDING THEM TO getAllFullyDefinedKeys() IN keys_test.go // diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index 9f470f4a82..6b263a4fdb 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -54,6 +54,7 @@ func getAllKeyPrefixes() []byte { providertypes.ConsumerAddrsToPruneBytePrefix, providertypes.SlashLogBytePrefix, providertypes.VSCMaturedHandledThisBlockBytePrefix, + providertypes.EquivocationEvidenceMinHeightBytePrefix, } } @@ -98,6 +99,7 @@ func getAllFullyDefinedKeys() [][]byte { providertypes.ConsumerAddrsToPruneKey("chainID", 88), providertypes.SlashLogKey(providertypes.NewProviderConsAddress([]byte{0x05})), providertypes.VSCMaturedHandledThisBlockKey(), + providertypes.EquivocationEvidenceMinHeightKey("chainID"), } } From 695bd6ede377b2f247096662a146329f5b02e27a Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 20 Nov 2023 20:27:49 +0100 Subject: [PATCH 78/82] fix: update consumer double vote cmd (#1439) add cmd fix --- x/ccv/provider/client/cli/tx.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 7349f0b06b..78fbaaf9df 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -1,7 +1,9 @@ package cli import ( + "encoding/json" "fmt" + "os" "strings" ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" @@ -151,17 +153,28 @@ Example: txf = txf.WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) submitter := clientCtx.GetFromAddress() - var ev *tmproto.DuplicateVoteEvidence - if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(args[1]), &ev); err != nil { + + ev := tmproto.DuplicateVoteEvidence{} + evidenceJson, err := os.ReadFile(args[0]) + if err != nil { return err } - var header ibctmtypes.Header - if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(args[2]), &header); err != nil { + if err := json.Unmarshal(evidenceJson, &ev); err != nil { + return fmt.Errorf("duplicate vote evidence unmarshalling failed: %s", err) + } + + headerRaw, err := os.ReadFile(args[1]) + if err != nil { return err } - msg, err := types.NewMsgSubmitConsumerDoubleVoting(submitter, ev, &header) + header := ibctmtypes.Header{} + if err := types.ModuleCdc.UnmarshalJSON(headerRaw, &header); err != nil { + return fmt.Errorf("infraction IBC header unmarshalling failed: %s", err) + } + + msg, err := types.NewMsgSubmitConsumerDoubleVoting(submitter, &ev, &header) if err != nil { return err } From 8caad9f5e1a9e9a50b5b195338f81f23db2d0e4e Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 21 Nov 2023 11:32:47 +0100 Subject: [PATCH 79/82] update CHANGELOG --- CHANGELOG.md | 62 ++++++++++------------------------------------------ 1 file changed, 12 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5890240b97..80bdec5792 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,54 +1,5 @@ # CHANGELOG -## [Unreleased for Provider] - -Add an entry to the unreleased provider section whenever merging a PR to main that is not targeted at a specific release. These entries will eventually be included in a provider release. - -* (feature!) [#1280](https://github.com/cosmos/interchain-security/pull/1280) provider proposal for changing reward denoms -* (feature!) [#1244](https://github.com/cosmos/interchain-security/pull/1244) Update the default consumer unbonding period to 2 weeks. -* (deps) [#1259](https://github.com/cosmos/interchain-security/pull/1259) Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to [v0.47.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.5). -* (deps!) [#1258](https://github.com/cosmos/interchain-security/pull/1258) Bump [ibc-go](https://github.com/cosmos/ibc-go) to [v7.3.0](https://github.com/cosmos/ibc-go/releases/tag/v7.3.0). -* (deps) [#1258](https://github.com/cosmos/interchain-security/pull/1258) Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to [v0.47.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.4). -* (deps!) [#1196](https://github.com/cosmos/interchain-security/pull/1196) Bump [ibc-go](https://github.com/cosmos/ibc-go) to [v7.2.0](https://github.com/cosmos/ibc-go/releases/tag/v7.2.0). -* `[x/ccv/provider]` (fix) [#1076](https://github.com/cosmos/interchain-security/pull/1076) Add `InitTimeoutTimestamps` and `ExportedVscSendTimestamps` to exported genesis. -* (fix!) [#1422](https://github.com/cosmos/interchain-security/pull/1422) Fix the misbehaviour handling by verifying the signatures of byzantine validators. - -## v2.2.0-provider-lsm - -### 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). - -## [Unreleased for Consumer] - -Add an entry to the unreleased consumer section whenever merging a PR to main that is not targeted at a specific release. These entries will eventually be included in a consumer release. - -## v3.2.0-consumer - -Date September 6th, 2023 - -A minor version upgrade to the CONSUMER CCV module. This release includes various changes involving throttling v2 functionality, validation, and bumps to deps like cosmos-sdk and ibc-go. - -Note: - -* This release is ONLY RELEVANT TO CONSUMERS. The most recent provider release is v3.1.0, and will eventually be a release postfixed with `-provider`. -* this is the first upgrade to the consumer module with a separate semver cycle from the provider module. See [contributing.md](./CONTRIBUTING.md#semantic-versioning) and [associated ADR](docs/docs/adrs/adr-012-separate-releasing.md) for more info. - -Changes: - -* (feat) [#1295](https://github.com/cosmos/interchain-security/pull/1295) increment consumer consensus version and register consumer packet migration. -* (fix!) [#1262](https://github.com/cosmos/interchain-security/pull/1262) Remove incorrect address validation on `ProviderFeePoolAddrStr` param. -* (feature!) [#1244](https://github.com/cosmos/interchain-security/pull/1244) Update the default consumer unbonding period to 2 weeks. -* (fix!) [#1244](https://github.com/cosmos/interchain-security/pull/1244) Validate token transfer messages before calling `Transfer()`. -* (deps) [#1259](https://github.com/cosmos/interchain-security/pull/1259) Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to [v0.47.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.5). -* (deps!) [#1258](https://github.com/cosmos/interchain-security/pull/1258) Bump [ibc-go](https://github.com/cosmos/ibc-go) to [v7.3.0](https://github.com/cosmos/ibc-go/releases/tag/v7.3.0). -* (deps) [#1258](https://github.com/cosmos/interchain-security/pull/1258) Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to [v0.47.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.4). -* (deps!) [#1196](https://github.com/cosmos/interchain-security/pull/1196) Bump [ibc-go](https://github.com/cosmos/ibc-go) to [v7.2.0](https://github.com/cosmos/ibc-go/releases/tag/v7.2.0). -* (feat!) [#1024](https://github.com/cosmos/interchain-security/pull/1024) Throttle with retries, consumer changes. -* (fix!) [#1150](https://github.com/cosmos/interchain-security/pull/1150) Revert consumer packet data changes from #1037. -* (fix!) [#1146](https://github.com/cosmos/interchain-security/pull/1146) Proper deletion of pending packets. -* (feat!) [#1037](https://github.com/cosmos/interchain-security/pull/1037) Optimize pending packets storage on consumer, with migration. -* (feat) [#1164](https://github.com/cosmos/interchain-security/pull/1164) Introduce the gRPC query `/interchain_security/ccv/consumer/provider-info` and CLI command `interchain-security-cd q ccvconsumer provider-info` to retrieve provider info from the consumer chain. - ## v3.1.0 Date July 11th, 2023 @@ -74,6 +25,17 @@ Interchain Security v3 uses SDK 0.47 and IBC 7. * `[x/ccv/provider]` (fix) [#977](https://github.com/cosmos/interchain-security/pull/977) Avoids panicking the provider when an unbonding delegation was removed through a `CancelUnbondingDelegation` message. * `[x/ccv/democracy]` (feat) [#1019](https://github.com/cosmos/interchain-security/pull/1019) Whitelisting non-legacy params in the "democracy module" require the entire module to be whitelisted. +## v2.3.0-provider-lsm + +*November 15, 2023* + +* (fix!) [#1422](https://github.com/cosmos/interchain-security/pull/1422) Fix the misbehaviour handling by verifying the signatures of byzantine validators. + +## v2.2.0-provider-lsm + +### 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-provider-lsm Date: September 15th, 2023 @@ -223,4 +185,4 @@ RS consist of the following core features: In addition, RS has the following features: - **Key Assignment**: Enables validator operators to use different consensus keys for each consumer chain validator node that they operate. -- **Jail Throttling**: Enables the provider to slow down a "worst case scenario" attack where a malicious consumer binary attempts to jail a significant amount (> 2/3) of the voting power, effectively taking control of the provider. +- **Jail Throttling**: Enables the provider to slow down a "worst case scenario" attack where a malicious consumer binary attempts to jail a significant amount (> 2/3) of the voting power, effectively taking control of the provider. \ No newline at end of file From 3310d91ef8377ec157b8052fe8d37a1dd6a5d7dd Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 21 Nov 2023 15:17:14 +0100 Subject: [PATCH 80/82] update changelog --- CHANGELOG.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80bdec5792..4be3bbe606 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,17 +25,6 @@ Interchain Security v3 uses SDK 0.47 and IBC 7. * `[x/ccv/provider]` (fix) [#977](https://github.com/cosmos/interchain-security/pull/977) Avoids panicking the provider when an unbonding delegation was removed through a `CancelUnbondingDelegation` message. * `[x/ccv/democracy]` (feat) [#1019](https://github.com/cosmos/interchain-security/pull/1019) Whitelisting non-legacy params in the "democracy module" require the entire module to be whitelisted. -## v2.3.0-provider-lsm - -*November 15, 2023* - -* (fix!) [#1422](https://github.com/cosmos/interchain-security/pull/1422) Fix the misbehaviour handling by verifying the signatures of byzantine validators. - -## v2.2.0-provider-lsm - -### 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-provider-lsm Date: September 15th, 2023 @@ -185,4 +174,5 @@ RS consist of the following core features: In addition, RS has the following features: - **Key Assignment**: Enables validator operators to use different consensus keys for each consumer chain validator node that they operate. -- **Jail Throttling**: Enables the provider to slow down a "worst case scenario" attack where a malicious consumer binary attempts to jail a significant amount (> 2/3) of the voting power, effectively taking control of the provider. \ No newline at end of file +- **Jail Throttling**: Enables the provider to slow down a "worst case scenario" attack where a malicious consumer binary attempts to jail a significant amount (> 2/3) of the voting power, effectively taking control of the provider. + From 2d476c2df5da0caf19681e38d4361cde677a0fa9 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 21 Nov 2023 15:27:51 +0100 Subject: [PATCH 81/82] update changelog entries --- ...dd-cryptographic-verification-of-equivocation-feature.md | 4 +--- .../provider/1340-cryptographic-equivocation-feature.md | 6 ++++-- .../provider/1340-cryptographic-equivocation-feature.md | 6 ++++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md b/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md index b6a2d31dce..8d49bc2162 100644 --- a/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md +++ b/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md @@ -1,3 +1 @@ -- Add the cryptographic verification of equivocation feature to the - provider (cf. ADR-005 & ADR-013) and remove the equivocation proposal. - ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file +- Deprecate equivocation proposals. ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file diff --git a/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md b/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md index 6d84b9c8e4..4a90b46488 100644 --- a/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md +++ b/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md @@ -1,2 +1,4 @@ -- Add the cryptographic verification of equivocation feature to the provider - (cf. ADR-005 & ADR-013). ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file +- Introduce the cryptographic verification of equivocation feature to the provider + (cf. [ADR-005](/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md) + & [ADR-013](/docs/docs/adrs/adr-013-equivocation-slashing.md)). + ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md b/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md index 6d84b9c8e4..4a90b46488 100644 --- a/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md +++ b/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md @@ -1,2 +1,4 @@ -- Add the cryptographic verification of equivocation feature to the provider - (cf. ADR-005 & ADR-013). ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file +- Introduce the cryptographic verification of equivocation feature to the provider + (cf. [ADR-005](/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md) + & [ADR-013](/docs/docs/adrs/adr-013-equivocation-slashing.md)). + ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file From 03aada4af3243dbf739a12adfacc7b37232df694 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 21 Nov 2023 15:30:19 +0100 Subject: [PATCH 82/82] nits --- ...0-add-cryptographic-verification-of-equivocation-feature.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md b/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md index 8d49bc2162..c50662be72 100644 --- a/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md +++ b/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md @@ -1 +1,2 @@ -- Deprecate equivocation proposals. ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file +- Deprecate equivocation proposals. +([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file