From b33893530ad8d64977838f0fd132e6d5c87232d7 Mon Sep 17 00:00:00 2001 From: mpoke Date: Thu, 11 Jan 2024 14:13:21 +0100 Subject: [PATCH 01/18] cleanup ./changelog entries --- .changelog/epilogue.md | 198 +----------------- .../consumer/1146-pending-packets.md | 2 - .../consumer/1150-revert-wire-breaking.md | 2 - .../consumer/1244-validate-transfer.md | 2 - .../bug-fixes/consumer/1262-fee-pool-addr.md | 2 - .../bug-fixes/consumer/1295-migration.md | 2 - .../v3.2.0/dependencies/1196-bump-ibc.md | 3 - .../v3.2.0/dependencies/1258-bump-ibc.md | 3 - .../v3.2.0/dependencies/1258-bump-sdk.md | 3 - .../v3.2.0/dependencies/1259-bump-sdk.md | 3 - .../consumer/1024-jail-throttling-v2.md | 2 - .../consumer/1164-provider-info-query.md | 4 - .../provider/1076-export-timestamps.md | 2 - .../features/provider/1280-reward-denoms.md | 3 - .../improvements/1244-consumer-unbonding.md | 2 - .../consumer/1037-optimize-storage.md | 2 - .../v3.2.0/state-breaking/1196-bump-ibc.md | 3 - .../state-breaking/1244-consumer-unbonding.md | 2 - .../v3.2.0/state-breaking/1258-bump-ibc.md | 3 - .../v3.2.0/state-breaking/1258-bump-sdk.md | 3 - .../v3.2.0/state-breaking/1259-bump-sdk.md | 3 - .../consumer/1024-jail-throttling-v2.md | 2 - .../consumer/1037-optimize-storage.md | 2 - .../consumer/1146-pending-packets.md | 2 - .../consumer/1150-revert-wire-breaking.md | 2 - .../consumer/1244-validate-transfer.md | 2 - .../consumer/1262-fee-pool-addr.md | 2 - .../state-breaking/consumer/1295-migration.md | 2 - .../provider/1280-reward-denoms.md | 3 - .changelog/v3.2.0/summary.md | 1 - ...ic-verification-of-equivocation-feature.md | 2 - .../v3.3.0/dependencies/1373-bump-ibc.md | 3 - .../v3.3.0/features/1336-quint-model.md | 2 - .../1339-check-key-assignment-in-use.md | 3 - ...1340-cryptographic-equivocation-feature.md | 4 - .../improvements/1324-consumer-genesis.md | 16 -- .../v3.3.0/improvements/1350-cleanup-types.md | 3 - .../provider/1503-query-key-assignment.md | 1 - .../state-breaking/1324-consumer-genesis.md | 2 - .../state-breaking/1460-msg-validation.md | 3 - .../1339-check-key-assignment-in-use.md | 3 - ...1340-cryptographic-equivocation-feature.md | 4 - .changelog/v3.3.0/summary.md | 1 - 43 files changed, 2 insertions(+), 312 deletions(-) delete mode 100644 .changelog/v3.2.0/bug-fixes/consumer/1146-pending-packets.md delete mode 100644 .changelog/v3.2.0/bug-fixes/consumer/1150-revert-wire-breaking.md delete mode 100644 .changelog/v3.2.0/bug-fixes/consumer/1244-validate-transfer.md delete mode 100644 .changelog/v3.2.0/bug-fixes/consumer/1262-fee-pool-addr.md delete mode 100644 .changelog/v3.2.0/bug-fixes/consumer/1295-migration.md delete mode 100644 .changelog/v3.2.0/dependencies/1196-bump-ibc.md delete mode 100644 .changelog/v3.2.0/dependencies/1258-bump-ibc.md delete mode 100644 .changelog/v3.2.0/dependencies/1258-bump-sdk.md delete mode 100644 .changelog/v3.2.0/dependencies/1259-bump-sdk.md delete mode 100644 .changelog/v3.2.0/features/consumer/1024-jail-throttling-v2.md delete mode 100644 .changelog/v3.2.0/features/consumer/1164-provider-info-query.md delete mode 100644 .changelog/v3.2.0/features/provider/1076-export-timestamps.md delete mode 100644 .changelog/v3.2.0/features/provider/1280-reward-denoms.md delete mode 100644 .changelog/v3.2.0/improvements/1244-consumer-unbonding.md delete mode 100644 .changelog/v3.2.0/improvements/consumer/1037-optimize-storage.md delete mode 100644 .changelog/v3.2.0/state-breaking/1196-bump-ibc.md delete mode 100644 .changelog/v3.2.0/state-breaking/1244-consumer-unbonding.md delete mode 100644 .changelog/v3.2.0/state-breaking/1258-bump-ibc.md delete mode 100644 .changelog/v3.2.0/state-breaking/1258-bump-sdk.md delete mode 100644 .changelog/v3.2.0/state-breaking/1259-bump-sdk.md delete mode 100644 .changelog/v3.2.0/state-breaking/consumer/1024-jail-throttling-v2.md delete mode 100644 .changelog/v3.2.0/state-breaking/consumer/1037-optimize-storage.md delete mode 100644 .changelog/v3.2.0/state-breaking/consumer/1146-pending-packets.md delete mode 100644 .changelog/v3.2.0/state-breaking/consumer/1150-revert-wire-breaking.md delete mode 100644 .changelog/v3.2.0/state-breaking/consumer/1244-validate-transfer.md delete mode 100644 .changelog/v3.2.0/state-breaking/consumer/1262-fee-pool-addr.md delete mode 100644 .changelog/v3.2.0/state-breaking/consumer/1295-migration.md delete mode 100644 .changelog/v3.2.0/state-breaking/provider/1280-reward-denoms.md delete mode 100644 .changelog/v3.2.0/summary.md delete mode 100644 .changelog/v3.3.0/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md delete mode 100644 .changelog/v3.3.0/dependencies/1373-bump-ibc.md delete mode 100644 .changelog/v3.3.0/features/1336-quint-model.md delete mode 100644 .changelog/v3.3.0/features/provider/1339-check-key-assignment-in-use.md delete mode 100644 .changelog/v3.3.0/features/provider/1340-cryptographic-equivocation-feature.md delete mode 100644 .changelog/v3.3.0/improvements/1324-consumer-genesis.md delete mode 100644 .changelog/v3.3.0/improvements/1350-cleanup-types.md delete mode 100644 .changelog/v3.3.0/improvements/provider/1503-query-key-assignment.md delete mode 100644 .changelog/v3.3.0/state-breaking/1324-consumer-genesis.md delete mode 100644 .changelog/v3.3.0/state-breaking/1460-msg-validation.md delete mode 100644 .changelog/v3.3.0/state-breaking/provider/1339-check-key-assignment-in-use.md delete mode 100644 .changelog/v3.3.0/state-breaking/provider/1340-cryptographic-equivocation-feature.md delete mode 100644 .changelog/v3.3.0/summary.md diff --git a/.changelog/epilogue.md b/.changelog/epilogue.md index 88987ef0a0..15e2568e25 100644 --- a/.changelog/epilogue.md +++ b/.changelog/epilogue.md @@ -1,197 +1,3 @@ -## v3.1.0 +## Previous Versions -Date July 11th, 2023 - -A minor upgrade to v3.0.0, which removes the panic in the consumer ccv module which would occur in an emergency scenario where the ccv channel is closed. This release also fixes how a distribution related event is emitted, and bumps cometbft. - -* (feat) [#1127](https://github.com/cosmos/interchain-security/pull/1127) Remove consumer panic when ccv channel is closed -* (fix) [#720](https://github.com/cosmos/interchain-security/issues/720) Fix the attribute `AttributeDistributionTotal` value in `FeeDistribution` event emit. -* (deps) [#1119](https://github.com/cosmos/interchain-security/pull/1119) bump cometbft from `v0.37.1` to `0.37.2`. - -## v3.0.0 - -Date: June 21st, 2023 - -Interchain Security v3 uses SDK 0.47 and IBC 7. - -* (fix) [#1093](https://github.com/cosmos/interchain-security/pull/1093) Make SlashPacketData backward compatible when sending data over the wire -* (deps) [#1019](https://github.com/cosmos/interchain-security/pull/1019) Bump multiple dependencies. - * Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to [v0.47.3](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.3). - * Bump [ibc-go](https://github.com/cosmos/ibc-go) to [v7.1.0](https://github.com/cosmos/ibc-go/releases/tag/v7.1.0). - * Bump [CometBFT](https://github.com/cometbft/cometbft) to [v0.37.1](https://github.com/cometbft/cometbft/releases/tag/v0.37.1). -* `[x/ccv/provider]` (fix) [#945](https://github.com/cosmos/interchain-security/issues/945) Refactor `AfterUnbondingInitiated` to not panic when `PutUnbondingOnHold` returns error. -* `[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.4.0-lsm - -*November 20, 2023* - -* (fix) [#1439](https://github.com/cosmos/interchain-security/pull/1439) Fix unmarshaling for the CLI consumer double vote cmd. -* (feat!) [#1435](https://github.com/cosmos/interchain-security/pull/1435) Add height-base filter for consumer equivocation evidence. - -## v2.3.0-provider-lsm - -*November 15, 2023* - -❗ *This release is deprecated and should not be used in production.* - -* (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 - -❗ *This release is deprecated and should not be used in production.* - -### 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 - -* (feature!) [#1280](https://github.com/cosmos/interchain-security/pull/1280) provider proposal for changing reward denoms - -## 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](https://github.com/cosmos/interchain-security/blob/v2.0.0/x/ccv/provider/keeper/migration.go). - -Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state migrations. - -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 - -* MVP for standalone to consumer changeover, see [EPIC](https://github.com/cosmos/interchain-security/issues/756) -* MVP for soft opt out, see [EPIC](https://github.com/cosmos/interchain-security/issues/851) -* Various fixes, critical and non-critical -* Docs updates which should not affect production code - -## 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) consumer 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) 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) -* (feat) Add warnings when provider unbonding is shorter than consumer unbonding [#858](https://github.com/cosmos/interchain-security/pull/858) -* (chore) use go 1.19 [#899](https://github.com/cosmos/interchain-security/pull/899), [#840](https://github.com/cosmos/interchain-security/pull/840) -* (feat) Standalone to consumer changeover - recycle existing transfer channel [#832](https://github.com/cosmos/interchain-security/pull/832) -* (deps) Bump IBC [862](https://github.com/cosmos/interchain-security/pull/862) -* (testing) Add tests for soft opt out [#857](https://github.com/cosmos/interchain-security/pull/857) -* (feat) Standalone to consumer changeover - staking functionalities [#794](https://github.com/cosmos/interchain-security/pull/794) -* (fix) prevent provider from sending VSCPackets with multiple updates for the same validator [#850](https://github.com/cosmos/interchain-security/pull/850) -* (feat) Soft opt out [#833](https://github.com/cosmos/interchain-security/issues/833) -* (fix) Correctly handle VSC packet with duplicate val updates on consumer [#846](https://github.com/cosmos/interchain-security/pull/846) -* (deps) bump sdk to v0.45.15.ics [#805](https://github.com/cosmos/interchain-security/pull/805) -* (refactor) Remove spm module [#812](https://github.com/cosmos/interchain-security/pull/812) -* (feat) Standalone to consumer changeover part 1 [#757](https://github.com/cosmos/interchain-security/pull/757) -* (chore) Swap names of e2e and integration tests [#681](https://github.com/cosmos/interchain-security/pull/681) -* (fix) fix StopConsumerChain not running in cachedContext [#802](https://github.com/cosmos/interchain-security/pull/802). Also in earlier releases with different commit order! -* (docs) Introduce docs website [#759](https://github.com/cosmos/interchain-security/pull/759) -* (fix) Serialize correct byte prefix for SlashLogKey [#786](https://github.com/cosmos/interchain-security/pull/786) -* (feature) Improve keeper field validation [#766](https://github.com/cosmos/interchain-security/pull/766) -* (docs) Contributing guidelines [#744](https://github.com/cosmos/interchain-security/pull/744) -* (refactor) Key assignment type safety [#725](https://github.com/cosmos/interchain-security/pull/725) -* (fix) Update protos and fix deps [#752](https://github.com/cosmos/interchain-security/pull/752) -* (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 - -This is the first version of Interchain Security (ICS), also known as _Replicated Security_ (RS). -Replicated Security is a feature which will allow a chain -- referred to as the _provider_ -- to share security with other chains -- referred to as _consumers_. -This means that the provider's validator set will be granted the right to validate consumer chains. -The communication between the provider and the consumer chains is done through the IBC protocol over a unique, ordered channel (one for each consumer chain). Thus, RS is an IBC application. - -### Features / sub-protocols - -RS consist of the following core features: - -- **Channel Initialization**: Enables the provider to add new consumer chains. This process is governance-gated, i.e., to add a new consumer chain, a `ConsumerAdditionProposal` governance proposal must be sent to the provider and it must receive the necessary votes. -- **Validator Set Update**: Enables the provider to - (1) update the consumers on the voting power granted to validators (based on the changes in the active validator set on the provider chain), - and (2) ensure the timely completion of unbonding operations (e.g., undelegations). -- **Consumer Initiated Slashing**: Enables the provider to jail validators for downtime infractions on the consumer chains. -- **Reward Distribution**: Enables the consumers to transfer to the provider (over IBC) a portion of their block rewards as payment for the security provided. Once transferred, these rewards are distributed on the provider using the protocol in the [distribution module of Cosmos SDK](https://docs.cosmos.network/v0.45/modules/distribution/). -- **Consumer Chain Removal**: Enables the provider to remove a consumer either after a `ConsumerRemovalProposal` passes governance or after one of the timeout periods elapses -- `InitTimeoutPeriod`, `VscTimeoutPeriod`, `IBCTimeoutPeriod`. -- **Social Slashing**: Equivocation offenses (double signing etc.) on consumer chains are logged, and then can be used in a governance proposal to slash the validators responsible. - -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 +[CHANGELOG of previous versions](https://github.com/cosmos/interchain-security/blob/main/CHANGELOG.md) \ No newline at end of file diff --git a/.changelog/v3.2.0/bug-fixes/consumer/1146-pending-packets.md b/.changelog/v3.2.0/bug-fixes/consumer/1146-pending-packets.md deleted file mode 100644 index 0bab707fec..0000000000 --- a/.changelog/v3.2.0/bug-fixes/consumer/1146-pending-packets.md +++ /dev/null @@ -1,2 +0,0 @@ -- Fix deletion of pending packets that may cause duplicate sends - ([\#1146](https://github.com/cosmos/interchain-security/pull/1146)) \ No newline at end of file diff --git a/.changelog/v3.2.0/bug-fixes/consumer/1150-revert-wire-breaking.md b/.changelog/v3.2.0/bug-fixes/consumer/1150-revert-wire-breaking.md deleted file mode 100644 index 067448e770..0000000000 --- a/.changelog/v3.2.0/bug-fixes/consumer/1150-revert-wire-breaking.md +++ /dev/null @@ -1,2 +0,0 @@ -- Remove `idx` field from the `ccv.ConsumerPacketData` type as this would break the - wire ([\#1150](https://github.com/cosmos/interchain-security/pull/1150)) \ No newline at end of file diff --git a/.changelog/v3.2.0/bug-fixes/consumer/1244-validate-transfer.md b/.changelog/v3.2.0/bug-fixes/consumer/1244-validate-transfer.md deleted file mode 100644 index 2d94c79c75..0000000000 --- a/.changelog/v3.2.0/bug-fixes/consumer/1244-validate-transfer.md +++ /dev/null @@ -1,2 +0,0 @@ -- Validate token transfer messages before calling `Transfer()`. - ([\#1244](https://github.com/cosmos/interchain-security/pull/1244)) \ No newline at end of file diff --git a/.changelog/v3.2.0/bug-fixes/consumer/1262-fee-pool-addr.md b/.changelog/v3.2.0/bug-fixes/consumer/1262-fee-pool-addr.md deleted file mode 100644 index bbb54db2e3..0000000000 --- a/.changelog/v3.2.0/bug-fixes/consumer/1262-fee-pool-addr.md +++ /dev/null @@ -1,2 +0,0 @@ -- Remove incorrect address validation on `ProviderFeePoolAddrStr` param. - ([\#1262](https://github.com/cosmos/interchain-security/pull/1262)) \ No newline at end of file diff --git a/.changelog/v3.2.0/bug-fixes/consumer/1295-migration.md b/.changelog/v3.2.0/bug-fixes/consumer/1295-migration.md deleted file mode 100644 index 739b08dc39..0000000000 --- a/.changelog/v3.2.0/bug-fixes/consumer/1295-migration.md +++ /dev/null @@ -1,2 +0,0 @@ -- Increment consumer consensus version and register consumer migration. - ([\#1295](https://github.com/cosmos/interchain-security/pull/1295)) \ No newline at end of file diff --git a/.changelog/v3.2.0/dependencies/1196-bump-ibc.md b/.changelog/v3.2.0/dependencies/1196-bump-ibc.md deleted file mode 100644 index fcf4450150..0000000000 --- a/.changelog/v3.2.0/dependencies/1196-bump-ibc.md +++ /dev/null @@ -1,3 +0,0 @@ -- Bump [ibc-go](https://github.com/cosmos/ibc-go) to - [v7.2.0](https://github.com/cosmos/ibc-go/releases/tag/v7.2.0). - ([\#1196](https://github.com/cosmos/interchain-security/pull/1196)) \ No newline at end of file diff --git a/.changelog/v3.2.0/dependencies/1258-bump-ibc.md b/.changelog/v3.2.0/dependencies/1258-bump-ibc.md deleted file mode 100644 index 68c6e2b104..0000000000 --- a/.changelog/v3.2.0/dependencies/1258-bump-ibc.md +++ /dev/null @@ -1,3 +0,0 @@ -- Bump [ibc-go](https://github.com/cosmos/ibc-go) to - [v7.3.0](https://github.com/cosmos/ibc-go/releases/tag/v7.3.0). - ([\#1258](https://github.com/cosmos/interchain-security/pull/1258)) \ No newline at end of file diff --git a/.changelog/v3.2.0/dependencies/1258-bump-sdk.md b/.changelog/v3.2.0/dependencies/1258-bump-sdk.md deleted file mode 100644 index 7344fac97e..0000000000 --- a/.changelog/v3.2.0/dependencies/1258-bump-sdk.md +++ /dev/null @@ -1,3 +0,0 @@ -- Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to - [v0.47.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.4). - ([\#1258](https://github.com/cosmos/interchain-security/pull/1258)) \ No newline at end of file diff --git a/.changelog/v3.2.0/dependencies/1259-bump-sdk.md b/.changelog/v3.2.0/dependencies/1259-bump-sdk.md deleted file mode 100644 index 247c623b7d..0000000000 --- a/.changelog/v3.2.0/dependencies/1259-bump-sdk.md +++ /dev/null @@ -1,3 +0,0 @@ -- Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to - [v0.47.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.5). - ([\#1259](https://github.com/cosmos/interchain-security/pull/1259)) \ No newline at end of file diff --git a/.changelog/v3.2.0/features/consumer/1024-jail-throttling-v2.md b/.changelog/v3.2.0/features/consumer/1024-jail-throttling-v2.md deleted file mode 100644 index 7570facb34..0000000000 --- a/.changelog/v3.2.0/features/consumer/1024-jail-throttling-v2.md +++ /dev/null @@ -1,2 +0,0 @@ -- Add the consumer-side changes for jail throttling with retries (cf. ADR 008). - ([\#1024](https://github.com/cosmos/interchain-security/pull/1024)) \ No newline at end of file diff --git a/.changelog/v3.2.0/features/consumer/1164-provider-info-query.md b/.changelog/v3.2.0/features/consumer/1164-provider-info-query.md deleted file mode 100644 index fc1d27b964..0000000000 --- a/.changelog/v3.2.0/features/consumer/1164-provider-info-query.md +++ /dev/null @@ -1,4 +0,0 @@ -- 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. - ([\#1164](https://github.com/cosmos/interchain-security/pull/1164)) \ No newline at end of file diff --git a/.changelog/v3.2.0/features/provider/1076-export-timestamps.md b/.changelog/v3.2.0/features/provider/1076-export-timestamps.md deleted file mode 100644 index f2a8608f8b..0000000000 --- a/.changelog/v3.2.0/features/provider/1076-export-timestamps.md +++ /dev/null @@ -1,2 +0,0 @@ -- Add `InitTimeoutTimestamps` and `ExportedVscSendTimestamps` to exported - genesis. ([\#1076](https://github.com/cosmos/interchain-security/pull/1076)) \ No newline at end of file diff --git a/.changelog/v3.2.0/features/provider/1280-reward-denoms.md b/.changelog/v3.2.0/features/provider/1280-reward-denoms.md deleted file mode 100644 index c1f3659a44..0000000000 --- a/.changelog/v3.2.0/features/provider/1280-reward-denoms.md +++ /dev/null @@ -1,3 +0,0 @@ -- Add a governance proposal for setting on the provider the denominations for - rewards from consumer chains. - ([\#1280](https://github.com/cosmos/interchain-security/pull/1280)) \ No newline at end of file diff --git a/.changelog/v3.2.0/improvements/1244-consumer-unbonding.md b/.changelog/v3.2.0/improvements/1244-consumer-unbonding.md deleted file mode 100644 index 4a8504e4ce..0000000000 --- a/.changelog/v3.2.0/improvements/1244-consumer-unbonding.md +++ /dev/null @@ -1,2 +0,0 @@ -- Update the default consumer unbonding period to 2 weeks. - ([\#1244](https://github.com/cosmos/interchain-security/pull/1244)) \ No newline at end of file diff --git a/.changelog/v3.2.0/improvements/consumer/1037-optimize-storage.md b/.changelog/v3.2.0/improvements/consumer/1037-optimize-storage.md deleted file mode 100644 index 726906420b..0000000000 --- a/.changelog/v3.2.0/improvements/consumer/1037-optimize-storage.md +++ /dev/null @@ -1,2 +0,0 @@ -- Optimize pending packets storage on consumer, with migration. - ([\#1037](https://github.com/cosmos/interchain-security/pull/1037)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/1196-bump-ibc.md b/.changelog/v3.2.0/state-breaking/1196-bump-ibc.md deleted file mode 100644 index fcf4450150..0000000000 --- a/.changelog/v3.2.0/state-breaking/1196-bump-ibc.md +++ /dev/null @@ -1,3 +0,0 @@ -- Bump [ibc-go](https://github.com/cosmos/ibc-go) to - [v7.2.0](https://github.com/cosmos/ibc-go/releases/tag/v7.2.0). - ([\#1196](https://github.com/cosmos/interchain-security/pull/1196)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/1244-consumer-unbonding.md b/.changelog/v3.2.0/state-breaking/1244-consumer-unbonding.md deleted file mode 100644 index 4a8504e4ce..0000000000 --- a/.changelog/v3.2.0/state-breaking/1244-consumer-unbonding.md +++ /dev/null @@ -1,2 +0,0 @@ -- Update the default consumer unbonding period to 2 weeks. - ([\#1244](https://github.com/cosmos/interchain-security/pull/1244)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/1258-bump-ibc.md b/.changelog/v3.2.0/state-breaking/1258-bump-ibc.md deleted file mode 100644 index 68c6e2b104..0000000000 --- a/.changelog/v3.2.0/state-breaking/1258-bump-ibc.md +++ /dev/null @@ -1,3 +0,0 @@ -- Bump [ibc-go](https://github.com/cosmos/ibc-go) to - [v7.3.0](https://github.com/cosmos/ibc-go/releases/tag/v7.3.0). - ([\#1258](https://github.com/cosmos/interchain-security/pull/1258)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/1258-bump-sdk.md b/.changelog/v3.2.0/state-breaking/1258-bump-sdk.md deleted file mode 100644 index 7344fac97e..0000000000 --- a/.changelog/v3.2.0/state-breaking/1258-bump-sdk.md +++ /dev/null @@ -1,3 +0,0 @@ -- Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to - [v0.47.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.4). - ([\#1258](https://github.com/cosmos/interchain-security/pull/1258)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/1259-bump-sdk.md b/.changelog/v3.2.0/state-breaking/1259-bump-sdk.md deleted file mode 100644 index 247c623b7d..0000000000 --- a/.changelog/v3.2.0/state-breaking/1259-bump-sdk.md +++ /dev/null @@ -1,3 +0,0 @@ -- Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to - [v0.47.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.5). - ([\#1259](https://github.com/cosmos/interchain-security/pull/1259)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/consumer/1024-jail-throttling-v2.md b/.changelog/v3.2.0/state-breaking/consumer/1024-jail-throttling-v2.md deleted file mode 100644 index 7570facb34..0000000000 --- a/.changelog/v3.2.0/state-breaking/consumer/1024-jail-throttling-v2.md +++ /dev/null @@ -1,2 +0,0 @@ -- Add the consumer-side changes for jail throttling with retries (cf. ADR 008). - ([\#1024](https://github.com/cosmos/interchain-security/pull/1024)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/consumer/1037-optimize-storage.md b/.changelog/v3.2.0/state-breaking/consumer/1037-optimize-storage.md deleted file mode 100644 index 726906420b..0000000000 --- a/.changelog/v3.2.0/state-breaking/consumer/1037-optimize-storage.md +++ /dev/null @@ -1,2 +0,0 @@ -- Optimize pending packets storage on consumer, with migration. - ([\#1037](https://github.com/cosmos/interchain-security/pull/1037)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/consumer/1146-pending-packets.md b/.changelog/v3.2.0/state-breaking/consumer/1146-pending-packets.md deleted file mode 100644 index a10d75a505..0000000000 --- a/.changelog/v3.2.0/state-breaking/consumer/1146-pending-packets.md +++ /dev/null @@ -1,2 +0,0 @@ -- Fix deletion of pending packets that may cause duplicate sends - ([\#1146](https://github.com/cosmos/interchain-security/pull/1146)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/consumer/1150-revert-wire-breaking.md b/.changelog/v3.2.0/state-breaking/consumer/1150-revert-wire-breaking.md deleted file mode 100644 index 067448e770..0000000000 --- a/.changelog/v3.2.0/state-breaking/consumer/1150-revert-wire-breaking.md +++ /dev/null @@ -1,2 +0,0 @@ -- Remove `idx` field from the `ccv.ConsumerPacketData` type as this would break the - wire ([\#1150](https://github.com/cosmos/interchain-security/pull/1150)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/consumer/1244-validate-transfer.md b/.changelog/v3.2.0/state-breaking/consumer/1244-validate-transfer.md deleted file mode 100644 index 2d94c79c75..0000000000 --- a/.changelog/v3.2.0/state-breaking/consumer/1244-validate-transfer.md +++ /dev/null @@ -1,2 +0,0 @@ -- Validate token transfer messages before calling `Transfer()`. - ([\#1244](https://github.com/cosmos/interchain-security/pull/1244)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/consumer/1262-fee-pool-addr.md b/.changelog/v3.2.0/state-breaking/consumer/1262-fee-pool-addr.md deleted file mode 100644 index bbb54db2e3..0000000000 --- a/.changelog/v3.2.0/state-breaking/consumer/1262-fee-pool-addr.md +++ /dev/null @@ -1,2 +0,0 @@ -- Remove incorrect address validation on `ProviderFeePoolAddrStr` param. - ([\#1262](https://github.com/cosmos/interchain-security/pull/1262)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/consumer/1295-migration.md b/.changelog/v3.2.0/state-breaking/consumer/1295-migration.md deleted file mode 100644 index 739b08dc39..0000000000 --- a/.changelog/v3.2.0/state-breaking/consumer/1295-migration.md +++ /dev/null @@ -1,2 +0,0 @@ -- Increment consumer consensus version and register consumer migration. - ([\#1295](https://github.com/cosmos/interchain-security/pull/1295)) \ No newline at end of file diff --git a/.changelog/v3.2.0/state-breaking/provider/1280-reward-denoms.md b/.changelog/v3.2.0/state-breaking/provider/1280-reward-denoms.md deleted file mode 100644 index c1f3659a44..0000000000 --- a/.changelog/v3.2.0/state-breaking/provider/1280-reward-denoms.md +++ /dev/null @@ -1,3 +0,0 @@ -- Add a governance proposal for setting on the provider the denominations for - rewards from consumer chains. - ([\#1280](https://github.com/cosmos/interchain-security/pull/1280)) \ No newline at end of file diff --git a/.changelog/v3.2.0/summary.md b/.changelog/v3.2.0/summary.md deleted file mode 100644 index e7b2c7d6d4..0000000000 --- a/.changelog/v3.2.0/summary.md +++ /dev/null @@ -1 +0,0 @@ -*November 24, 2023* diff --git a/.changelog/v3.3.0/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md b/.changelog/v3.3.0/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md deleted file mode 100644 index c50662be72..0000000000 --- a/.changelog/v3.3.0/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md +++ /dev/null @@ -1,2 +0,0 @@ -- Deprecate equivocation proposals. -([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file diff --git a/.changelog/v3.3.0/dependencies/1373-bump-ibc.md b/.changelog/v3.3.0/dependencies/1373-bump-ibc.md deleted file mode 100644 index efe4e0c286..0000000000 --- a/.changelog/v3.3.0/dependencies/1373-bump-ibc.md +++ /dev/null @@ -1,3 +0,0 @@ -- Bump [ibc-go](https://github.com/cosmos/ibc-go) to - [v7.3.1](https://github.com/cosmos/ibc-go/releases/tag/v7.3.1). - ([\#1373](https://github.com/cosmos/interchain-security/pull/1373)) \ No newline at end of file diff --git a/.changelog/v3.3.0/features/1336-quint-model.md b/.changelog/v3.3.0/features/1336-quint-model.md deleted file mode 100644 index 96c4562b32..0000000000 --- a/.changelog/v3.3.0/features/1336-quint-model.md +++ /dev/null @@ -1,2 +0,0 @@ -- Add Quint model of Replicated Security. - ([\#1336](https://github.com/cosmos/interchain-security/pull/1336)) \ No newline at end of file diff --git a/.changelog/v3.3.0/features/provider/1339-check-key-assignment-in-use.md b/.changelog/v3.3.0/features/provider/1339-check-key-assignment-in-use.md deleted file mode 100644 index 9f274f7bb4..0000000000 --- a/.changelog/v3.3.0/features/provider/1339-check-key-assignment-in-use.md +++ /dev/null @@ -1,3 +0,0 @@ -- Update how consumer-assigned keys are checked when a validator is - created on the provider. - ([\#1339](https://github.com/cosmos/interchain-security/pull/1339)) \ No newline at end of file diff --git a/.changelog/v3.3.0/features/provider/1340-cryptographic-equivocation-feature.md b/.changelog/v3.3.0/features/provider/1340-cryptographic-equivocation-feature.md deleted file mode 100644 index 5437fba186..0000000000 --- a/.changelog/v3.3.0/features/provider/1340-cryptographic-equivocation-feature.md +++ /dev/null @@ -1,4 +0,0 @@ -- 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/v3.3.0/improvements/1324-consumer-genesis.md b/.changelog/v3.3.0/improvements/1324-consumer-genesis.md deleted file mode 100644 index a727be8341..0000000000 --- a/.changelog/v3.3.0/improvements/1324-consumer-genesis.md +++ /dev/null @@ -1,16 +0,0 @@ -- Split out consumer genesis state to reduce shared data between provider and - consumer. ([\#1324](https://github.com/cosmos/interchain-security/pull/1324)) - - Note: This breaks json format used by augmenting Genesis files of consumer - chains with consumer genesis content exported from provider chain. Consumer - Genesis content exported from a provider chain using major version 1, 2 or 3 - of the provider module needs to be transformed with the transformation command - introduced by this PR: - ``` - Transform the consumer genesis file from a provider version v1, v2 or v3 to a version supported by this consumer. Result is printed to STDOUT. - - Example: - $ transform /path/to/ccv_consumer_genesis.json - - Usage: - interchain-security-cd genesis transform [genesis-file] [flags] - ``` \ No newline at end of file diff --git a/.changelog/v3.3.0/improvements/1350-cleanup-types.md b/.changelog/v3.3.0/improvements/1350-cleanup-types.md deleted file mode 100644 index 6e26fc3992..0000000000 --- a/.changelog/v3.3.0/improvements/1350-cleanup-types.md +++ /dev/null @@ -1,3 +0,0 @@ -- Refactor shared events, codecs and errors assign to - consumer and provider dedicated types where possible. - ([\#1350](https://github.com/cosmos/interchain-security/pull/1350)) \ No newline at end of file diff --git a/.changelog/v3.3.0/improvements/provider/1503-query-key-assignment.md b/.changelog/v3.3.0/improvements/provider/1503-query-key-assignment.md deleted file mode 100644 index 62b505ec01..0000000000 --- a/.changelog/v3.3.0/improvements/provider/1503-query-key-assignment.md +++ /dev/null @@ -1 +0,0 @@ -- Add `QueryAllPairsValConAddrByConsumerChainID` method to get list of all pairs `valConsensus` address by `Consummer chainID`. ([\#1503](https://github.com/cosmos/interchain-security/pull/1503)) \ No newline at end of file diff --git a/.changelog/v3.3.0/state-breaking/1324-consumer-genesis.md b/.changelog/v3.3.0/state-breaking/1324-consumer-genesis.md deleted file mode 100644 index b47f7199fd..0000000000 --- a/.changelog/v3.3.0/state-breaking/1324-consumer-genesis.md +++ /dev/null @@ -1,2 +0,0 @@ -- Split out consumer genesis state to reduce shared data between provider and - consumer. ([\#1324](https://github.com/cosmos/interchain-security/pull/1324)) \ No newline at end of file diff --git a/.changelog/v3.3.0/state-breaking/1460-msg-validation.md b/.changelog/v3.3.0/state-breaking/1460-msg-validation.md deleted file mode 100644 index 46d18bd4c9..0000000000 --- a/.changelog/v3.3.0/state-breaking/1460-msg-validation.md +++ /dev/null @@ -1,3 +0,0 @@ -- Improve validation of IBC packet data and provider messages. Also, - enable the provider to validate consumer packets before handling them. - ([\#1460](https://github.com/cosmos/interchain-security/pull/1460)) \ No newline at end of file diff --git a/.changelog/v3.3.0/state-breaking/provider/1339-check-key-assignment-in-use.md b/.changelog/v3.3.0/state-breaking/provider/1339-check-key-assignment-in-use.md deleted file mode 100644 index 2890582ba8..0000000000 --- a/.changelog/v3.3.0/state-breaking/provider/1339-check-key-assignment-in-use.md +++ /dev/null @@ -1,3 +0,0 @@ -- Change the states by adding a consumer key for each chain that is - not yet registered meaning for which the gov proposal has not passed. - ([\#1339](https://github.com/cosmos/interchain-security/pull/1339)) \ No newline at end of file diff --git a/.changelog/v3.3.0/state-breaking/provider/1340-cryptographic-equivocation-feature.md b/.changelog/v3.3.0/state-breaking/provider/1340-cryptographic-equivocation-feature.md deleted file mode 100644 index 5437fba186..0000000000 --- a/.changelog/v3.3.0/state-breaking/provider/1340-cryptographic-equivocation-feature.md +++ /dev/null @@ -1,4 +0,0 @@ -- 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/v3.3.0/summary.md b/.changelog/v3.3.0/summary.md deleted file mode 100644 index e556c0f0ca..0000000000 --- a/.changelog/v3.3.0/summary.md +++ /dev/null @@ -1 +0,0 @@ -*January 5, 2024* From 3165f03488bb2e11c45f2fae9620a9cebf833c18 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Thu, 11 Jan 2024 15:40:13 +0100 Subject: [PATCH 02/18] docs: changelog and release notes for v4.0.0 (#1564) * add v4.0.0 section to changelog * add release notes --- .changelog/unreleased/.gitkeep | 0 .../consumer/1458-consumer-genesis-cli-fix.md | 0 .../1458-consumer-genesis-cli-fix.md | 0 .../bug-fixes/1460-msg-validation.md | 0 .../bug-fixes/consumer/1549-soft-opt-out.md | 0 .../dependencies/1557-bump-go.md | 0 .../dependencies/1558-bump-comet.md | 0 .../dependencies/1558-bump-sdk.md | 0 .../provider/1321-jail-throttling-v2.md | 0 .../consumer/1549-soft-opt-out.md | 0 .../provider/1321-jail-throttling-v2.md | 0 .changelog/v4.0.0/summary.md | 1 + CHANGELOG.md | 365 ++---------------- RELEASE_NOTES.md | 36 +- 14 files changed, 36 insertions(+), 366 deletions(-) create mode 100644 .changelog/unreleased/.gitkeep rename .changelog/{unreleased => v4.0.0}/api-breaking/consumer/1458-consumer-genesis-cli-fix.md (100%) rename .changelog/{unreleased => v4.0.0}/bug-fixes/1458-consumer-genesis-cli-fix.md (100%) rename .changelog/{unreleased => v4.0.0}/bug-fixes/1460-msg-validation.md (100%) rename .changelog/{unreleased => v4.0.0}/bug-fixes/consumer/1549-soft-opt-out.md (100%) rename .changelog/{unreleased => v4.0.0}/dependencies/1557-bump-go.md (100%) rename .changelog/{unreleased => v4.0.0}/dependencies/1558-bump-comet.md (100%) rename .changelog/{unreleased => v4.0.0}/dependencies/1558-bump-sdk.md (100%) rename .changelog/{unreleased => v4.0.0}/features/provider/1321-jail-throttling-v2.md (100%) rename .changelog/{unreleased => v4.0.0}/state-breaking/consumer/1549-soft-opt-out.md (100%) rename .changelog/{unreleased => v4.0.0}/state-breaking/provider/1321-jail-throttling-v2.md (100%) create mode 100644 .changelog/v4.0.0/summary.md diff --git a/.changelog/unreleased/.gitkeep b/.changelog/unreleased/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.changelog/unreleased/api-breaking/consumer/1458-consumer-genesis-cli-fix.md b/.changelog/v4.0.0/api-breaking/consumer/1458-consumer-genesis-cli-fix.md similarity index 100% rename from .changelog/unreleased/api-breaking/consumer/1458-consumer-genesis-cli-fix.md rename to .changelog/v4.0.0/api-breaking/consumer/1458-consumer-genesis-cli-fix.md diff --git a/.changelog/unreleased/bug-fixes/1458-consumer-genesis-cli-fix.md b/.changelog/v4.0.0/bug-fixes/1458-consumer-genesis-cli-fix.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1458-consumer-genesis-cli-fix.md rename to .changelog/v4.0.0/bug-fixes/1458-consumer-genesis-cli-fix.md diff --git a/.changelog/unreleased/bug-fixes/1460-msg-validation.md b/.changelog/v4.0.0/bug-fixes/1460-msg-validation.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1460-msg-validation.md rename to .changelog/v4.0.0/bug-fixes/1460-msg-validation.md diff --git a/.changelog/unreleased/bug-fixes/consumer/1549-soft-opt-out.md b/.changelog/v4.0.0/bug-fixes/consumer/1549-soft-opt-out.md similarity index 100% rename from .changelog/unreleased/bug-fixes/consumer/1549-soft-opt-out.md rename to .changelog/v4.0.0/bug-fixes/consumer/1549-soft-opt-out.md diff --git a/.changelog/unreleased/dependencies/1557-bump-go.md b/.changelog/v4.0.0/dependencies/1557-bump-go.md similarity index 100% rename from .changelog/unreleased/dependencies/1557-bump-go.md rename to .changelog/v4.0.0/dependencies/1557-bump-go.md diff --git a/.changelog/unreleased/dependencies/1558-bump-comet.md b/.changelog/v4.0.0/dependencies/1558-bump-comet.md similarity index 100% rename from .changelog/unreleased/dependencies/1558-bump-comet.md rename to .changelog/v4.0.0/dependencies/1558-bump-comet.md diff --git a/.changelog/unreleased/dependencies/1558-bump-sdk.md b/.changelog/v4.0.0/dependencies/1558-bump-sdk.md similarity index 100% rename from .changelog/unreleased/dependencies/1558-bump-sdk.md rename to .changelog/v4.0.0/dependencies/1558-bump-sdk.md diff --git a/.changelog/unreleased/features/provider/1321-jail-throttling-v2.md b/.changelog/v4.0.0/features/provider/1321-jail-throttling-v2.md similarity index 100% rename from .changelog/unreleased/features/provider/1321-jail-throttling-v2.md rename to .changelog/v4.0.0/features/provider/1321-jail-throttling-v2.md diff --git a/.changelog/unreleased/state-breaking/consumer/1549-soft-opt-out.md b/.changelog/v4.0.0/state-breaking/consumer/1549-soft-opt-out.md similarity index 100% rename from .changelog/unreleased/state-breaking/consumer/1549-soft-opt-out.md rename to .changelog/v4.0.0/state-breaking/consumer/1549-soft-opt-out.md diff --git a/.changelog/unreleased/state-breaking/provider/1321-jail-throttling-v2.md b/.changelog/v4.0.0/state-breaking/provider/1321-jail-throttling-v2.md similarity index 100% rename from .changelog/unreleased/state-breaking/provider/1321-jail-throttling-v2.md rename to .changelog/v4.0.0/state-breaking/provider/1321-jail-throttling-v2.md diff --git a/.changelog/v4.0.0/summary.md b/.changelog/v4.0.0/summary.md new file mode 100644 index 0000000000..4561a0c2f8 --- /dev/null +++ b/.changelog/v4.0.0/summary.md @@ -0,0 +1 @@ +*January 11, 2024* diff --git a/CHANGELOG.md b/CHANGELOG.md index 4129c4d26f..365b7ea805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,367 +1,56 @@ # CHANGELOG -## v3.3.0 +## v4.0.0 -*January 5, 2024* +*January 11, 2024* ### API BREAKING -- [Provider](x/ccv/provider) - - Deprecate equivocation proposals. - ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) - -### DEPENDENCIES - -- Bump [ibc-go](https://github.com/cosmos/ibc-go) to - [v7.3.1](https://github.com/cosmos/ibc-go/releases/tag/v7.3.1). - ([\#1373](https://github.com/cosmos/interchain-security/pull/1373)) - -### FEATURES - -- General - - Add Quint model of Replicated Security. - ([\#1336](https://github.com/cosmos/interchain-security/pull/1336)) -- [Provider](x/ccv/provider) - - Update how consumer-assigned keys are checked when a validator is - created on the provider. - ([\#1339](https://github.com/cosmos/interchain-security/pull/1339)) - - 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)) - -### IMPROVEMENTS - -- General - - Split out consumer genesis state to reduce shared data between provider and - consumer. ([\#1324](https://github.com/cosmos/interchain-security/pull/1324)) - - Note: This breaks json format used by augmenting Genesis files of consumer - chains with consumer genesis content exported from provider chain. Consumer - Genesis content exported from a provider chain using major version 1, 2 or 3 - of the provider module needs to be transformed with the transformation command - introduced by this PR: - ``` - Transform the consumer genesis file from a provider version v1, v2 or v3 to a version supported by this consumer. Result is printed to STDOUT. - - Example: - $ transform /path/to/ccv_consumer_genesis.json - - Usage: - interchain-security-cd genesis transform [genesis-file] [flags] - ``` - - Refactor shared events, codecs and errors assign to - consumer and provider dedicated types where possible. - ([\#1350](https://github.com/cosmos/interchain-security/pull/1350)) -- [Provider](x/ccv/provider) - - Add `QueryAllPairsValConAddrByConsumerChainID` method to get list of all pairs `valConsensus` address by `Consummer chainID`. ([\#1503](https://github.com/cosmos/interchain-security/pull/1503)) +- [Consumer](x/ccv/consumer) + - Fix a bug in consmer genesis file transform CLI command. + ([\#1458](https://github.com/cosmos/interchain-security/pull/1458)) -### STATE BREAKING +### BUG FIXES - General - - Split out consumer genesis state to reduce shared data between provider and - consumer. ([\#1324](https://github.com/cosmos/interchain-security/pull/1324)) + - Fix a bug in consmer genesis file transform CLI command. + ([\#1458](https://github.com/cosmos/interchain-security/pull/1458)) - Improve validation of IBC packet data and provider messages. Also, enable the provider to validate consumer packets before handling them. ([\#1460](https://github.com/cosmos/interchain-security/pull/1460)) -- [Provider](x/ccv/provider) - - Change the states by adding a consumer key for each chain that is - not yet registered meaning for which the gov proposal has not passed. - ([\#1339](https://github.com/cosmos/interchain-security/pull/1339)) - - 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)) - -## v3.2.0 - -*November 24, 2023* - -### BUG FIXES - - [Consumer](x/ccv/consumer) - - Fix deletion of pending packets that may cause duplicate sends - ([\#1146](https://github.com/cosmos/interchain-security/pull/1146)) - - Remove `idx` field from the `ccv.ConsumerPacketData` type as this would break the - wire ([\#1150](https://github.com/cosmos/interchain-security/pull/1150)) - - Validate token transfer messages before calling `Transfer()`. - ([\#1244](https://github.com/cosmos/interchain-security/pull/1244)) - - Remove incorrect address validation on `ProviderFeePoolAddrStr` param. - ([\#1262](https://github.com/cosmos/interchain-security/pull/1262)) - - Increment consumer consensus version and register consumer migration. - ([\#1295](https://github.com/cosmos/interchain-security/pull/1295)) + - Avoid jailing validators immediately once they can no longer opt-out from + validating consumer chains. + ([\#1549](https://github.com/cosmos/interchain-security/pull/1549)) ### DEPENDENCIES -- Bump [ibc-go](https://github.com/cosmos/ibc-go) to - [v7.2.0](https://github.com/cosmos/ibc-go/releases/tag/v7.2.0). - ([\#1196](https://github.com/cosmos/interchain-security/pull/1196)) -- Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to - [v0.47.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.4). - ([\#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). - ([\#1258](https://github.com/cosmos/interchain-security/pull/1258)) +- Bump Golang to v1.21 + ([\#1557](https://github.com/cosmos/interchain-security/pull/1557)) - Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to - [v0.47.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.5). - ([\#1259](https://github.com/cosmos/interchain-security/pull/1259)) + [v0.47.7](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.7). + ([\#1558](https://github.com/cosmos/interchain-security/pull/1558)) +- Bump [CometBFT](https://github.com/cometbft/cometbft) to + [v0.37.4](https://github.com/cometbft/cometbft/releases/tag/v0.37.4). + ([\#1558](https://github.com/cosmos/interchain-security/pull/1558)) ### FEATURES -- [Consumer](x/ccv/consumer) - - Add the consumer-side changes for jail throttling with retries (cf. ADR 008). - ([\#1024](https://github.com/cosmos/interchain-security/pull/1024)) - - 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. - ([\#1164](https://github.com/cosmos/interchain-security/pull/1164)) - [Provider](x/ccv/provider) - - Add `InitTimeoutTimestamps` and `ExportedVscSendTimestamps` to exported - genesis. ([\#1076](https://github.com/cosmos/interchain-security/pull/1076)) - - Add a governance proposal for setting on the provider the denominations for - rewards from consumer chains. - ([\#1280](https://github.com/cosmos/interchain-security/pull/1280)) - -### IMPROVEMENTS - -- General - - Update the default consumer unbonding period to 2 weeks. - ([\#1244](https://github.com/cosmos/interchain-security/pull/1244)) -- [Consumer](x/ccv/consumer) - - Optimize pending packets storage on consumer, with migration. - ([\#1037](https://github.com/cosmos/interchain-security/pull/1037)) + - Add the provider-side changes for jail throttling with retries (cf. ADR 008). + ([\#1321](https://github.com/cosmos/interchain-security/pull/1321)) ### STATE BREAKING -- General - - Bump [ibc-go](https://github.com/cosmos/ibc-go) to - [v7.2.0](https://github.com/cosmos/ibc-go/releases/tag/v7.2.0). - ([\#1196](https://github.com/cosmos/interchain-security/pull/1196)) - - Update the default consumer unbonding period to 2 weeks. - ([\#1244](https://github.com/cosmos/interchain-security/pull/1244)) - - Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to - [v0.47.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.4). - ([\#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). - ([\#1258](https://github.com/cosmos/interchain-security/pull/1258)) - - Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to - [v0.47.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.5). - ([\#1259](https://github.com/cosmos/interchain-security/pull/1259)) - [Consumer](x/ccv/consumer) - - Add the consumer-side changes for jail throttling with retries (cf. ADR 008). - ([\#1024](https://github.com/cosmos/interchain-security/pull/1024)) - - Optimize pending packets storage on consumer, with migration. - ([\#1037](https://github.com/cosmos/interchain-security/pull/1037)) - - Fix deletion of pending packets that may cause duplicate sends - ([\#1146](https://github.com/cosmos/interchain-security/pull/1146)) - - Remove `idx` field from the `ccv.ConsumerPacketData` type as this would break the - wire ([\#1150](https://github.com/cosmos/interchain-security/pull/1150)) - - Validate token transfer messages before calling `Transfer()`. - ([\#1244](https://github.com/cosmos/interchain-security/pull/1244)) - - Remove incorrect address validation on `ProviderFeePoolAddrStr` param. - ([\#1262](https://github.com/cosmos/interchain-security/pull/1262)) - - Increment consumer consensus version and register consumer migration. - ([\#1295](https://github.com/cosmos/interchain-security/pull/1295)) + - Avoid jailing validators immediately once they can no longer opt-out from + validating consumer chains. + ([\#1549](https://github.com/cosmos/interchain-security/pull/1549)) - [Provider](x/ccv/provider) - - Add a governance proposal for setting on the provider the denominations for - rewards from consumer chains. - ([\#1280](https://github.com/cosmos/interchain-security/pull/1280)) - -## v3.1.0 - -Date July 11th, 2023 - -A minor upgrade to v3.0.0, which removes the panic in the consumer ccv module which would occur in an emergency scenario where the ccv channel is closed. This release also fixes how a distribution related event is emitted, and bumps cometbft. - -* (feat) [#1127](https://github.com/cosmos/interchain-security/pull/1127) Remove consumer panic when ccv channel is closed -* (fix) [#720](https://github.com/cosmos/interchain-security/issues/720) Fix the attribute `AttributeDistributionTotal` value in `FeeDistribution` event emit. -* (deps) [#1119](https://github.com/cosmos/interchain-security/pull/1119) bump cometbft from `v0.37.1` to `0.37.2`. - -## v3.0.0 - -Date: June 21st, 2023 - -Interchain Security v3 uses SDK 0.47 and IBC 7. - -* (fix) [#1093](https://github.com/cosmos/interchain-security/pull/1093) Make SlashPacketData backward compatible when sending data over the wire -* (deps) [#1019](https://github.com/cosmos/interchain-security/pull/1019) Bump multiple dependencies. - * Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to [v0.47.3](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.3). - * Bump [ibc-go](https://github.com/cosmos/ibc-go) to [v7.1.0](https://github.com/cosmos/ibc-go/releases/tag/v7.1.0). - * Bump [CometBFT](https://github.com/cometbft/cometbft) to [v0.37.1](https://github.com/cometbft/cometbft/releases/tag/v0.37.1). -* `[x/ccv/provider]` (fix) [#945](https://github.com/cosmos/interchain-security/issues/945) Refactor `AfterUnbondingInitiated` to not panic when `PutUnbondingOnHold` returns error. -* `[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.4.0-lsm - -*November 20, 2023* - -* (fix) [#1439](https://github.com/cosmos/interchain-security/pull/1439) Fix unmarshaling for the CLI consumer double vote cmd. -* (feat!) [#1435](https://github.com/cosmos/interchain-security/pull/1435) Add height-base filter for consumer equivocation evidence. - -## v2.3.0-provider-lsm - -*November 15, 2023* - -❗ *This release is deprecated and should not be used in production.* - -* (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 - -❗ *This release is deprecated and should not be used in production.* - -### 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 - -* (feature!) [#1280](https://github.com/cosmos/interchain-security/pull/1280) provider proposal for changing reward denoms - -## 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](https://github.com/cosmos/interchain-security/blob/v2.0.0/x/ccv/provider/keeper/migration.go). - -Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state migrations. - -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 - -* MVP for standalone to consumer changeover, see [EPIC](https://github.com/cosmos/interchain-security/issues/756) -* MVP for soft opt out, see [EPIC](https://github.com/cosmos/interchain-security/issues/851) -* Various fixes, critical and non-critical -* Docs updates which should not affect production code - -## 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) consumer 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) 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) -* (feat) Add warnings when provider unbonding is shorter than consumer unbonding [#858](https://github.com/cosmos/interchain-security/pull/858) -* (chore) use go 1.19 [#899](https://github.com/cosmos/interchain-security/pull/899), [#840](https://github.com/cosmos/interchain-security/pull/840) -* (feat) Standalone to consumer changeover - recycle existing transfer channel [#832](https://github.com/cosmos/interchain-security/pull/832) -* (deps) Bump IBC [862](https://github.com/cosmos/interchain-security/pull/862) -* (testing) Add tests for soft opt out [#857](https://github.com/cosmos/interchain-security/pull/857) -* (feat) Standalone to consumer changeover - staking functionalities [#794](https://github.com/cosmos/interchain-security/pull/794) -* (fix) prevent provider from sending VSCPackets with multiple updates for the same validator [#850](https://github.com/cosmos/interchain-security/pull/850) -* (feat) Soft opt out [#833](https://github.com/cosmos/interchain-security/issues/833) -* (fix) Correctly handle VSC packet with duplicate val updates on consumer [#846](https://github.com/cosmos/interchain-security/pull/846) -* (deps) bump sdk to v0.45.15.ics [#805](https://github.com/cosmos/interchain-security/pull/805) -* (refactor) Remove spm module [#812](https://github.com/cosmos/interchain-security/pull/812) -* (feat) Standalone to consumer changeover part 1 [#757](https://github.com/cosmos/interchain-security/pull/757) -* (chore) Swap names of e2e and integration tests [#681](https://github.com/cosmos/interchain-security/pull/681) -* (fix) fix StopConsumerChain not running in cachedContext [#802](https://github.com/cosmos/interchain-security/pull/802). Also in earlier releases with different commit order! -* (docs) Introduce docs website [#759](https://github.com/cosmos/interchain-security/pull/759) -* (fix) Serialize correct byte prefix for SlashLogKey [#786](https://github.com/cosmos/interchain-security/pull/786) -* (feature) Improve keeper field validation [#766](https://github.com/cosmos/interchain-security/pull/766) -* (docs) Contributing guidelines [#744](https://github.com/cosmos/interchain-security/pull/744) -* (refactor) Key assignment type safety [#725](https://github.com/cosmos/interchain-security/pull/725) -* (fix) Update protos and fix deps [#752](https://github.com/cosmos/interchain-security/pull/752) -* (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 - -This is the first version of Interchain Security (ICS), also known as _Replicated Security_ (RS). -Replicated Security is a feature which will allow a chain -- referred to as the _provider_ -- to share security with other chains -- referred to as _consumers_. -This means that the provider's validator set will be granted the right to validate consumer chains. -The communication between the provider and the consumer chains is done through the IBC protocol over a unique, ordered channel (one for each consumer chain). Thus, RS is an IBC application. - -### Features / sub-protocols - -RS consist of the following core features: - -- **Channel Initialization**: Enables the provider to add new consumer chains. This process is governance-gated, i.e., to add a new consumer chain, a `ConsumerAdditionProposal` governance proposal must be sent to the provider and it must receive the necessary votes. -- **Validator Set Update**: Enables the provider to - (1) update the consumers on the voting power granted to validators (based on the changes in the active validator set on the provider chain), - and (2) ensure the timely completion of unbonding operations (e.g., undelegations). -- **Consumer Initiated Slashing**: Enables the provider to jail validators for downtime infractions on the consumer chains. -- **Reward Distribution**: Enables the consumers to transfer to the provider (over IBC) a portion of their block rewards as payment for the security provided. Once transferred, these rewards are distributed on the provider using the protocol in the [distribution module of Cosmos SDK](https://docs.cosmos.network/v0.45/modules/distribution/). -- **Consumer Chain Removal**: Enables the provider to remove a consumer either after a `ConsumerRemovalProposal` passes governance or after one of the timeout periods elapses -- `InitTimeoutPeriod`, `VscTimeoutPeriod`, `IBCTimeoutPeriod`. -- **Social Slashing**: Equivocation offenses (double signing etc.) on consumer chains are logged, and then can be used in a governance proposal to slash the validators responsible. + - Add the provider-side changes for jail throttling with retries (cf. ADR 008). + ([\#1321](https://github.com/cosmos/interchain-security/pull/1321)) -In addition, RS has the following features: +## Previous Versions -- **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. +[CHANGELOG of previous versions](https://github.com/cosmos/interchain-security/blob/main/CHANGELOG.md) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5d5ad1eb62..f833594abe 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,37 +1,17 @@ - - -# Replicated Security Release Notes - - -❗ ***Note this release is ONLY relevant to *** +# Replicated Security v4.0.0 Release Notes ## 📝 Changelog -** REMOVE THE LINE BELOW TO ENABLE THE MARKDOWN LINK CHECKER FOR RELEASE ** - - -Check out the [changelog](https://github.com/cosmos/interchain-security/blob//CHANGELOG.md) for a list of relevant changes or [compare all changes](https://github.com/cosmos/interchain-security/compare/release/...) from last release. +Check out the [changelog](https://github.com/cosmos/interchain-security/blob/v4.0.0/CHANGELOG.md) for a list of relevant changes or [compare all changes](https://github.com/cosmos/interchain-security/compare/v3.3.0...v4.0.0) from last release. -Refer to the [upgrading guide](https://github.com/cosmos/interchain-security/blob/release//UPGRADING.md) when migrating from `` to ``. +Refer to the [upgrading guide](https://github.com/cosmos/interchain-security/blob/release/v4.0.x/UPGRADING.md) when migrating +from a version `>= v3.1.x` to `v4.0.x`. -** REMOVE THE LINE BELOW TO ENABLE THE MARKDOWN LINK CHECKER FOR RELEASE ** - ## 🚀 Highlights - +This release introduces the following noteworthy changes: -## ❤️ Contributors - -* Informal Systems ([@informalinc](https://twitter.com/informalinc)) - +- It sets the minimum required version of Go to `1.21`. +- It adds the provider-side changes for jail throttling with retries and, as a result, it fully enables the jail throttling with retries feature (cf. [ADR 008](https://github.com/cosmos/interchain-security/blob/release/v3.2.x/docs/docs/adrs/adr-008-throttle-retries.md)). +- Fixes a bug in the soft opt-out protocol -- it avoids jailing validators immediately once they can no longer opt-out from validating consumer chains. -This list is non-exhaustive and ordered alphabetically. -Thank you to everyone who contributed to this release! From 37b66f7aa9c2e0bf3a81cd3f6c2274ee042d3dd7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 17:09:27 +0100 Subject: [PATCH 03/18] docs: update upgrading instructions (backport #1563) (#1566) docs: update upgrading instructions (#1563) * update upgrading instructions * Update UPGRADING.md (cherry picked from commit 2811d57add8aa6ca91e19e5ea772f5c533f3cd1d) Co-authored-by: Marius Poke --- UPGRADING.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 06df8eb91e..cc7de42395 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,6 +2,34 @@ This guide provides instructions for upgrading to specific versions of Replicated Security. +## [v4.0.x](https://github.com/cosmos/interchain-security/tree/release/v4.0.x) + +`v4.0.x` sets the minimum required version of Go to `1.21`, see https://github.com/cosmos/interchain-security/blob/release/v4.0.x/go.mod#L3. + +### Provider + +Upgrading a provider from `v3.3.0` to `v4.0.0` will require state migrations, see https://github.com/cosmos/interchain-security/blob/release/v4.0.x/x/ccv/provider/migrations/migrator.go#L31. + +### Consumer + +Upgrading a consumer from `v3.2.0` to `v4.0.0` will not require state migration, however, upgrading directly from `v3.1.0` to `v4.0.0` will require state migrations, see https://github.com/cosmos/interchain-security/blob/release/v4.0.x/x/ccv/consumer/keeper/migrations.go#L22. + +Note that consumer chains can upgrade directly from `v3.1.0` to `v4.0.0`. + +## [v3.3.x](https://github.com/cosmos/interchain-security/tree/release/v3.2.x) + +### Provider + +Upgrading the provider from `v2.x.y` to `v3.3.0` will not require state migration. + +## [v3.2.x](https://github.com/cosmos/interchain-security/tree/release/v3.2.x) + +`v3.2.0` bumps IBC to `v7.3`. As a result, `legacy_ibc_testing` is not longer required and was removed, see https://github.com/cosmos/interchain-security/pull/1185. This means that when upgrading to `v3.2.0`, any customized tests relying on `legacy_ibc_testing` need to be updated. + +### Consumer + +Upgrading the consumer from either `v3.0.0` or `v3.1.0` to `v3.2.0` will require state migrations, see https://github.com/cosmos/interchain-security/blob/release/v3.2.x/x/ccv/consumer/keeper/migration.go#L25. + ## [v3.0.x](https://github.com/cosmos/interchain-security/releases/tag/v3.0.0-rc0) ### Upgrading to Cosmos SDK 0.47 From 7b9f465e6b44177a546fdf708d45b4deeb0abc60 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 19 Jan 2024 19:03:37 +0100 Subject: [PATCH 04/18] chore: add version linker flags to make install (backport #1575) (#1576) chore: add version linker flags to make install (#1575) (cherry picked from commit dda626b446ec8c9fb4a9130a6d7521f7c2171bc9) Co-authored-by: MSalopek --- Makefile | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index dfa42b3e82..350c66f8af 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,33 @@ #!/usr/bin/make -f +BRANCH := $(shell git rev-parse --abbrev-ref HEAD) +COMMIT := $(shell git log -1 --format='%H') + +# don't override user values +ifeq (,$(VERSION)) + VERSION := $(shell git describe --exact-match 2>/dev/null) + # if VERSION is empty, then populate it with branch's name and raw commit hash + ifeq (,$(VERSION)) + VERSION := $(BRANCH)-$(COMMIT) + endif +endif + +sharedFlags = -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ + -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) + +providerFlags := $(sharedFlags) -X github.com/cosmos/cosmos-sdk/version.AppName=interchain-security-pd -X github.com/cosmos/cosmos-sdk/version.Name=interchain-security-pd +consumerFlags := $(sharedFlags) -X github.com/cosmos/cosmos-sdk/version.AppName=interchain-security-cd -X github.com/cosmos/cosmos-sdk/version.Name=interchain-security-cd +democracyFlags := $(sharedFlags) -X github.com/cosmos/cosmos-sdk/version.AppName=interchain-security-cdd -X github.com/cosmos/cosmos-sdk/version.Name=interchain-security-cdd +standaloneFlags := $(sharedFlags) -X github.com/cosmos/cosmos-sdk/version.AppName=interchain-security-sd -X github.com/cosmos/cosmos-sdk/version.Name=interchain-security-sd + install: go.sum export GOFLAGS='-buildmode=pie' export CGO_CPPFLAGS="-D_FORTIFY_SOURCE=2" export CGO_LDFLAGS="-Wl,-z,relro,-z,now -fstack-protector" - 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 + go install -ldflags "$(providerFlags)" ./cmd/interchain-security-pd + go install -ldflags "$(consumerFlags)" ./cmd/interchain-security-cd + go install -ldflags "$(democracyFlags)" ./cmd/interchain-security-cdd + go install -ldflags "$(standaloneFlags)" ./cmd/interchain-security-sd # run all tests: unit, integration, diff, and E2E test: test-unit test-integration test-mbt test-e2e From 0b7bd11b475912bd595735e00095824072576d55 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 19 Jan 2024 19:25:23 +0100 Subject: [PATCH 05/18] fix!: Validation of SlashAcks fails due to marshaling to Bech32 (backport #1570) (#1577) fix!: Validation of SlashAcks fails due to marshaling to Bech32 (#1570) * add different Bech32Prefix for consumer and provider * separate app encoding and params * remove ConsumerValPubKey from ValidatorConfig * update addresses in tests * make SlashAcks consistent across chains * add comments for clarity * Regenerate traces * Fix argument order * set bech32prefix for provider to cosmos * add changelog entries * add consumer-double-downtime e2e test * update nightly-e2e workflow * fix typo * add consumer-double-downtime to testConfigs * remove changes on provider * skip invalid SlashAcks * seal the config * clear the outstanding downtime flag for new vals * add info on upgrading to v4.0.0 * fix upgrade handler * fix changeover e2e test * Update tests/e2e/config.go Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * Update tests/e2e/config.go Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * add AccountPrefix to ChainConfig * fix docstrings * update AccountAddressPrefix in app.go * fix consumer-misb e2e test --------- Co-authored-by: Philip Offtermatt Co-authored-by: Simon Noetzlin Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> (cherry picked from commit 86046926502f7b0ba795bebcdd1fdc97ac776573) # Conflicts: # .changelog/v4.0.0/bug-fixes/1570-slashack-bech32.md Co-authored-by: Marius Poke --- .../state-breaking/1570-slashack-bech32.md | 2 + .../v4.0.0/bug-fixes/1570-slashack-bech32.md | 2 + .github/workflows/manual-e2e.yml | 103 ------- .github/workflows/nightly-e2e.yml | 55 +++- UPGRADING.md | 22 +- app/consumer-democracy/app.go | 12 +- .../ante/disabled_modules_ante_test.go | 4 +- app/consumer/ante/msg_filter_ante_test.go | 4 +- app/consumer/app.go | 12 +- app/{params/proto.go => encoding/encoding.go} | 12 +- app/params/config.go | 43 ++- app/params/doc.go | 19 -- app/params/encoding.go | 16 -- app/params/params.go | 7 - app/params/weights.go | 42 --- app/provider/app.go | 10 +- app/sovereign/app.go | 12 +- cmd/interchain-security-cd/cmd/root.go | 8 +- cmd/interchain-security-cd/main.go | 3 +- cmd/interchain-security-cdd/cmd/root.go | 8 +- cmd/interchain-security-cdd/main.go | 3 +- cmd/interchain-security-pd/cmd/root.go | 8 +- cmd/interchain-security-pd/main.go | 3 +- cmd/interchain-security-sd/cmd/root.go | 8 +- cmd/interchain-security-sd/main.go | 3 +- tests/e2e/actions.go | 109 ++++++-- tests/e2e/config.go | 253 ++++++++++++------ tests/e2e/main.go | 24 +- tests/e2e/state.go | 65 +++-- tests/e2e/steps.go | 8 + tests/e2e/steps_consumer_misbehaviour.go | 6 +- tests/e2e/steps_downtime.go | 198 ++++++++++++++ tests/e2e/steps_start_chains.go | 32 +-- tests/e2e/testnet-scripts/fork-consumer.sh | 2 +- .../testnet-scripts/sovereign-genesis.json | 10 +- x/ccv/consumer/keeper/keeper.go | 8 +- x/ccv/consumer/keeper/relay.go | 15 +- x/ccv/consumer/keeper/relay_test.go | 4 +- x/ccv/consumer/keeper/validators.go | 4 + x/ccv/types/utils.go | 17 ++ x/ccv/types/wire.go | 7 - x/ccv/types/wire_test.go | 73 +---- 42 files changed, 756 insertions(+), 500 deletions(-) create mode 100644 .changelog/unreleased/state-breaking/1570-slashack-bech32.md create mode 100644 .changelog/v4.0.0/bug-fixes/1570-slashack-bech32.md delete mode 100644 .github/workflows/manual-e2e.yml rename app/{params/proto.go => encoding/encoding.go} (61%) delete mode 100644 app/params/doc.go delete mode 100644 app/params/encoding.go delete mode 100644 app/params/params.go delete mode 100644 app/params/weights.go diff --git a/.changelog/unreleased/state-breaking/1570-slashack-bech32.md b/.changelog/unreleased/state-breaking/1570-slashack-bech32.md new file mode 100644 index 0000000000..a0e9fe9262 --- /dev/null +++ b/.changelog/unreleased/state-breaking/1570-slashack-bech32.md @@ -0,0 +1,2 @@ +- Fix the validation of VSCPackets to not fail due to marshaling to string using Bech32. + ([\#1570](https://github.com/cosmos/interchain-security/pull/1570)) \ No newline at end of file diff --git a/.changelog/v4.0.0/bug-fixes/1570-slashack-bech32.md b/.changelog/v4.0.0/bug-fixes/1570-slashack-bech32.md new file mode 100644 index 0000000000..a0e9fe9262 --- /dev/null +++ b/.changelog/v4.0.0/bug-fixes/1570-slashack-bech32.md @@ -0,0 +1,2 @@ +- Fix the validation of VSCPackets to not fail due to marshaling to string using Bech32. + ([\#1570](https://github.com/cosmos/interchain-security/pull/1570)) \ No newline at end of file diff --git a/.github/workflows/manual-e2e.yml b/.github/workflows/manual-e2e.yml deleted file mode 100644 index f9ca0ca33d..0000000000 --- a/.github/workflows/manual-e2e.yml +++ /dev/null @@ -1,103 +0,0 @@ -# manually run full E2E test suite -# all tests are run sequentially -name: manual-e2e-main -on: - workflow_dispatch: - -jobs: - happy-path-test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E happy-path test - run: go run ./tests/e2e/... --tc happy-path - changeover-test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E changeover test - run: go run ./tests/e2e/... --tc changeover - democracy-reward-test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E democracy-reward tests - run: go run ./tests/e2e/... --tc democracy-reward - democracy-test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E democracy tests - run: go run ./tests/e2e/... --tc democracy - slash-throttle-test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E slash-throttle tests - run: go run ./tests/e2e/... --tc slash-throttle - multiconsumer-test: - runs-on: ubuntu-latest - timeout-minutes: 40 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E multi-consumer tests - run: go run ./tests/e2e/... --tc multiconsumer diff --git a/.github/workflows/nightly-e2e.yml b/.github/workflows/nightly-e2e.yml index 2e6e60d042..043f19fc1a 100644 --- a/.github/workflows/nightly-e2e.yml +++ b/.github/workflows/nightly-e2e.yml @@ -1,9 +1,7 @@ # Run integration tests nightly on main - -# !! Relevant changes to this file should be propagated manual-integration.yml - name: nightly-e2e-main on: + workflow_dispatch: schedule: # run every day at 03:00 UTC # ┌───────────── minute (0 - 59) @@ -114,6 +112,54 @@ jobs: go-version: "1.21" # The Go version to download (if necessary) and use. - name: E2E multi-consumer tests run: go run ./tests/e2e/... --tc multiconsumer + consumer-misbehaviour-test: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/setup-go@v5 + with: + go-version: "1.21" + - uses: actions/checkout@v4 + - name: Checkout LFS objects + run: git lfs checkout + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.21" # The Go version to download (if necessary) and use. + - name: E2E consumer-misbehaviour tests + run: go run ./tests/e2e/... --tc consumer-misbehaviour + consumer-double-sign-test: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/setup-go@v5 + with: + go-version: "1.21" + - uses: actions/checkout@v4 + - name: Checkout LFS objects + run: git lfs checkout + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.21" # The Go version to download (if necessary) and use. + - name: E2E consumer-double-sign tests + run: go run ./tests/e2e/... --tc consumer-double-sign + consumer-double-downtime-test: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/setup-go@v5 + with: + go-version: "1.21" + - uses: actions/checkout@v4 + - name: Checkout LFS objects + run: git lfs checkout + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.21" # The Go version to download (if necessary) and use. + - name: E2E consumer-double-downtime tests + run: go run ./tests/e2e/... --tc consumer-double-downtime nightly-test-fail: needs: @@ -123,6 +169,9 @@ jobs: - democracy-test - slash-throttle-test - multiconsumer-test + - consumer-misbehaviour-test + - consumer-double-sign-test + - consumer-double-downtime-test if: ${{ failure() }} runs-on: ubuntu-latest steps: diff --git a/UPGRADING.md b/UPGRADING.md index cc7de42395..851f31fe78 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -12,9 +12,29 @@ Upgrading a provider from `v3.3.0` to `v4.0.0` will require state migrations, se ### Consumer +***Note that consumer chains can upgrade directly from `v3.1.0` to `v4.0.0`.*** + Upgrading a consumer from `v3.2.0` to `v4.0.0` will not require state migration, however, upgrading directly from `v3.1.0` to `v4.0.0` will require state migrations, see https://github.com/cosmos/interchain-security/blob/release/v4.0.x/x/ccv/consumer/keeper/migrations.go#L22. -Note that consumer chains can upgrade directly from `v3.1.0` to `v4.0.0`. +In addition, the following migration needs to be added to the upgrade handler of the consumer chain: +```golang +func migrateICSOutstandingDowntime(ctx sdk.Context, keepers *upgrades.UpgradeKeepers) error { + ctx.Logger().Info("Migrating ICS oustanding downtime...") + + downtimes := keepers.ConsumerKeeper.GetAllOutstandingDowntimes(ctx) + for _, od := range downtimes { + consAddr, err := sdk.ConsAddressFromBech32(od.ValidatorConsensusAddress) + if err != nil { + return err + } + keepers.ConsumerKeeper.DeleteOutstandingDowntime(ctx, consAddr) + } + + ctx.Logger().Info("Finished ICS oustanding downtime") + + return nil +} +``` ## [v3.3.x](https://github.com/cosmos/interchain-security/tree/release/v3.2.x) diff --git a/app/consumer-democracy/app.go b/app/consumer-democracy/app.go index e740c89406..e90d35760e 100644 --- a/app/consumer-democracy/app.go +++ b/app/consumer-democracy/app.go @@ -103,7 +103,7 @@ import ( "github.com/cometbft/cometbft/libs/log" tmos "github.com/cometbft/cometbft/libs/os" - appparams "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" testutil "github.com/cosmos/interchain-security/v4/testutil/integration" consumer "github.com/cosmos/interchain-security/v4/x/ccv/consumer" consumerkeeper "github.com/cosmos/interchain-security/v4/x/ccv/consumer/keeper" @@ -116,7 +116,7 @@ import ( const ( AppName = "interchain-security-cd" upgradeName = "sovereign-changeover" // arbitrary name, define your own appropriately named upgrade - AccountAddressPrefix = "cosmos" + AccountAddressPrefix = "consumer" ) var ( @@ -1005,8 +1005,8 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino // 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() +func MakeTestEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) @@ -1014,8 +1014,8 @@ func MakeTestEncodingConfig() appparams.EncodingConfig { return encodingConfig } -func makeEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func makeEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) diff --git a/app/consumer/ante/disabled_modules_ante_test.go b/app/consumer/ante/disabled_modules_ante_test.go index 2b12bed63a..7fa95f37c6 100644 --- a/app/consumer/ante/disabled_modules_ante_test.go +++ b/app/consumer/ante/disabled_modules_ante_test.go @@ -13,11 +13,11 @@ import ( slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/interchain-security/v4/app/consumer/ante" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" ) func TestDisabledModulesDecorator(t *testing.T) { - txCfg := params.MakeTestEncodingConfig().TxConfig + txCfg := appencoding.MakeTestEncodingConfig().TxConfig authzMsgExecSlashing := authz.NewMsgExec(sdk.AccAddress{}, []sdk.Msg{&slashingtypes.MsgUnjail{}}) authzMsgExecEvidence := authz.NewMsgExec(sdk.AccAddress{}, []sdk.Msg{&evidencetypes.MsgSubmitEvidence{}}) nestedAuthzMsgExecSlashing := authz.NewMsgExec(sdk.AccAddress{}, []sdk.Msg{&authzMsgExecSlashing}) diff --git a/app/consumer/ante/msg_filter_ante_test.go b/app/consumer/ante/msg_filter_ante_test.go index 47538dad1a..bfc1bb0a50 100644 --- a/app/consumer/ante/msg_filter_ante_test.go +++ b/app/consumer/ante/msg_filter_ante_test.go @@ -10,7 +10,7 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/interchain-security/v4/app/consumer/ante" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" ) type consumerKeeper struct { @@ -28,7 +28,7 @@ func noOpAnteDecorator() sdk.AnteHandler { } func TestMsgFilterDecorator(t *testing.T) { - txCfg := params.MakeTestEncodingConfig().TxConfig + txCfg := appencoding.MakeTestEncodingConfig().TxConfig testCases := []struct { name string diff --git a/app/consumer/app.go b/app/consumer/app.go index 220be5ed80..9853145117 100644 --- a/app/consumer/app.go +++ b/app/consumer/app.go @@ -87,7 +87,7 @@ import ( "github.com/cometbft/cometbft/libs/log" tmos "github.com/cometbft/cometbft/libs/os" - appparams "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" testutil "github.com/cosmos/interchain-security/v4/testutil/integration" ibcconsumer "github.com/cosmos/interchain-security/v4/x/ccv/consumer" ibcconsumerkeeper "github.com/cosmos/interchain-security/v4/x/ccv/consumer/keeper" @@ -97,7 +97,7 @@ import ( const ( AppName = "interchain-security-c" upgradeName = "ics-v1-to-v2" - AccountAddressPrefix = "cosmos" + AccountAddressPrefix = "consumer" ) var ( @@ -830,8 +830,8 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino // return encodingConfig // } -func MakeTestEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func MakeTestEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) @@ -839,8 +839,8 @@ func MakeTestEncodingConfig() appparams.EncodingConfig { return encodingConfig } -func makeEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func makeEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) diff --git a/app/params/proto.go b/app/encoding/encoding.go similarity index 61% rename from app/params/proto.go rename to app/encoding/encoding.go index d11fe8d06c..6897e53634 100644 --- a/app/params/proto.go +++ b/app/encoding/encoding.go @@ -1,11 +1,21 @@ -package params +package encoding import ( + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/x/auth/tx" ) +// EncodingConfig specifies the concrete encoding types to use for a given app. +// This is provided for compatibility between protobuf and amino implementations. +type EncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} + // MakeTestEncodingConfig creates an EncodingConfig for an amino based test configuration. func MakeTestEncodingConfig() EncodingConfig { amino := codec.NewLegacyAmino() diff --git a/app/params/config.go b/app/params/config.go index e4f7308498..c4e3cb7e3c 100644 --- a/app/params/config.go +++ b/app/params/config.go @@ -4,32 +4,27 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -var ( - Bech32Prefix = "cosmos" - - // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address - Bech32PrefixAccAddr = Bech32Prefix - // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key - Bech32PrefixAccPub = Bech32Prefix + sdk.PrefixPublic - // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address - Bech32PrefixValAddr = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator - // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key - Bech32PrefixValPub = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator + sdk.PrefixPublic - // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address - Bech32PrefixConsAddr = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus - // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key - Bech32PrefixConsPub = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus + sdk.PrefixPublic -) - // SetAddressPrefixes builds the Config with Bech32 addressPrefix and publKeyPrefix for accounts, validators, and consensus nodes and verifies that addreeses have correct format. // Not sealed yet -func SetAddressPrefixes() { +func SetAddressPrefixes(bech32Prefix string) { cfg := sdk.GetConfig() - cfg.SetBech32PrefixForAccount(Bech32PrefixAccAddr, Bech32PrefixAccPub) - cfg.SetBech32PrefixForValidator(Bech32PrefixValAddr, Bech32PrefixValPub) - cfg.SetBech32PrefixForConsensusNode(Bech32PrefixConsAddr, Bech32PrefixConsPub) -} -func init() { - SetAddressPrefixes() + // bech32PrefixAccAddr defines the Bech32 prefix of an account's address + bech32PrefixAccAddr := bech32Prefix + // bech32PrefixAccPub defines the Bech32 prefix of an account's public key + bech32PrefixAccPub := bech32Prefix + sdk.PrefixPublic + // bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address + bech32PrefixValAddr := bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator + // bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key + bech32PrefixValPub := bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator + sdk.PrefixPublic + // bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address + bech32PrefixConsAddr := bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus + // bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key + bech32PrefixConsPub := bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus + sdk.PrefixPublic + + cfg.SetBech32PrefixForAccount(bech32PrefixAccAddr, bech32PrefixAccPub) + cfg.SetBech32PrefixForValidator(bech32PrefixValAddr, bech32PrefixValPub) + cfg.SetBech32PrefixForConsensusNode(bech32PrefixConsAddr, bech32PrefixConsPub) + + cfg.Seal() } diff --git a/app/params/doc.go b/app/params/doc.go deleted file mode 100644 index 49b5f6d1e7..0000000000 --- a/app/params/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -Package params defines the simulation parameters in the gaia. - -It contains the default weights used for each transaction used on the module's -simulation. These weights define the chance for a transaction to be simulated at -any given operation. - -You can replace the default values for the weights by providing a params.json -file with the weights defined for each of the transaction operations: - - { - "op_weight_msg_send": 60, - "op_weight_msg_delegate": 100, - } - -In the example above, the `MsgSend` has 60% chance to be simulated, while the -`MsgDelegate` will always be simulated. -*/ -package params diff --git a/app/params/encoding.go b/app/params/encoding.go deleted file mode 100644 index 8ff9ea04b3..0000000000 --- a/app/params/encoding.go +++ /dev/null @@ -1,16 +0,0 @@ -package params - -import ( - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/codec/types" -) - -// EncodingConfig specifies the concrete encoding types to use for a given app. -// This is provided for compatibility between protobuf and amino implementations. -type EncodingConfig struct { - InterfaceRegistry types.InterfaceRegistry - Codec codec.Codec - TxConfig client.TxConfig - Amino *codec.LegacyAmino -} diff --git a/app/params/params.go b/app/params/params.go deleted file mode 100644 index b6aa5fb55e..0000000000 --- a/app/params/params.go +++ /dev/null @@ -1,7 +0,0 @@ -package params - -// Simulation parameter constants -const ( - StakePerAccount = "stake_per_account" - InitiallyBondedValidators = "initially_bonded_validators" -) diff --git a/app/params/weights.go b/app/params/weights.go deleted file mode 100644 index 4e5452c9b2..0000000000 --- a/app/params/weights.go +++ /dev/null @@ -1,42 +0,0 @@ -package params - -// Default simulation operation weights for messages and gov proposals -const ( - DefaultWeightMsgSend int = 100 - DefaultWeightMsgMultiSend int = 10 - DefaultWeightMsgSetWithdrawAddress int = 50 - DefaultWeightMsgWithdrawDelegationReward int = 50 - DefaultWeightMsgWithdrawValidatorCommission int = 50 - DefaultWeightMsgFundCommunityPool int = 50 - DefaultWeightMsgDeposit int = 100 - DefaultWeightMsgVote int = 67 - DefaultWeightMsgUnjail int = 100 - DefaultWeightMsgCreateValidator int = 100 - DefaultWeightMsgEditValidator int = 5 - DefaultWeightMsgDelegate int = 100 - DefaultWeightMsgUndelegate int = 100 - DefaultWeightMsgBeginRedelegate int = 100 - - DefaultWeightCommunitySpendProposal int = 5 - DefaultWeightTextProposal int = 5 - DefaultWeightParamChangeProposal int = 5 - - DefaultWeightMsgStoreCode int = 50 - DefaultWeightMsgInstantiateContract int = 100 - DefaultWeightMsgExecuteContract int = 100 - DefaultWeightMsgUpdateAdmin int = 25 - DefaultWeightMsgClearAdmin int = 10 - DefaultWeightMsgMigrateContract int = 50 - - DefaultWeightStoreCodeProposal int = 5 - DefaultWeightInstantiateContractProposal int = 5 - DefaultWeightUpdateAdminProposal int = 5 - DefaultWeightExecuteContractProposal int = 5 - DefaultWeightClearAdminProposal int = 5 - DefaultWeightMigrateContractProposal int = 5 - DefaultWeightSudoContractProposal int = 5 - DefaultWeightPinCodesProposal int = 5 - DefaultWeightUnpinCodesProposal int = 5 - DefaultWeightUpdateInstantiateConfigProposal int = 5 - DefaultWeightStoreAndInstantiateContractProposal int = 5 -) diff --git a/app/provider/app.go b/app/provider/app.go index c1f3c4d6ff..7527756d45 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -100,7 +100,7 @@ import ( "github.com/cometbft/cometbft/libs/log" tmos "github.com/cometbft/cometbft/libs/os" - appparams "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" testutil "github.com/cosmos/interchain-security/v4/testutil/integration" ibcprovider "github.com/cosmos/interchain-security/v4/x/ccv/provider" ibcproviderclient "github.com/cosmos/interchain-security/v4/x/ccv/provider/client" @@ -930,8 +930,8 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino // return encodingConfig // } -func MakeTestEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func MakeTestEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) @@ -939,8 +939,8 @@ func MakeTestEncodingConfig() appparams.EncodingConfig { return encodingConfig } -func makeEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func makeEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) diff --git a/app/sovereign/app.go b/app/sovereign/app.go index 55967df923..4017ca9d4f 100644 --- a/app/sovereign/app.go +++ b/app/sovereign/app.go @@ -106,14 +106,14 @@ import ( "github.com/cometbft/cometbft/libs/log" tmos "github.com/cometbft/cometbft/libs/os" - appparams "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" testutil "github.com/cosmos/interchain-security/v4/testutil/integration" ) const ( AppName = "interchain-security-s" upgradeName = "v07-Theta" // arbitrary name, define your own appropriately named upgrade - AccountAddressPrefix = "cosmos" + AccountAddressPrefix = "consumer" ) var ( @@ -864,8 +864,8 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino // return encodingConfig // } -func MakeTestEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func MakeTestEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) @@ -873,8 +873,8 @@ func MakeTestEncodingConfig() appparams.EncodingConfig { return encodingConfig } -func makeEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func makeEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) diff --git a/cmd/interchain-security-cd/cmd/root.go b/cmd/interchain-security-cd/cmd/root.go index 64e7b76d3d..215b45317a 100644 --- a/cmd/interchain-security-cd/cmd/root.go +++ b/cmd/interchain-security-cd/cmd/root.go @@ -32,7 +32,7 @@ import ( "github.com/cometbft/cometbft/libs/log" consumer "github.com/cosmos/interchain-security/v4/app/consumer" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" ) // NewRootCmd creates a new root command for simd. It is called once in the @@ -40,7 +40,7 @@ import ( func NewRootCmd() *cobra.Command { // we "pre"-instantiate the application for getting the injected/configured encoding configuration tempApp := consumer.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(consumer.DefaultNodeHome)) - encodingConfig := params.EncodingConfig{ + encodingConfig := appencoding.EncodingConfig{ InterfaceRegistry: tempApp.InterfaceRegistry(), Codec: tempApp.AppCodec(), TxConfig: tempApp.TxConfig(), @@ -186,7 +186,7 @@ lru_size = 0` return customAppTemplate, customAppConfig } -func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { +func initRootCmd(rootCmd *cobra.Command, encodingConfig appencoding.EncodingConfig) { cfg := sdk.GetConfig() cfg.Seal() @@ -276,7 +276,7 @@ func addModuleInitFlags(startCmd *cobra.Command) { } // genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter -func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { +func genesisCommand(encodingConfig appencoding.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, consumer.ModuleBasics, consumer.DefaultNodeHome) for _, sub_cmd := range cmds { diff --git a/cmd/interchain-security-cd/main.go b/cmd/interchain-security-cd/main.go index 3ecd26f899..a64a2a8645 100644 --- a/cmd/interchain-security-cd/main.go +++ b/cmd/interchain-security-cd/main.go @@ -7,12 +7,13 @@ import ( svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" app "github.com/cosmos/interchain-security/v4/app/consumer" + appparams "github.com/cosmos/interchain-security/v4/app/params" "github.com/cosmos/interchain-security/v4/cmd/interchain-security-cd/cmd" ) func main() { + appparams.SetAddressPrefixes("consumer") rootCmd := cmd.NewRootCmd() - if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { switch e := err.(type) { case server.ErrorCode: diff --git a/cmd/interchain-security-cdd/cmd/root.go b/cmd/interchain-security-cdd/cmd/root.go index b69bd57636..6b2e6cc726 100644 --- a/cmd/interchain-security-cdd/cmd/root.go +++ b/cmd/interchain-security-cdd/cmd/root.go @@ -32,7 +32,7 @@ import ( "github.com/cometbft/cometbft/libs/log" cdd "github.com/cosmos/interchain-security/v4/app/consumer-democracy" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" ) // NewRootCmd creates a new root command for simd. It is called once in the @@ -40,7 +40,7 @@ import ( func NewRootCmd() *cobra.Command { // we "pre"-instantiate the application for getting the injected/configured encoding configuration tempApp := cdd.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(cdd.DefaultNodeHome)) - encodingConfig := params.EncodingConfig{ + encodingConfig := appencoding.EncodingConfig{ InterfaceRegistry: tempApp.InterfaceRegistry(), Codec: tempApp.AppCodec(), TxConfig: tempApp.TxConfig(), @@ -186,7 +186,7 @@ lru_size = 0` return customAppTemplate, customAppConfig } -func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { +func initRootCmd(rootCmd *cobra.Command, encodingConfig appencoding.EncodingConfig) { cfg := sdk.GetConfig() cfg.Seal() @@ -276,7 +276,7 @@ func addModuleInitFlags(startCmd *cobra.Command) { } // genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter -func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { +func genesisCommand(encodingConfig appencoding.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, cdd.ModuleBasics, cdd.DefaultNodeHome) for _, sub_cmd := range cmds { diff --git a/cmd/interchain-security-cdd/main.go b/cmd/interchain-security-cdd/main.go index 430ab60591..9b6aacd759 100644 --- a/cmd/interchain-security-cdd/main.go +++ b/cmd/interchain-security-cdd/main.go @@ -7,12 +7,13 @@ import ( svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" app "github.com/cosmos/interchain-security/v4/app/consumer-democracy" + appparams "github.com/cosmos/interchain-security/v4/app/params" "github.com/cosmos/interchain-security/v4/cmd/interchain-security-cdd/cmd" ) func main() { + appparams.SetAddressPrefixes("consumer") rootCmd := cmd.NewRootCmd() - if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { switch e := err.(type) { case server.ErrorCode: diff --git a/cmd/interchain-security-pd/cmd/root.go b/cmd/interchain-security-pd/cmd/root.go index 1f752d758b..8a8f5fec32 100644 --- a/cmd/interchain-security-pd/cmd/root.go +++ b/cmd/interchain-security-pd/cmd/root.go @@ -31,7 +31,7 @@ import ( tmcfg "github.com/cometbft/cometbft/config" "github.com/cometbft/cometbft/libs/log" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" providerApp "github.com/cosmos/interchain-security/v4/app/provider" ) @@ -40,7 +40,7 @@ import ( func NewRootCmd() *cobra.Command { // we "pre"-instantiate the application for getting the injected/configured encoding configuration tempApp := providerApp.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(providerApp.DefaultNodeHome)) - encodingConfig := params.EncodingConfig{ + encodingConfig := appencoding.EncodingConfig{ InterfaceRegistry: tempApp.InterfaceRegistry(), Codec: tempApp.AppCodec(), TxConfig: tempApp.TxConfig(), @@ -186,7 +186,7 @@ lru_size = 0` return customAppTemplate, customAppConfig } -func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { +func initRootCmd(rootCmd *cobra.Command, encodingConfig appencoding.EncodingConfig) { cfg := sdk.GetConfig() cfg.Seal() @@ -276,7 +276,7 @@ func addModuleInitFlags(startCmd *cobra.Command) { } // genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter -func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { +func genesisCommand(encodingConfig appencoding.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, providerApp.ModuleBasics, providerApp.DefaultNodeHome) for _, sub_cmd := range cmds { cmd.AddCommand(sub_cmd) diff --git a/cmd/interchain-security-pd/main.go b/cmd/interchain-security-pd/main.go index 6091c60156..7788f06bff 100644 --- a/cmd/interchain-security-pd/main.go +++ b/cmd/interchain-security-pd/main.go @@ -6,13 +6,14 @@ import ( "github.com/cosmos/cosmos-sdk/server" svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + appparams "github.com/cosmos/interchain-security/v4/app/params" app "github.com/cosmos/interchain-security/v4/app/provider" "github.com/cosmos/interchain-security/v4/cmd/interchain-security-pd/cmd" ) func main() { + appparams.SetAddressPrefixes("cosmos") rootCmd := cmd.NewRootCmd() - if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { switch e := err.(type) { case server.ErrorCode: diff --git a/cmd/interchain-security-sd/cmd/root.go b/cmd/interchain-security-sd/cmd/root.go index 0862e92836..28520ef1a4 100644 --- a/cmd/interchain-security-sd/cmd/root.go +++ b/cmd/interchain-security-sd/cmd/root.go @@ -31,7 +31,7 @@ import ( tmcfg "github.com/cometbft/cometbft/config" "github.com/cometbft/cometbft/libs/log" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" sovereignApp "github.com/cosmos/interchain-security/v4/app/sovereign" ) @@ -40,7 +40,7 @@ import ( func NewRootCmd() *cobra.Command { // we "pre"-instantiate the application for getting the injected/configured encoding configuration tempApp := sovereignApp.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(sovereignApp.DefaultNodeHome)) - encodingConfig := params.EncodingConfig{ + encodingConfig := appencoding.EncodingConfig{ InterfaceRegistry: tempApp.InterfaceRegistry(), Codec: tempApp.AppCodec(), TxConfig: tempApp.TxConfig(), @@ -186,7 +186,7 @@ lru_size = 0` return customAppTemplate, customAppConfig } -func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { +func initRootCmd(rootCmd *cobra.Command, encodingConfig appencoding.EncodingConfig) { cfg := sdk.GetConfig() cfg.Seal() @@ -276,7 +276,7 @@ func addModuleInitFlags(startCmd *cobra.Command) { } // genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter -func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { +func genesisCommand(encodingConfig appencoding.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, sovereignApp.ModuleBasics, sovereignApp.DefaultNodeHome) for _, sub_cmd := range cmds { cmd.AddCommand(sub_cmd) diff --git a/cmd/interchain-security-sd/main.go b/cmd/interchain-security-sd/main.go index 239b08022b..2265afad90 100644 --- a/cmd/interchain-security-sd/main.go +++ b/cmd/interchain-security-sd/main.go @@ -6,13 +6,14 @@ import ( "github.com/cosmos/cosmos-sdk/server" svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + appparams "github.com/cosmos/interchain-security/v4/app/params" app "github.com/cosmos/interchain-security/v4/app/sovereign" "github.com/cosmos/interchain-security/v4/cmd/interchain-security-sd/cmd" ) func main() { + appparams.SetAddressPrefixes("consumer") rootCmd := cmd.NewRootCmd() - if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { switch e := err.(type) { case server.ErrorCode: diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index e98fd611e3..84c3020b91 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -33,13 +33,33 @@ func (tr TestConfig) sendTokens( action SendTokensAction, verbose bool, ) { + fromValCfg := tr.validatorConfigs[action.From] + toValCfg := tr.validatorConfigs[action.To] + fromAddress := fromValCfg.DelAddress + toAddress := toValCfg.DelAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if fromValCfg.UseConsumerKey { + fromAddress = fromValCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + fromAddress = fromValCfg.DelAddressOnConsumer + } + if toValCfg.UseConsumerKey { + toAddress = toValCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + toAddress = toValCfg.DelAddressOnConsumer + } + } + binaryName := tr.chainConfigs[action.Chain].BinaryName //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, binaryName, "tx", "bank", "send", - tr.validatorConfigs[action.From].DelAddress, - tr.validatorConfigs[action.To].DelAddress, + fromAddress, + toAddress, fmt.Sprint(action.Amount)+`stake`, `--chain-id`, string(tr.chainConfigs[action.Chain].ChainId), @@ -653,7 +673,7 @@ type AddChainToRelayerAction struct { const hermesChainConfigTemplate = ` [[chains]] -account_prefix = "cosmos" +account_prefix = "%s" clock_drift = "5s" gas_multiplier = 1.1 grpc_addr = "%s" @@ -687,7 +707,7 @@ const gorelayerChainConfigTemplate = ` "key": "default", "chain-id": "%s", "rpc-addr": "%s", - "account-prefix": "cosmos", + "account-prefix": "%s", "keyring-backend": "test", "gas-adjustment": 1.2, "gas-prices": "0.00stake", @@ -720,6 +740,7 @@ func (tr TestConfig) addChainToGorelayer( chainConfig := fmt.Sprintf(gorelayerChainConfigTemplate, ChainId, rpcAddr, + tr.chainConfigs[action.Chain].AccountPrefix, ) //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. @@ -759,6 +780,7 @@ func (tr TestConfig) addChainToHermes( wsAddr := "ws://" + queryNodeIP + ":26658/websocket" chainConfig := fmt.Sprintf(hermesChainConfigTemplate, + tr.chainConfigs[action.Chain].AccountPrefix, grpcAddr, ChainId, keyName, @@ -1311,15 +1333,22 @@ func (tr TestConfig) delegateTokens( verbose bool, ) { toValCfg := tr.validatorConfigs[action.To] - delegateAddr := toValCfg.ValoperAddress - if action.Chain != ChainID("provi") && toValCfg.UseConsumerKey { - delegateAddr = toValCfg.ConsumerValoperAddress + validatorAddress := toValCfg.ValoperAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if toValCfg.UseConsumerKey { + validatorAddress = toValCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + validatorAddress = toValCfg.ValoperAddressOnConsumer + } } + //#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", "delegate", - delegateAddr, + validatorAddress, fmt.Sprint(action.Amount)+`stake`, `--from`, `validator`+fmt.Sprint(action.From), @@ -1354,16 +1383,23 @@ func (tr TestConfig) unbondTokens( action UnbondTokensAction, verbose bool, ) { - unbondFrom := tr.validatorConfigs[action.UnbondFrom].ValoperAddress - if tr.validatorConfigs[action.UnbondFrom].UseConsumerKey { - unbondFrom = tr.validatorConfigs[action.UnbondFrom].ConsumerValoperAddress + unbondFromValCfg := tr.validatorConfigs[action.UnbondFrom] + validatorAddress := unbondFromValCfg.ValoperAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if unbondFromValCfg.UseConsumerKey { + validatorAddress = unbondFromValCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + validatorAddress = unbondFromValCfg.ValoperAddressOnConsumer + } } //#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", "unbond", - unbondFrom, + validatorAddress, fmt.Sprint(action.Amount)+`stake`, `--from`, `validator`+fmt.Sprint(action.Sender), @@ -1399,17 +1435,32 @@ func (tr TestConfig) cancelUnbondTokens( action CancelUnbondTokensAction, verbose bool, ) { - validator := tr.validatorConfigs[action.Validator].ValoperAddress - if tr.validatorConfigs[action.Validator].UseConsumerKey { - validator = tr.validatorConfigs[action.Validator].ConsumerValoperAddress + valCfg := tr.validatorConfigs[action.Validator] + delCfg := tr.validatorConfigs[action.Delegator] + validatorAddress := valCfg.ValoperAddress + delegatorAddress := delCfg.DelAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if valCfg.UseConsumerKey { + validatorAddress = valCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + validatorAddress = valCfg.ValoperAddressOnConsumer + } + if delCfg.UseConsumerKey { + delegatorAddress = delCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + delegatorAddress = delCfg.DelAddressOnConsumer + } } // 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, + delegatorAddress, + validatorAddress, `--home`, tr.getValidatorHome(action.Chain, action.Delegator), `--node`, tr.getValidatorNode(action.Chain, action.Delegator), `-o`, `json`, @@ -1430,7 +1481,7 @@ func (tr TestConfig) cancelUnbondTokens( //#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, + validatorAddress, fmt.Sprint(action.Amount)+`stake`, fmt.Sprint(creationHeight), `--from`, `validator`+fmt.Sprint(action.Delegator), @@ -1467,16 +1518,24 @@ type RedelegateTokensAction struct { func (tr TestConfig) redelegateTokens(action RedelegateTokensAction, verbose bool) { srcCfg := tr.validatorConfigs[action.Src] dstCfg := tr.validatorConfigs[action.Dst] - redelegateSrc := srcCfg.ValoperAddress - if action.Chain != ChainID("provi") && srcCfg.UseConsumerKey { - redelegateSrc = srcCfg.ConsumerValoperAddress - } - redelegateDst := dstCfg.ValoperAddress - if action.Chain != ChainID("provi") && dstCfg.UseConsumerKey { - redelegateDst = dstCfg.ConsumerValoperAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if srcCfg.UseConsumerKey { + redelegateSrc = srcCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + redelegateSrc = srcCfg.ValoperAddressOnConsumer + } + if dstCfg.UseConsumerKey { + redelegateDst = dstCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + redelegateDst = dstCfg.ValoperAddressOnConsumer + } } + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 311ee67c89..c2079af0c3 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -6,6 +6,11 @@ import ( "time" ) +var ( + ProviderAccountPrefix = "cosmos" + ConsumerAccountPrefix = "consumer" +) + // TODO: Determine if user defined type (wrapping a primitive string) is desired in long run type ( ChainID string @@ -15,10 +20,27 @@ type ( // Attributes that are unique to a validator. Allows us to map (part of) // the set of strings defined above to a set of viable validators type ValidatorConfig struct { - Mnemonic string - DelAddress string - ValoperAddress string - ValconsAddress string + // Seed phrase to generate a secp256k1 key used by the validator on the provider + Mnemonic string + // Validator account address on provider marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + DelAddress string + // Validator account address on provider marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + DelAddressOnConsumer string + // Validator operator address on provider marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + ValoperAddress string + // Validator operator address on provider marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + ValoperAddressOnConsumer string + // Validator consensus address on provider marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix. It matches the PrivValidatorKey below. + ValconsAddress string + // Validator consensus address on provider marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix. + ValconsAddressOnConsumer string + // Key used for consensus on provider PrivValidatorKey string NodeKey string // Must be an integer greater than 0 and less than 253 @@ -26,11 +48,29 @@ type ValidatorConfig struct { // consumer chain key assignment data // keys are used on a new node - ConsumerMnemonic string - ConsumerDelAddress string - ConsumerValoperAddress string - ConsumerValconsAddress string - ConsumerValPubKey string + + // Seed phrase to generate a secp256k1 key used by the validator on the consumer + ConsumerMnemonic string + // Validator account address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + ConsumerDelAddress string + // Validator account address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + ConsumerDelAddressOnProvider string + // Validator operator address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + ConsumerValoperAddress string + // Validator operator address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + ConsumerValoperAddressOnProvider string + // Validator consensus address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix. It matches the PrivValidatorKey below. + ConsumerValconsAddress string + // Validator consensus address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix. + ConsumerValconsAddressOnProvider string + ConsumerValPubKey string + // Key used for consensus on consumer ConsumerPrivValidatorKey string ConsumerNodeKey string UseConsumerKey bool // if true the validator node will start with consumer key @@ -40,6 +80,8 @@ type ValidatorConfig struct { // the set of strings defined above to a set of viable chains type ChainConfig struct { ChainId ChainID + // The account prefix configured on the chain. For example, on the Hub, this is "cosmos" + AccountPrefix string // Must be unique per chain IpPrefix string VotingWaitTime uint @@ -92,61 +134,79 @@ func (tr *TestConfig) Initialize() { func getDefaultValidators() map[ValidatorID]ValidatorConfig { return 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", + 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", + DelAddressOnConsumer: "consumer19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtz33vu", + ValoperAddress: "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", + ValoperAddressOnConsumer: "consumervaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddy6jwzg", + ValconsAddress: "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + ValconsAddressOnConsumer: "consumervalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xpvpagq", + 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: false, + 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: "consumer1eeeggku6dzk3mv7wph3zq035rhtd890sh9rl32", + ConsumerDelAddressOnProvider: "cosmos1eeeggku6dzk3mv7wph3zq035rhtd890sjswszd", + ConsumerValoperAddress: "consumervaloper1eeeggku6dzk3mv7wph3zq035rhtd890scaqql7", + ConsumerValoperAddressOnProvider: "cosmosvaloper1eeeggku6dzk3mv7wph3zq035rhtd890shy69w7", + ConsumerValconsAddress: "consumervalcons1muys5jyqk4xd27e208nym85kn0t4zjcfk9q5ce", + ConsumerValconsAddressOnProvider: "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: false, }, 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", + 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", + DelAddressOnConsumer: "consumer1dkas8mu4kyhl5jrh4nzvm65qz588hy9qahzgv6", + ValoperAddress: "cosmosvaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qakmjnw", + ValoperAddressOnConsumer: "consumervaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qj0phzw", + ValconsAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", + ValconsAddressOnConsumer: "consumervalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klhuqtq9", + 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, + 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: "consumer1q90l6j6lzzgt460ehjj56azknlt5yrd44y2uke", + ConsumerDelAddressOnProvider: "cosmos1q90l6j6lzzgt460ehjj56azknlt5yrd4s38n97", + ConsumerValoperAddress: "consumervaloper1q90l6j6lzzgt460ehjj56azknlt5yrd46ufrcd", + ConsumerValoperAddressOnProvider: "cosmosvaloper1q90l6j6lzzgt460ehjj56azknlt5yrd449nxfd", + ConsumerValconsAddress: "consumervalcons1uuec3cjxajv5te08p220usrjhkfhg9wyref26m", + ConsumerValconsAddressOnProvider: "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, }, ValidatorID("carol"): { - Mnemonic: "sight similar better jar bitter laptop solve fashion father jelly scissors chest uniform play unhappy convince silly clump another conduct behave reunion marble animal", - DelAddress: "cosmos19hz4m226ztankqramvt4a7t0shejv4dc79gp9u", - ValoperAddress: "cosmosvaloper19hz4m226ztankqramvt4a7t0shejv4dcm3u5f0", - ValconsAddress: "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", - PrivValidatorKey: `{"address":"C888306A908A217B9A943D1DAD8790044D0947A4","pub_key":{"type":"tendermint/PubKeyEd25519","value":"IHo4QEikWZfIKmM0X+N+BjKttz8HOzGs2npyjiba3Xk="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"z08bmSB91uFVpVmR3t2ewd/bDjZ/AzwQpe5rKjWiPG0gejhASKRZl8gqYzRf434GMq23Pwc7MazaenKOJtrdeQ=="}}`, - NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"WLTcHEjbwB24Wp3z5oBSYTvtGQonz/7IQabOFw85BN0UkkyY5HDf38o8oHlFxVI26f+DFVeICuLbe9aXKGnUeg=="}}`, - IpSuffix: "6", + Mnemonic: "sight similar better jar bitter laptop solve fashion father jelly scissors chest uniform play unhappy convince silly clump another conduct behave reunion marble animal", + DelAddress: "cosmos19hz4m226ztankqramvt4a7t0shejv4dc79gp9u", + DelAddressOnConsumer: "consumer19hz4m226ztankqramvt4a7t0shejv4dcms9wkm", + ValoperAddress: "cosmosvaloper19hz4m226ztankqramvt4a7t0shejv4dcm3u5f0", + ValoperAddressOnConsumer: "consumervaloper19hz4m226ztankqramvt4a7t0shejv4dc5gx3c0", + ValconsAddress: "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + ValconsAddressOnConsumer: "consumervalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayds0ea6", + PrivValidatorKey: `{"address":"C888306A908A217B9A943D1DAD8790044D0947A4","pub_key":{"type":"tendermint/PubKeyEd25519","value":"IHo4QEikWZfIKmM0X+N+BjKttz8HOzGs2npyjiba3Xk="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"z08bmSB91uFVpVmR3t2ewd/bDjZ/AzwQpe5rKjWiPG0gejhASKRZl8gqYzRf434GMq23Pwc7MazaenKOJtrdeQ=="}}`, + NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"WLTcHEjbwB24Wp3z5oBSYTvtGQonz/7IQabOFw85BN0UkkyY5HDf38o8oHlFxVI26f+DFVeICuLbe9aXKGnUeg=="}}`, + IpSuffix: "6", // consumer chain assigned key - ConsumerMnemonic: "clip choose cake west range gun slam cry village receive juice galaxy lend ritual range provide ritual can since verify breeze vacant play dragon", - ConsumerDelAddress: "cosmos1sx6j9g2rh324a342a5f0rnx7me34r9nwgf0mc7", - ConsumerValoperAddress: "cosmosvaloper1sx6j9g2rh324a342a5f0rnx7me34r9nwdamw5d", - ConsumerValconsAddress: "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", - ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}`, - ConsumerPrivValidatorKey: `{"address":"B41C3A40142963AA5B12DDD1C4E5890C0B3926B1","pub_key":{"type":"tendermint/PubKeyEd25519","value":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"3YaBAZLA+sl/E73lLfbFbG0u6DYm33ayr/0UpCt/vFBSLkZ/X6a1ZR0fy7fGWbN0ogP4Xc8rSx9dnvcZnqrqKw=="}}`, - ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"rxBzFedtD3pqgfJQblbxGusKOr47oBfr8ba0Iz14gobtDRZQZlSZ/UGP4pSHkVf+4vtkrkO1vRHBYJobuiP+7A=="}}`, - UseConsumerKey: true, + ConsumerMnemonic: "clip choose cake west range gun slam cry village receive juice galaxy lend ritual range provide ritual can since verify breeze vacant play dragon", + ConsumerDelAddress: "consumer1sx6j9g2rh324a342a5f0rnx7me34r9nwduz5te", + ConsumerDelAddressOnProvider: "cosmos1sx6j9g2rh324a342a5f0rnx7me34r9nwgf0mc7", + ConsumerValoperAddress: "consumervaloper1sx6j9g2rh324a342a5f0rnx7me34r9nwzypt9d", + ConsumerValoperAddressOnProvider: "cosmosvaloper1sx6j9g2rh324a342a5f0rnx7me34r9nwdamw5d", + ConsumerValconsAddress: "consumervalcons1kswr5sq599365kcjmhgufevfps9njf43kv9tuk", + ConsumerValconsAddressOnProvider: "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", + ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}`, + ConsumerPrivValidatorKey: `{"address":"B41C3A40142963AA5B12DDD1C4E5890C0B3926B1","pub_key":{"type":"tendermint/PubKeyEd25519","value":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"3YaBAZLA+sl/E73lLfbFbG0u6DYm33ayr/0UpCt/vFBSLkZ/X6a1ZR0fy7fGWbN0ogP4Xc8rSx9dnvcZnqrqKw=="}}`, + ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"rxBzFedtD3pqgfJQblbxGusKOr47oBfr8ba0Iz14gobtDRZQZlSZ/UGP4pSHkVf+4vtkrkO1vRHBYJobuiP+7A=="}}`, + UseConsumerKey: true, }, } } @@ -164,6 +224,7 @@ func SlashThrottleTestConfig() TestConfig { chainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -179,6 +240,7 @@ func SlashThrottleTestConfig() TestConfig { }, ChainID("consu"): { ChainId: ChainID("consu"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cd", IpPrefix: "7.7.8", VotingWaitTime: 20, @@ -210,6 +272,7 @@ func DefaultTestConfig() TestConfig { chainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -225,6 +288,7 @@ func DefaultTestConfig() TestConfig { }, ChainID("consu"): { ChainId: ChainID("consu"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cd", IpPrefix: "7.7.8", VotingWaitTime: 20, @@ -268,6 +332,7 @@ func DemocracyTestConfig(allowReward bool) TestConfig { chainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -282,6 +347,7 @@ func DemocracyTestConfig(allowReward bool) TestConfig { }, ChainID("democ"): { ChainId: ChainID("democ"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cdd", IpPrefix: "7.7.9", VotingWaitTime: 20, @@ -308,6 +374,7 @@ func MultiConsumerTestConfig() TestConfig { chainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -322,6 +389,7 @@ func MultiConsumerTestConfig() TestConfig { }, ChainID("consu"): { ChainId: ChainID("consu"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cd", IpPrefix: "7.7.8", VotingWaitTime: 20, @@ -333,6 +401,7 @@ func MultiConsumerTestConfig() TestConfig { }, ChainID("densu"): { ChainId: ChainID("densu"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cd", IpPrefix: "7.7.9", VotingWaitTime: 20, @@ -363,6 +432,7 @@ func ChangeoverTestConfig() TestConfig { chainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -378,6 +448,7 @@ func ChangeoverTestConfig() TestConfig { }, ChainID("sover"): { ChainId: ChainID("sover"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-sd", UpgradeBinary: "interchain-security-cdd", IpPrefix: "7.7.8", @@ -408,47 +479,60 @@ func ConsumerMisbehaviourTestConfig() TestConfig { }, 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", + 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", + DelAddressOnConsumer: "consumer19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtz33vu", + ValoperAddress: "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", + ValoperAddressOnConsumer: "consumervaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddy6jwzg", + ValconsAddress: "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + ValconsAddressOnConsumer: "consumervalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xpvpagq", + 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, + 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: "consumer1eeeggku6dzk3mv7wph3zq035rhtd890sh9rl32", + ConsumerDelAddressOnProvider: "cosmos1eeeggku6dzk3mv7wph3zq035rhtd890sjswszd", + ConsumerValoperAddress: "consumervaloper1eeeggku6dzk3mv7wph3zq035rhtd890scaqql7", + ConsumerValoperAddressOnProvider: "cosmosvaloper1eeeggku6dzk3mv7wph3zq035rhtd890shy69w7", + ConsumerValconsAddress: "consumervalcons1muys5jyqk4xd27e208nym85kn0t4zjcfk9q5ce", + ConsumerValconsAddressOnProvider: "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", + 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", + DelAddressOnConsumer: "consumer1dkas8mu4kyhl5jrh4nzvm65qz588hy9qahzgv6", + ValoperAddress: "cosmosvaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qakmjnw", + ValoperAddressOnConsumer: "consumervaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qj0phzw", + ValconsAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", + ValconsAddressOnConsumer: "consumervalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klhuqtq9", + 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, + 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: "consumer1q90l6j6lzzgt460ehjj56azknlt5yrd44y2uke", + ConsumerDelAddressOnProvider: "cosmos1q90l6j6lzzgt460ehjj56azknlt5yrd4s38n97", + ConsumerValoperAddress: "consumervaloper1q90l6j6lzzgt460ehjj56azknlt5yrd46ufrcd", + ConsumerValoperAddressOnProvider: "cosmosvaloper1q90l6j6lzzgt460ehjj56azknlt5yrd449nxfd", + ConsumerValconsAddress: "consumervalcons1uuec3cjxajv5te08p220usrjhkfhg9wyref26m", + ConsumerValconsAddressOnProvider: "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"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -464,6 +548,7 @@ func ConsumerMisbehaviourTestConfig() TestConfig { }, ChainID("consu"): { ChainId: ChainID("consu"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cd", IpPrefix: "7.7.8", VotingWaitTime: 20, diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 13e3fae1e7..5f3ea71e74 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -54,14 +54,15 @@ var ( // map the test config names to their structs to allow for easy selection of test configs, // and also to programmatically set parameters, i.e. see DemocracyTestConfig testConfigs = map[string]TestConfig{ - "default": DefaultTestConfig(), - "changeover": ChangeoverTestConfig(), - "democracy": DemocracyTestConfig(false), - "democracy-reward": DemocracyTestConfig(true), - "slash-throttle": SlashThrottleTestConfig(), - "multiconsumer": MultiConsumerTestConfig(), - "consumer-misbehaviour": ConsumerMisbehaviourTestConfig(), - "consumer-double-sign": DefaultTestConfig(), + "default": DefaultTestConfig(), + "changeover": ChangeoverTestConfig(), + "democracy": DemocracyTestConfig(false), + "democracy-reward": DemocracyTestConfig(true), + "slash-throttle": SlashThrottleTestConfig(), + "multiconsumer": MultiConsumerTestConfig(), + "consumer-misbehaviour": ConsumerMisbehaviourTestConfig(), + "consumer-double-sign": DefaultTestConfig(), + "consumer-double-downtime": DefaultTestConfig(), } ) @@ -128,6 +129,12 @@ var stepChoices = map[string]StepChoice{ description: "consumer double signing tests", testConfig: DefaultTestConfig(), }, + "consumer-double-downtime": { + name: "consumer-double-downtime", + steps: consumerDoubleDowntimeSteps, + description: "jail a validator for two (different) downtime infractions on consumer", + testConfig: DefaultTestConfig(), + }, } func executeTests(tests []testStepsWithConfig) (err error) { @@ -232,6 +239,7 @@ func getTestCases(selectedPredefinedTests, selectedTestFiles TestSet) (tests []t "changeover", "happy-path", "democracy-reward", "democracy", "slash-throttle", "consumer-double-sign", "consumer-misbehaviour", + "consumer-double-downtime", } if includeMultiConsumer != nil && *includeMultiConsumer { selectedPredefinedTests = append(selectedPredefinedTests, "multiconsumer") diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 6578aad03a..0b2c2d1835 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -316,9 +316,16 @@ func (tr TestConfig) getRewards(chain ChainID, modelState Rewards) Rewards { } func (tr TestConfig) 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 + valCfg := tr.validatorConfigs[validator] + delAddresss := valCfg.DelAddress + if chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if valCfg.UseConsumerKey { + delAddresss = valCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + delAddresss = valCfg.DelAddressOnConsumer + } } //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. @@ -345,9 +352,16 @@ func (tr TestConfig) getReward(chain ChainID, validator ValidatorID, blockHeight func (tr TestConfig) getBalance(chain ChainID, validator ValidatorID) uint { //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - valDelAddress := tr.validatorConfigs[validator].DelAddress - if chain != ChainID("provi") && tr.validatorConfigs[validator].UseConsumerKey { - valDelAddress = tr.validatorConfigs[validator].ConsumerDelAddress + valCfg := tr.validatorConfigs[validator] + valDelAddress := valCfg.DelAddress + if chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if valCfg.UseConsumerKey { + valDelAddress = valCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + valDelAddress = valCfg.DelAddressOnConsumer + } } //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. @@ -485,6 +499,7 @@ type ValPubKey struct { Value string `yaml:"value"` } +// TODO (mpoke) Return powers for multiple validators func (tr TestConfig) getValPower(chain ChainID, validator ValidatorID) uint { if *verbose { log.Println("getting validator power for chain: ", chain, " validator: ", validator) @@ -519,16 +534,25 @@ func (tr TestConfig) getValPower(chain ChainID, validator ValidatorID) uint { } for _, val := range valset.Validators { - if val.Address == tr.validatorConfigs[validator].ValconsAddress || - val.Address == tr.validatorConfigs[validator].ConsumerValconsAddress { - - votingPower, err := strconv.Atoi(val.VotingPower) - if err != nil { - log.Fatalf("strconv.Atoi returned an error while converting validator voting power: %v, voting power string: %s, validator set: %s", err, val.VotingPower, pretty.Sprint(valset)) + if chain == ChainID("provi") { + // use binary with Bech32Prefix set to ProviderAccountPrefix + if val.Address != tr.validatorConfigs[validator].ValconsAddress { + continue } + } else { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if val.Address != tr.validatorConfigs[validator].ValconsAddressOnConsumer && + val.Address != tr.validatorConfigs[validator].ConsumerValconsAddress { + continue + } + } - return uint(votingPower) + votingPower, err := strconv.Atoi(val.VotingPower) + if err != nil { + log.Fatalf("strconv.Atoi returned an error while converting validator voting power: %v, voting power string: %s, validator set: %s", err, val.VotingPower, pretty.Sprint(valset)) } + + return uint(votingPower) } // Validator not in set, its validator power is zero. @@ -536,11 +560,22 @@ func (tr TestConfig) getValPower(chain ChainID, validator ValidatorID) uint { } func (tr TestConfig) getValStakedTokens(chain ChainID, validator ValidatorID) uint { + valoperAddress := tr.validatorConfigs[validator].ValoperAddress + if chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if tr.validatorConfigs[validator].UseConsumerKey { + valoperAddress = tr.validatorConfigs[validator].ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + valoperAddress = tr.validatorConfigs[validator].ValoperAddressOnConsumer + } + } + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, "query", "staking", "validator", - tr.validatorConfigs[validator].ValoperAddress, + valoperAddress, `--node`, tr.getQueryNode(chain), `-o`, `json`, @@ -641,7 +676,7 @@ func (tr TestConfig) getProviderAddressFromConsumer(consumerChain ChainID, valid cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[ChainID("provi")].BinaryName, "query", "provider", "validator-provider-key", - string(consumerChain), tr.validatorConfigs[validator].ConsumerValconsAddress, + string(consumerChain), tr.validatorConfigs[validator].ConsumerValconsAddressOnProvider, `--node`, tr.getQueryNode(ChainID("provi")), `-o`, `json`, ) diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 783d976a3a..1626df0de2 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -114,3 +114,11 @@ var consumerDoubleSignSteps = concatSteps( // make a consumer validator double sign and get jailed stepsCauseDoubleSignOnConsumer("consu", "provi"), ) + +var consumerDoubleDowntimeSteps = concatSteps( + stepsStartChains([]string{"consu"}, false), + stepsDelegate("consu"), + stepsUnbond("consu"), + stepsRedelegateShort("consu"), + stepsDoubleDowntime("consu"), +) diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index 4df7b9daec..3cb4ce0f1b 100644 --- a/tests/e2e/steps_consumer_misbehaviour.go +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -61,7 +61,7 @@ func stepsStartChainsWithSoftOptOut(consumerName string) []Step { Action: AssignConsumerPubKeyAction{ Chain: ChainID(consumerName), Validator: ValidatorID("alice"), - ConsumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="}`, + ConsumerPubkey: getDefaultValidators()[ValidatorID("alice")].ConsumerValPubKey, // consumer chain has not started // we don't need to reconfigure the node // since it will start with consumer key @@ -70,10 +70,10 @@ func stepsStartChainsWithSoftOptOut(consumerName string) []Step { State: State{ ChainID(consumerName): ChainState{ AssignedKeys: &map[ValidatorID]string{ - ValidatorID("alice"): "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe", + ValidatorID("alice"): getDefaultValidators()[ValidatorID("alice")].ConsumerValconsAddressOnProvider, }, ProviderKeys: &map[ValidatorID]string{ - ValidatorID("alice"): "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + ValidatorID("alice"): getDefaultValidators()[ValidatorID("alice")].ValconsAddress, }, }, }, diff --git a/tests/e2e/steps_downtime.go b/tests/e2e/steps_downtime.go index f63d0a31c9..a8506bf518 100644 --- a/tests/e2e/steps_downtime.go +++ b/tests/e2e/steps_downtime.go @@ -211,6 +211,204 @@ func stepsDowntime(consumerName string) []Step { } } +// stepsDowstepsDoubleDowntime time tests that a validator can get jailed twice +// on a consumer. +// These are the steps: +// - a validator is down on a consumer +// - the validator gets jailed on the provider (when the SlashPacket is received) +// - the validator gets removed from the consumer (when the VSCPacket is received) +// - the validator gets unjailed on the provider +// - the validator is added to the consumer (when the VSCPacket is received) +// - the validator is down again on the consumer +// - the validator gets jailed on the provider (when the SlashPacket is received) +// - the validator gets removed from the consumer (when the VSCPacket is received) +func stepsDoubleDowntime(consumerName string) []Step { + return []Step{ + { + Action: DowntimeSlashAction{ + Chain: ChainID(consumerName), + Validator: ValidatorID("bob"), + }, + State: State{ + // validator should be slashed on consumer, powers not affected on either chain yet + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + }, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // Downtime jailing and corresponding voting power change are processed by provider + ValidatorID("bob"): 0, + ValidatorID("carol"): 501, + }, + }, + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // Bob's stake may or may not be slashed at this point depending on comet vs cometmock + // See https://github.com/cosmos/interchain-security/issues/1304 + ValidatorID("carol"): 501, + }, + }, + }, + }, + // A block is incremented each action, hence why VSC is committed on provider, + // and can now be relayed as packet to consumer + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // VSC now seen on consumer + ValidatorID("bob"): 0, + ValidatorID("carol"): 501, + }, + }, + }, + }, + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // bob's stake should not be slashed + // since the slash was initiated from consumer + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + ValidatorID("bob"): 0, + ValidatorID("carol"): 501, + }, + }, + }, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // bob's stake should not be slashed + // since the slash was initiated from consumer + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + }, + }, + // Try to jail bob again on the consumer + { + Action: DowntimeSlashAction{ + Chain: ChainID(consumerName), + Validator: ValidatorID("bob"), + }, + State: State{ + // validator should be slashed on consumer, powers not affected on either chain yet + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + }, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // Downtime jailing and corresponding voting power change are processed by provider + ValidatorID("bob"): 0, + ValidatorID("carol"): 501, + }, + }, + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // Bob's stake may or may not be slashed at this point depending on comet vs cometmock + // See https://github.com/cosmos/interchain-security/issues/1304 + ValidatorID("carol"): 501, + }, + }, + }, + }, + // A block is incremented each action, hence why VSC is committed on provider, + // and can now be relayed as packet to consumer + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // VSC now seen on consumer + ValidatorID("bob"): 0, + ValidatorID("carol"): 501, + }, + }, + }, + }, + } +} + // stepsDowntimeWithOptOut returns steps validating that alice can incur downtime // and not be slashed/jailed, since her voting power is less than 5% of the total. // diff --git a/tests/e2e/steps_start_chains.go b/tests/e2e/steps_start_chains.go index b1fdb65df5..08732e3f37 100644 --- a/tests/e2e/steps_start_chains.go +++ b/tests/e2e/steps_start_chains.go @@ -64,7 +64,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint Action: AssignConsumerPubKeyAction{ Chain: ChainID(consumerName), Validator: ValidatorID("carol"), - ConsumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}`, + ConsumerPubkey: getDefaultValidators()[ValidatorID("carol")].ConsumerValPubKey, // consumer chain has not started // we don't need to reconfigure the node // since it will start with consumer key @@ -73,10 +73,10 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint State: State{ ChainID(consumerName): ChainState{ AssignedKeys: &map[ValidatorID]string{ - ValidatorID("carol"): "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ConsumerValconsAddressOnProvider, }, ProviderKeys: &map[ValidatorID]string{ - ValidatorID("carol"): "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ValconsAddress, }, }, }, @@ -86,7 +86,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint Action: AssignConsumerPubKeyAction{ Chain: ChainID(consumerName), Validator: ValidatorID("carol"), - ConsumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}`, + ConsumerPubkey: getDefaultValidators()[ValidatorID("carol")].ConsumerValPubKey, ReconfigureNode: false, ExpectError: true, ExpectedError: "a validator has assigned the consumer key already: consumer key is already in use by a validator", @@ -99,7 +99,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint Chain: ChainID(consumerName), Validator: ValidatorID("bob"), // same pub key as carol - ConsumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}`, + ConsumerPubkey: getDefaultValidators()[ValidatorID("carol")].ConsumerValPubKey, ReconfigureNode: false, ExpectError: true, ExpectedError: "a validator has assigned the consumer key already: consumer key is already in use by a validator", @@ -107,11 +107,11 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint State: State{ ChainID(consumerName): ChainState{ AssignedKeys: &map[ValidatorID]string{ - ValidatorID("carol"): "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ConsumerValconsAddressOnProvider, ValidatorID("bob"): "", }, ProviderKeys: &map[ValidatorID]string{ - ValidatorID("carol"): "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ValconsAddress, }, }, }, @@ -235,7 +235,7 @@ func stepsAssignConsumerKeyOnStartedChain(consumerName, validator string) []Step Validator: ValidatorID("bob"), // reconfigure the node -> validator was using provider key // until this point -> key matches config.consumerValPubKey for "bob" - ConsumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="}`, + ConsumerPubkey: getDefaultValidators()[ValidatorID("bob")].ConsumerValPubKey, ReconfigureNode: true, }, State: State{ @@ -257,12 +257,12 @@ func stepsAssignConsumerKeyOnStartedChain(consumerName, validator string) []Step ValidatorID("carol"): 500, }, AssignedKeys: &map[ValidatorID]string{ - ValidatorID("bob"): "cosmosvalcons1uuec3cjxajv5te08p220usrjhkfhg9wyvqn0tm", - ValidatorID("carol"): "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", + ValidatorID("bob"): getDefaultValidators()[ValidatorID("bob")].ConsumerValconsAddressOnProvider, + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ConsumerValconsAddressOnProvider, }, ProviderKeys: &map[ValidatorID]string{ - ValidatorID("bob"): "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - ValidatorID("carol"): "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + ValidatorID("bob"): getDefaultValidators()[ValidatorID("bob")].ValconsAddress, + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ValconsAddress, }, }, }, @@ -293,12 +293,12 @@ func stepsAssignConsumerKeyOnStartedChain(consumerName, validator string) []Step ValidatorID("carol"): 500, }, AssignedKeys: &map[ValidatorID]string{ - ValidatorID("bob"): "cosmosvalcons1uuec3cjxajv5te08p220usrjhkfhg9wyvqn0tm", - ValidatorID("carol"): "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", + ValidatorID("bob"): getDefaultValidators()[ValidatorID("bob")].ConsumerValconsAddressOnProvider, + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ConsumerValconsAddressOnProvider, }, ProviderKeys: &map[ValidatorID]string{ - ValidatorID("bob"): "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - ValidatorID("carol"): "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + ValidatorID("bob"): getDefaultValidators()[ValidatorID("bob")].ValconsAddress, + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ValconsAddress, }, }, }, diff --git a/tests/e2e/testnet-scripts/fork-consumer.sh b/tests/e2e/testnet-scripts/fork-consumer.sh index 7c12438b71..db8199d192 100644 --- a/tests/e2e/testnet-scripts/fork-consumer.sh +++ b/tests/e2e/testnet-scripts/fork-consumer.sh @@ -53,7 +53,7 @@ enabled = true [[chains]] id = "consu" ccv_consumer_chain = true -account_prefix = "cosmos" +account_prefix = "consumer" clock_drift = "5s" gas_multiplier = 1.1 grpc_addr = "tcp://$CONS_CHAIN_PREFIX.252:9091" diff --git a/tests/e2e/testnet-scripts/sovereign-genesis.json b/tests/e2e/testnet-scripts/sovereign-genesis.json index c3ae9da36c..ba95b2fbce 100644 --- a/tests/e2e/testnet-scripts/sovereign-genesis.json +++ b/tests/e2e/testnet-scripts/sovereign-genesis.json @@ -35,7 +35,7 @@ "accounts": [ { "@type": "/cosmos.auth.v1beta1.BaseAccount", - "address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + "address": "consumer19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtz33vu", "pub_key": null, "account_number": "0", "sequence": "0" @@ -52,7 +52,7 @@ }, "balances": [ { - "address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + "address": "consumer19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtz33vu", "coins": [ { "denom": "stake", @@ -125,8 +125,8 @@ "max_change_rate": "0.010000000000000000" }, "min_self_delegation": "1", - "delegator_address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", - "validator_address": "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", + "delegator_address": "consumer19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtz33vu", + "validator_address": "consumervaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddy6jwzg", "pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "RrclQz9bIhkIy/gfL485g3PYMeiIku4qeo495787X10=" @@ -166,7 +166,7 @@ "tip": null }, "signatures": [ - "c7aD9dWzb85fn+Aq0ijMdhyJNJSOsOcFLvJt8ctvdxAAbwdrzKPVFbq9IYf1qCwKmfmQUrlFy40qiuQeXaZ8pg==" + "AZROMEeaBL9cDOWQJYYdAG3KDl+w37SK0XP88ecS+WwQQLj8rXuEKNDl1PXpZR0AFIJ8coSwhFEtbpV44j6uVQ==" ] } ] diff --git a/x/ccv/consumer/keeper/keeper.go b/x/ccv/consumer/keeper/keeper.go index a43a773862..b8751344b8 100644 --- a/x/ccv/consumer/keeper/keeper.go +++ b/x/ccv/consumer/keeper/keeper.go @@ -501,13 +501,9 @@ func (k Keeper) SetOutstandingDowntime(ctx sdk.Context, address sdk.ConsAddress) } // DeleteOutstandingDowntime deletes the outstanding downtime flag for the given validator consensus address -func (k Keeper) DeleteOutstandingDowntime(ctx sdk.Context, consAddress string) { - consAddr, err := sdk.ConsAddressFromBech32(consAddress) - if err != nil { - return // TODO: this should panic with appropriate tests to validate the panic won't happen in normal cases. - } +func (k Keeper) DeleteOutstandingDowntime(ctx sdk.Context, address sdk.ConsAddress) { store := ctx.KVStore(k.storeKey) - store.Delete(types.OutstandingDowntimeKey(consAddr)) + store.Delete(types.OutstandingDowntimeKey(address)) } // GetAllOutstandingDowntimes gets an array of the validator addresses of outstanding downtime flags diff --git a/x/ccv/consumer/keeper/relay.go b/x/ccv/consumer/keeper/relay.go index 0de4b0c35f..2d4e16510a 100644 --- a/x/ccv/consumer/keeper/relay.go +++ b/x/ccv/consumer/keeper/relay.go @@ -82,8 +82,19 @@ func (k Keeper) OnRecvVSCPacket(ctx sdk.Context, packet channeltypes.Packet, new // remove outstanding slashing flags of the validators // for which the slashing was acknowledged by the provider chain - for _, addr := range newChanges.GetSlashAcks() { - k.DeleteOutstandingDowntime(ctx, addr) + for _, ack := range newChanges.GetSlashAcks() { + // get consensus address from bech32 address + consAddr, err := ccv.GetConsAddrFromBech32(ack) + if err != nil { + // Do not return an error as it would lead to the consumer being + // removed by the provider + k.Logger(ctx).Error("invalid consensus address in VSCPacket.SlashAcks", + "vscID", newChanges.ValsetUpdateId, + "SlashAck", ack, + "error", err) + continue + } + k.DeleteOutstandingDowntime(ctx, consAddr) } k.Logger(ctx).Info("finished receiving/handling VSCPacket", diff --git a/x/ccv/consumer/keeper/relay_test.go b/x/ccv/consumer/keeper/relay_test.go index 7c30c3e4f0..46a805a85a 100644 --- a/x/ccv/consumer/keeper/relay_test.go +++ b/x/ccv/consumer/keeper/relay_test.go @@ -123,8 +123,8 @@ func TestOnRecvVSCPacket(t *testing.T) { }}, }, { - "failure on packet with invalid slash acks", - true, + "success on packet with invalid slash acks", + false, channeltypes.NewPacket(pd3.GetBytes(), 4, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, clienttypes.NewHeight(1, 0), 0), types.ValidatorSetChangePacketData{ValidatorUpdates: []abci.ValidatorUpdate{ diff --git a/x/ccv/consumer/keeper/validators.go b/x/ccv/consumer/keeper/validators.go index 31e803bad4..24a1c5a57c 100644 --- a/x/ccv/consumer/keeper/validators.go +++ b/x/ccv/consumer/keeper/validators.go @@ -59,6 +59,10 @@ func (k Keeper) ApplyCCValidatorChanges(ctx sdk.Context, changes []abci.Validato // AfterValidatorBonded is called by the Slashing module and should not return an error. panic(err) } + // Sanity check: making sure the outstanding downtime flag is not + // set for this new validator. This is especially useful to deal with + // https://github.com/cosmos/interchain-security/issues/1569. + k.DeleteOutstandingDowntime(ctx, consAddr) } else { // edge case: we received an update for 0 power // but the validator is already deleted. Do not forward diff --git a/x/ccv/types/utils.go b/x/ccv/types/utils.go index f6e7ffc3aa..ae85240256 100644 --- a/x/ccv/types/utils.go +++ b/x/ccv/types/utils.go @@ -1,8 +1,10 @@ package types import ( + "errors" "reflect" "sort" + "strings" "time" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" @@ -13,6 +15,7 @@ import ( cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/bech32" abci "github.com/cometbft/cometbft/abci/types" tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" @@ -106,3 +109,17 @@ func PanicIfZeroOrNil(x interface{}, nameForPanicMsg string) { panic("zero or nil value for " + nameForPanicMsg) } } + +// GetConsAddrFromBech32 returns a ConsAddress from a Bech32 with an arbitrary prefix +func GetConsAddrFromBech32(bech32str string) (sdk.ConsAddress, error) { + bech32Addr := strings.TrimSpace(bech32str) + if len(bech32Addr) == 0 { + return nil, errors.New("couldn't parse empty input") + } + // remove bech32 prefix + _, addr, err := bech32.DecodeAndConvert(bech32Addr) + if err != nil { + return nil, errors.New("couldn't find valid bech32") + } + return sdk.ConsAddress(addr), nil +} diff --git a/x/ccv/types/wire.go b/x/ccv/types/wire.go index c7cbe9e126..9c22522b74 100644 --- a/x/ccv/types/wire.go +++ b/x/ccv/types/wire.go @@ -30,13 +30,6 @@ func (vsc ValidatorSetChangePacketData) Validate() error { if vsc.ValsetUpdateId == 0 { return errorsmod.Wrap(ErrInvalidPacketData, "valset update id cannot be equal to zero") } - // Validate the slash acks - must be consensus addresses - for _, ack := range vsc.SlashAcks { - _, err := sdk.ConsAddressFromBech32(ack) - if err != nil { - return err - } - } return nil } diff --git a/x/ccv/types/wire_test.go b/x/ccv/types/wire_test.go index e8ba966c8c..ab6692912e 100644 --- a/x/ccv/types/wire_test.go +++ b/x/ccv/types/wire_test.go @@ -17,93 +17,40 @@ import ( ) func TestPacketDataValidateBasic(t *testing.T) { - pk1, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) - require.NoError(t, err) - pk2, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) + pk, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) require.NoError(t, err) - cId := crypto.NewCryptoIdentityFromIntSeed(4732894342) - validSlashAck := cId.SDKValConsAddress().String() - tooLongSlashAck := string(make([]byte, 1024)) - cases := []struct { name string expError bool packetData types.ValidatorSetChangePacketData }{ { - "invalid: nil packet data", + "invalid: zero ValsetUpdateId", true, - types.NewValidatorSetChangePacketData(nil, 1, nil), + types.NewValidatorSetChangePacketData([]abci.ValidatorUpdate{}, 0, nil), }, { - "valid: empty packet data", - false, - types.NewValidatorSetChangePacketData([]abci.ValidatorUpdate{}, 2, nil), - }, - { - "invalid: slash ack not consensus address", + "invalid: nil ValidatorUpdates", true, - types.NewValidatorSetChangePacketData( - []abci.ValidatorUpdate{ - { - PubKey: pk1, - Power: 30, - }, - }, - 3, - []string{ - "some_string", - }, - ), + types.NewValidatorSetChangePacketData(nil, 1, nil), }, { - "valid: packet data with valid slash ack", + "valid: empty ValidatorUpdates", false, - types.NewValidatorSetChangePacketData( - []abci.ValidatorUpdate{ - { - PubKey: pk2, - Power: 20, - }, - }, - 4, - []string{ - validSlashAck, - }, - ), - }, - { - "invalid: slash ack is too long", - true, - types.NewValidatorSetChangePacketData( - []abci.ValidatorUpdate{ - { - PubKey: pk2, - Power: 20, - }, - }, - 5, - []string{ - tooLongSlashAck, - }, - ), + types.NewValidatorSetChangePacketData([]abci.ValidatorUpdate{}, 2, nil), }, { - "valid: packet data with nil slash ack", + "valid: one validator update", false, types.NewValidatorSetChangePacketData( []abci.ValidatorUpdate{ { - PubKey: pk1, + PubKey: pk, Power: 30, }, - { - PubKey: pk2, - Power: 20, - }, }, - 6, + 3, nil, ), }, From d463df79ba375d6c917a659b9c145a2ceda7c705 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Mon, 22 Jan 2024 09:39:42 +0100 Subject: [PATCH 06/18] docs: update changelog for v4.0.0 (#1578) update changelog --- .../bug-fixes/consumer}/1570-slashack-bech32.md | 0 .../consumer}/1570-slashack-bech32.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .changelog/{unreleased/state-breaking => v4.0.0/bug-fixes/consumer}/1570-slashack-bech32.md (100%) rename .changelog/v4.0.0/{bug-fixes => state-breaking/consumer}/1570-slashack-bech32.md (100%) diff --git a/.changelog/unreleased/state-breaking/1570-slashack-bech32.md b/.changelog/v4.0.0/bug-fixes/consumer/1570-slashack-bech32.md similarity index 100% rename from .changelog/unreleased/state-breaking/1570-slashack-bech32.md rename to .changelog/v4.0.0/bug-fixes/consumer/1570-slashack-bech32.md diff --git a/.changelog/v4.0.0/bug-fixes/1570-slashack-bech32.md b/.changelog/v4.0.0/state-breaking/consumer/1570-slashack-bech32.md similarity index 100% rename from .changelog/v4.0.0/bug-fixes/1570-slashack-bech32.md rename to .changelog/v4.0.0/state-breaking/consumer/1570-slashack-bech32.md From 30e253dd8c0c308fda3f176f043f79446a4400e4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:43:25 +0100 Subject: [PATCH 07/18] chore: fix spelling errors (backport #1579) (#1580) chore: fix spelling errors (#1579) chore: spelling errors fixes Co-authored-by: github-merge-queue (cherry picked from commit eed76270de6e1b455cf938dfe6bf943815bc7b4c) Co-authored-by: Cosmos SDK <113218068+github-prbot@users.noreply.github.com> Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> --- UPGRADING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 851f31fe78..1267d3ac66 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -19,7 +19,7 @@ Upgrading a consumer from `v3.2.0` to `v4.0.0` will not require state migration, In addition, the following migration needs to be added to the upgrade handler of the consumer chain: ```golang func migrateICSOutstandingDowntime(ctx sdk.Context, keepers *upgrades.UpgradeKeepers) error { - ctx.Logger().Info("Migrating ICS oustanding downtime...") + ctx.Logger().Info("Migrating ICS outstanding downtime...") downtimes := keepers.ConsumerKeeper.GetAllOutstandingDowntimes(ctx) for _, od := range downtimes { @@ -30,7 +30,7 @@ func migrateICSOutstandingDowntime(ctx sdk.Context, keepers *upgrades.UpgradeKee keepers.ConsumerKeeper.DeleteOutstandingDowntime(ctx, consAddr) } - ctx.Logger().Info("Finished ICS oustanding downtime") + ctx.Logger().Info("Finished ICS outstanding downtime") return nil } From cca3e98795e4398aedda224a6c6e748fa0441aa6 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Mon, 22 Jan 2024 09:54:28 +0100 Subject: [PATCH 08/18] docs: prepare for v4.0.0 (#1581) * unclog build * update release notes * update release date --- .changelog/v4.0.0/summary.md | 2 +- CHANGELOG.md | 6 +++++- RELEASE_NOTES.md | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.changelog/v4.0.0/summary.md b/.changelog/v4.0.0/summary.md index 4561a0c2f8..8a69aefd10 100644 --- a/.changelog/v4.0.0/summary.md +++ b/.changelog/v4.0.0/summary.md @@ -1 +1 @@ -*January 11, 2024* +*January 22, 2024* diff --git a/CHANGELOG.md b/CHANGELOG.md index 365b7ea805..c33ee485f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v4.0.0 -*January 11, 2024* +*January 22, 2024* ### API BREAKING @@ -22,6 +22,8 @@ - Avoid jailing validators immediately once they can no longer opt-out from validating consumer chains. ([\#1549](https://github.com/cosmos/interchain-security/pull/1549)) + - Fix the validation of VSCPackets to not fail due to marshaling to string using Bech32. + ([\#1570](https://github.com/cosmos/interchain-security/pull/1570)) ### DEPENDENCIES @@ -46,6 +48,8 @@ - Avoid jailing validators immediately once they can no longer opt-out from validating consumer chains. ([\#1549](https://github.com/cosmos/interchain-security/pull/1549)) + - Fix the validation of VSCPackets to not fail due to marshaling to string using Bech32. + ([\#1570](https://github.com/cosmos/interchain-security/pull/1570)) - [Provider](x/ccv/provider) - Add the provider-side changes for jail throttling with retries (cf. ADR 008). ([\#1321](https://github.com/cosmos/interchain-security/pull/1321)) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f833594abe..913cb9faef 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -14,4 +14,5 @@ This release introduces the following noteworthy changes: - It sets the minimum required version of Go to `1.21`. - It adds the provider-side changes for jail throttling with retries and, as a result, it fully enables the jail throttling with retries feature (cf. [ADR 008](https://github.com/cosmos/interchain-security/blob/release/v3.2.x/docs/docs/adrs/adr-008-throttle-retries.md)). - Fixes a bug in the soft opt-out protocol -- it avoids jailing validators immediately once they can no longer opt-out from validating consumer chains. +- Fixes a bug the validation of VSCPackets caused by marshaling to string using Bech32. From 66d8f0615ccf1b982bb74c088c5617ec3845b050 Mon Sep 17 00:00:00 2001 From: insumity Date: Mon, 5 Feb 2024 09:27:26 +0100 Subject: [PATCH 09/18] feat!: enable Opt In and Top N chains through gov proposals (#1615) * init commit * added test * fixed tests * added changelog entry and comment * Update x/ccv/provider/keeper/proposal_test.go Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * Update .changelog/unreleased/features/1587-enable-opt-in-chains-through-gov-proposals.md Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * Update proto/interchain_security/ccv/provider/v1/provider.proto Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * Update proto/interchain_security/ccv/provider/v1/provider.proto Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * Update proto/interchain_security/ccv/provider/v1/provider.proto Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * Update proto/interchain_security/ccv/provider/v1/provider.proto Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * Update x/ccv/provider/keeper/keeper.go Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * changed to tabular test --------- Co-authored-by: insumity Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> --- ...ble-opt-in-chains-through-gov-proposals.md | 2 + .../ccv/provider/v1/provider.proto | 5 + testutil/keeper/unit_test_helpers.go | 1 + x/ccv/provider/client/proposal_handler.go | 6 +- x/ccv/provider/keeper/keeper.go | 48 ++++ x/ccv/provider/keeper/keeper_test.go | 31 +++ x/ccv/provider/keeper/proposal.go | 7 + x/ccv/provider/keeper/proposal_test.go | 18 ++ x/ccv/provider/proposal_handler_test.go | 1 + x/ccv/provider/types/keys.go | 8 + x/ccv/provider/types/keys_test.go | 1 + x/ccv/provider/types/proposal.go | 8 + x/ccv/provider/types/proposal_test.go | 44 +++- x/ccv/provider/types/provider.pb.go | 247 ++++++++++-------- 14 files changed, 304 insertions(+), 123 deletions(-) create mode 100644 .changelog/unreleased/features/1587-enable-opt-in-chains-through-gov-proposals.md diff --git a/.changelog/unreleased/features/1587-enable-opt-in-chains-through-gov-proposals.md b/.changelog/unreleased/features/1587-enable-opt-in-chains-through-gov-proposals.md new file mode 100644 index 0000000000..57f16adc9b --- /dev/null +++ b/.changelog/unreleased/features/1587-enable-opt-in-chains-through-gov-proposals.md @@ -0,0 +1,2 @@ +- Enable Opt In and Top N chains through gov proposals. + ([\#1587](https://github.com/cosmos/interchain-security/pull/1587)) \ No newline at end of file diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index f9bdf0a53f..38be02864f 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -83,6 +83,11 @@ message ConsumerAdditionProposal { // chain. it is most relevant for chains performing a sovereign to consumer // changeover in order to maintain the existing ibc transfer channel string distribution_transmission_channel = 14; + // Corresponds to the percentage of validators that have to validate the chain under the Top N case. + // For example, 53 corresponds to a Top 53% chain, meaning that the top 53% provider validators by voting power + // have to validate the proposed consumer chain. top_N can either be 0 or any value in [50, 100]. + // A chain can join with top_N == 0 as an Opt In chain, or with top_N ∈ [50, 100] as a Top N chain. + uint32 top_N = 15; } // ConsumerRemovalProposal is a governance proposal on the provider chain to diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index dc901712ff..468b0cb5e9 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -272,6 +272,7 @@ func GetTestConsumerAdditionProp() *providertypes.ConsumerAdditionProposal { types.DefaultCCVTimeoutPeriod, types.DefaultTransferTimeoutPeriod, types.DefaultConsumerUnbondingPeriod, + 0, ).(*providertypes.ConsumerAdditionProposal) return prop diff --git a/x/ccv/provider/client/proposal_handler.go b/x/ccv/provider/client/proposal_handler.go index fa74bb953e..9b5674f4c7 100644 --- a/x/ccv/provider/client/proposal_handler.go +++ b/x/ccv/provider/client/proposal_handler.go @@ -64,7 +64,8 @@ Where proposal.json contains: "transfer_timeout_period": 3600000000000, "ccv_timeout_period": 2419200000000000, "unbonding_period": 1728000000000000, - "deposit": "10000stake" + "deposit": "10000stake", + "top_n": 0, } `, RunE: func(cmd *cobra.Command, args []string) error { @@ -86,7 +87,7 @@ Where proposal.json contains: proposal.GenesisHash, proposal.BinaryHash, proposal.SpawnTime, proposal.ConsumerRedistributionFraction, proposal.BlocksPerDistributionTransmission, proposal.DistributionTransmissionChannel, proposal.HistoricalEntries, - proposal.CcvTimeoutPeriod, proposal.TransferTimeoutPeriod, proposal.UnbondingPeriod) + proposal.CcvTimeoutPeriod, proposal.TransferTimeoutPeriod, proposal.UnbondingPeriod, proposal.TopN) from := clientCtx.GetFromAddress() @@ -241,6 +242,7 @@ type ConsumerAdditionProposalJSON struct { UnbondingPeriod time.Duration `json:"unbonding_period"` Deposit string `json:"deposit"` + TopN uint32 `json:"top_N"` } type ConsumerAdditionProposalReq struct { diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 510d957c5d..1a7fe69ee5 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -1136,3 +1136,51 @@ func (k Keeper) GetAllRegisteredAndProposedChainIDs(ctx sdk.Context) []string { return allConsumerChains } + +// SetTopN stores the N value associated to chain with `chainID` +func (k Keeper) SetTopN( + ctx sdk.Context, + chainID string, + N uint32, +) { + store := ctx.KVStore(k.storeKey) + + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, N) + + store.Set(types.TopNKey(chainID), buf) +} + +// DeleteTopN removes the N value associated to chain with `chainID` +func (k Keeper) DeleteTopN( + ctx sdk.Context, + chainID string, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.TopNKey(chainID)) +} + +// GetTopN returns (N, true) if chain `chainID` has a top N associated, and (0, false) otherwise. +func (k Keeper) GetTopN( + ctx sdk.Context, + chainID string, +) (uint32, bool) { + store := ctx.KVStore(k.storeKey) + buf := store.Get(types.TopNKey(chainID)) + if buf == nil { + return 0, false + } + return binary.BigEndian.Uint32(buf), true +} + +// IsTopN returns true if chain with `chainID` is a Top N chain (i.e., enforces at least one validator to validate chain `chainID`) +func (k Keeper) IsTopN(ctx sdk.Context, chainID string) bool { + topN, found := k.GetTopN(ctx, chainID) + return found && topN > 0 +} + +// IsOptIn returns true if chain with `chainID` is an Opt In chain (i.e., no validator is forced to validate chain `chainID`) +func (k Keeper) IsOptIn(ctx sdk.Context, chainID string) bool { + topN, found := k.GetTopN(ctx, chainID) + return !found || topN == 0 +} diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index 9c9d421fd9..4dc73b7f9e 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -628,3 +628,34 @@ func TestGetAllProposedConsumerChainIDs(t *testing.T) { } } } + +// TestTopN tests `SetTopN`, `GetTopN`, `IsTopN`, and `IsOptIn` methods +func TestTopN(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + tests := []struct { + chainID string + N uint32 + isOptIn bool + }{ + {chainID: "TopNChain1", N: 50, isOptIn: false}, + {chainID: "TopNChain2", N: 100, isOptIn: false}, + {chainID: "OptInChain", N: 0, isOptIn: true}, + } + + for _, test := range tests { + providerKeeper.SetTopN(ctx, test.chainID, test.N) + topN, found := providerKeeper.GetTopN(ctx, test.chainID) + require.Equal(t, test.N, topN) + require.True(t, found) + + if test.isOptIn { + require.True(t, providerKeeper.IsOptIn(ctx, test.chainID)) + require.False(t, providerKeeper.IsTopN(ctx, test.chainID)) + } else { + require.False(t, providerKeeper.IsOptIn(ctx, test.chainID)) + require.True(t, providerKeeper.IsTopN(ctx, test.chainID)) + } + } +} diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 89d71ff344..50dc69d080 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -214,6 +214,8 @@ func (k Keeper) StopConsumerChain(ctx sdk.Context, chainID string, closeChan boo k.DeleteUnbondingOpIndex(ctx, chainID, unbondingOpsIndex.VscId) } + k.DeleteTopN(ctx, chainID) + k.Logger(ctx).Info("consumer chain removed from provider", "chainID", chainID) return nil @@ -365,6 +367,11 @@ func (k Keeper) BeginBlockInit(ctx sdk.Context) { ctx.Logger().Info("consumer client could not be created: %w", err) continue } + + // Only set Top N at the moment a chain starts. If we were to do this earlier (e.g., during the proposal), + // then someone could create a bogus ConsumerAdditionProposal to override the Top N for a specific chain. + k.SetTopN(ctx, prop.ChainId, prop.Top_N) + // The cached context is created with a new EventManager so we merge the event // into the original context ctx.EventManager().EmitEvents(cachedCtx.EventManager().Events()) diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index e78823899a..dacc456d2f 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -64,6 +64,7 @@ func TestHandleConsumerAdditionProposal(t *testing.T) { 100000000000, 100000000000, 100000000000, + 0, ).(*providertypes.ConsumerAdditionProposal), blockTime: now, expAppendProp: true, @@ -89,6 +90,7 @@ func TestHandleConsumerAdditionProposal(t *testing.T) { 100000000000, 100000000000, 100000000000, + 0, ).(*providertypes.ConsumerAdditionProposal), blockTime: now, expAppendProp: false, @@ -552,6 +554,10 @@ func TestStopConsumerChain(t *testing.T) { require.Error(t, err) } else { require.NoError(t, err) + + // in case the chain was successfully stopped, it should not contain a Top N associated to it + _, found := providerKeeper.GetTopN(ctx, "chainID") + require.False(t, found) } testkeeper.TestProviderStateIsCleanedAfterConsumerChainIsStopped(t, ctx, providerKeeper, "chainID", "channelID") @@ -923,6 +929,7 @@ func TestBeginBlockInit(t *testing.T) { 100000000000, 100000000000, 100000000000, + 67, ).(*providertypes.ConsumerAdditionProposal), providertypes.NewConsumerAdditionProposal( "title", "spawn time passed", "chain2", clienttypes.NewHeight(3, 4), []byte{}, []byte{}, @@ -934,6 +941,7 @@ func TestBeginBlockInit(t *testing.T) { 100000000000, 100000000000, 100000000000, + 0, ).(*providertypes.ConsumerAdditionProposal), providertypes.NewConsumerAdditionProposal( "title", "spawn time not passed", "chain3", clienttypes.NewHeight(3, 4), []byte{}, []byte{}, @@ -945,6 +953,7 @@ func TestBeginBlockInit(t *testing.T) { 100000000000, 100000000000, 100000000000, + 0, ).(*providertypes.ConsumerAdditionProposal), providertypes.NewConsumerAdditionProposal( "title", "invalid proposal: chain id already exists", "chain2", clienttypes.NewHeight(4, 5), []byte{}, []byte{}, @@ -956,6 +965,7 @@ func TestBeginBlockInit(t *testing.T) { 100000000000, 100000000000, 100000000000, + 0, ).(*providertypes.ConsumerAdditionProposal), } @@ -988,6 +998,13 @@ func TestBeginBlockInit(t *testing.T) { _, found = providerKeeper.GetPendingConsumerAdditionProp( ctx, pendingProps[3].SpawnTime, pendingProps[3].ChainId) require.False(t, found) + + // test that Top N is set correctly + require.True(t, providerKeeper.IsTopN(ctx, "chain1")) + topN, found := providerKeeper.GetTopN(ctx, "chain1") + require.Equal(t, uint32(67), topN) + + require.True(t, providerKeeper.IsOptIn(ctx, "chain2")) } // TestBeginBlockCCR tests BeginBlockCCR against the spec. @@ -1057,6 +1074,7 @@ func TestBeginBlockCCR(t *testing.T) { // // Test execution // + providerKeeper.BeginBlockCCR(ctx) // Only the 3rd (final) proposal is still stored as pending diff --git a/x/ccv/provider/proposal_handler_test.go b/x/ccv/provider/proposal_handler_test.go index 65e84ac661..30c1793360 100644 --- a/x/ccv/provider/proposal_handler_test.go +++ b/x/ccv/provider/proposal_handler_test.go @@ -44,6 +44,7 @@ func TestProviderProposalHandler(t *testing.T) { 100000000000, 100000000000, 100000000000, + 0, ), blockTime: hourFromNow, // ctx blocktime is after proposal's spawn time expValidConsumerAddition: true, diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 615b901368..47ad0417b2 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -145,6 +145,9 @@ const ( // ProposedConsumerChainByteKey is the byte prefix storing the consumer chainId in consumerAddition gov proposal submitted before voting finishes ProposedConsumerChainByteKey + // TopNBytePrefix is the byte prefix storing the mapping from a consumer chain to the N value of this chain, + // that corresponds to the N% of the top validators that have to validate this consumer chain + TopNBytePrefix // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -517,6 +520,11 @@ func ParseProposedConsumerChainKey(prefix byte, bz []byte) (uint64, error) { return proposalID, nil } +// TopNKey returns the key of consumer chain `chainID` +func TopNKey(chainID string) []byte { + return ChainIdWithLenKey(TopNBytePrefix, chainID) +} + // // End of generic helpers section // diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index 4d5ea58ff8..f0f316bf75 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -56,6 +56,7 @@ func getAllKeyPrefixes() []byte { providertypes.VSCMaturedHandledThisBlockBytePrefix, providertypes.EquivocationEvidenceMinHeightBytePrefix, providertypes.ProposedConsumerChainByteKey, + providertypes.TopNBytePrefix, } } diff --git a/x/ccv/provider/types/proposal.go b/x/ccv/provider/types/proposal.go index 93cadfd14e..10f3a88865 100644 --- a/x/ccv/provider/types/proposal.go +++ b/x/ccv/provider/types/proposal.go @@ -49,6 +49,7 @@ func NewConsumerAdditionProposal(title, description, chainID string, ccvTimeoutPeriod time.Duration, transferTimeoutPeriod time.Duration, unbondingPeriod time.Duration, + topN uint32, ) govv1beta1.Content { return &ConsumerAdditionProposal{ Title: title, @@ -65,6 +66,7 @@ func NewConsumerAdditionProposal(title, description, chainID string, CcvTimeoutPeriod: ccvTimeoutPeriod, TransferTimeoutPeriod: transferTimeoutPeriod, UnbondingPeriod: unbondingPeriod, + Top_N: topN, } } @@ -135,6 +137,12 @@ func (cccp *ConsumerAdditionProposal) ValidateBasic() error { return errorsmod.Wrap(ErrInvalidConsumerAdditionProposal, "unbonding period cannot be zero") } + // Top N corresponds to the top N% of validators that have to validate the consumer chain and can only be 0 (for an + // Opt In chain) or in the range [50, 100] (for a Top N chain). + if cccp.Top_N != 0 && cccp.Top_N < 50 || cccp.Top_N > 100 { + return errorsmod.Wrap(ErrInvalidConsumerAdditionProposal, "Top N can either be 0 or in the range [50, 100]") + } + return nil } diff --git a/x/ccv/provider/types/proposal_test.go b/x/ccv/provider/types/proposal_test.go index 72fcfe8436..13e7edb11e 100644 --- a/x/ccv/provider/types/proposal_test.go +++ b/x/ccv/provider/types/proposal_test.go @@ -36,6 +36,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 100000000000, 100000000000, 100000000000, + 0, ), true, }, @@ -48,7 +49,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 10000, 100000000000, 100000000000, - 100000000000), + 100000000000, + 0), true, }, { @@ -60,7 +62,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 10000, 100000000000, 100000000000, - 100000000000), + 100000000000, + 0), false, }, { @@ -72,7 +75,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 10000, 100000000000, 100000000000, - 100000000000), + 100000000000, + 0), false, }, { @@ -104,7 +108,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 10000, 100000000000, 100000000000, - 100000000000), + 100000000000, + 0), false, }, { @@ -116,7 +121,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 10000, 100000000000, 100000000000, - 100000000000), + 100000000000, + 0), false, }, { @@ -128,7 +134,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 10000, 100000000000, 100000000000, - 100000000000), + 100000000000, + 0), false, }, { @@ -140,7 +147,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 10000, 100000000000, 100000000000, - 100000000000), + 100000000000, + 0), false, }, { @@ -152,7 +160,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 100000000000, 10000, 100000000000, - 100000000000), + 100000000000, + 0), false, }, { @@ -164,7 +173,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 10000, 100000000000, 100000000000, - 100000000000), + 100000000000, + 0), false, }, { @@ -176,7 +186,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { -2, 100000000000, 100000000000, - 100000000000), + 100000000000, + 0), false, }, { @@ -188,7 +199,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 10000, 0, 100000000000, - 100000000000), + 100000000000, + 0), false, }, { @@ -200,7 +212,8 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 10000, 100000000000, 0, - 100000000000), + 100000000000, + 0), false, }, { @@ -212,6 +225,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 10000, 100000000000, 100000000000, + 0, 0), false, }, @@ -236,7 +250,8 @@ func TestMarshalConsumerAdditionProposal(t *testing.T) { 10000, 100000000000, 100000000000, - 100000000000) + 100000000000, + 0) cccp, ok := content.(*types.ConsumerAdditionProposal) require.True(t, ok) @@ -278,7 +293,8 @@ func TestConsumerAdditionProposalString(t *testing.T) { 500000, 100000000000, 10000000000, - 100000000000) + 100000000000, + 0) expect := fmt.Sprintf(`CreateConsumerChain Proposal Title: title diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index 41a87e69f4..edea9f4e02 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -90,6 +90,11 @@ type ConsumerAdditionProposal struct { // chain. it is most relevant for chains performing a sovereign to consumer // changeover in order to maintain the existing ibc transfer channel DistributionTransmissionChannel string `protobuf:"bytes,14,opt,name=distribution_transmission_channel,json=distributionTransmissionChannel,proto3" json:"distribution_transmission_channel,omitempty"` + // Corresponds to the percentage of validators that join under the Top N case. + // For example, 53 corresponds to a Top 53% chain, meaning that the top 53% provider validators + // have to validate the proposed consumer chain. top_N can be 0 or include any value in [50, 100]. + // A chain can join with top_N == 0 as an Opt In, or with top_N ∈ [50, 100] as a Top N chain. + Top_N uint32 `protobuf:"varint,15,opt,name=top_N,json=topN,proto3" json:"top_N,omitempty"` } func (m *ConsumerAdditionProposal) Reset() { *m = ConsumerAdditionProposal{} } @@ -1415,113 +1420,114 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1694 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x72, 0x1b, 0xc7, - 0x11, 0xe6, 0x12, 0x20, 0x45, 0x34, 0xf8, 0xa7, 0x25, 0x6d, 0x2d, 0x15, 0x06, 0xa4, 0xd6, 0xb1, - 0xc3, 0x94, 0xcb, 0x8b, 0x90, 0x4e, 0xaa, 0x5c, 0xaa, 0xb8, 0x5c, 0x24, 0x28, 0x5b, 0x14, 0x63, - 0x8b, 0x5e, 0x32, 0x54, 0x25, 0x39, 0x6c, 0x0d, 0x66, 0x47, 0xc0, 0x14, 0x17, 0x3b, 0xab, 0x99, - 0xd9, 0x95, 0x71, 0xc9, 0x39, 0x47, 0xe7, 0xe6, 0xca, 0x25, 0x4e, 0x5e, 0x20, 0xe7, 0xbc, 0x81, - 0x8f, 0x3e, 0xe6, 0x64, 0xa7, 0xa4, 0x63, 0x5e, 0x22, 0x35, 0xb3, 0xff, 0x20, 0xa1, 0x40, 0xe5, - 0xe4, 0x36, 0xdb, 0xd3, 0xfd, 0x75, 0xcf, 0x74, 0xf7, 0xd7, 0x03, 0xc0, 0x01, 0x0d, 0x25, 0xe1, - 0x78, 0x88, 0x68, 0xe8, 0x09, 0x82, 0x63, 0x4e, 0xe5, 0xb8, 0x8b, 0x71, 0xd2, 0x8d, 0x38, 0x4b, - 0xa8, 0x4f, 0x78, 0x37, 0xd9, 0x2f, 0xd6, 0x4e, 0xc4, 0x99, 0x64, 0xe6, 0x5b, 0x37, 0xd8, 0x38, - 0x18, 0x27, 0x4e, 0xa1, 0x97, 0xec, 0xdf, 0x7d, 0x7b, 0x1a, 0x70, 0xb2, 0xdf, 0x7d, 0x4e, 0x39, - 0x49, 0xb1, 0xee, 0x6e, 0x0e, 0xd8, 0x80, 0xe9, 0x65, 0x57, 0xad, 0x32, 0xe9, 0xce, 0x80, 0xb1, - 0x41, 0x40, 0xba, 0xfa, 0xab, 0x1f, 0x3f, 0xed, 0x4a, 0x3a, 0x22, 0x42, 0xa2, 0x51, 0x94, 0x29, - 0x74, 0x26, 0x15, 0xfc, 0x98, 0x23, 0x49, 0x59, 0x98, 0x03, 0xd0, 0x3e, 0xee, 0x62, 0xc6, 0x49, - 0x17, 0x07, 0x94, 0x84, 0x52, 0x79, 0x4d, 0x57, 0x99, 0x42, 0x57, 0x29, 0x04, 0x74, 0x30, 0x94, - 0xa9, 0x58, 0x74, 0x25, 0x09, 0x7d, 0xc2, 0x47, 0x34, 0x55, 0x2e, 0xbf, 0x32, 0x83, 0xed, 0xca, - 0x3e, 0xe6, 0xe3, 0x48, 0xb2, 0xee, 0x15, 0x19, 0x8b, 0x6c, 0xf7, 0x1d, 0xcc, 0xc4, 0x88, 0x89, - 0x2e, 0x51, 0xe7, 0x0f, 0x31, 0xe9, 0x26, 0xfb, 0x7d, 0x22, 0xd1, 0x7e, 0x21, 0xc8, 0xe3, 0xce, - 0xf4, 0xfa, 0x48, 0x94, 0x3a, 0x98, 0xd1, 0x2c, 0x6e, 0xfb, 0xfb, 0x45, 0xb0, 0x7a, 0x2c, 0x14, - 0xf1, 0x88, 0xf0, 0x43, 0xdf, 0xa7, 0xea, 0x48, 0x67, 0x9c, 0x45, 0x4c, 0xa0, 0xc0, 0xdc, 0x84, - 0x05, 0x49, 0x65, 0x40, 0x2c, 0x63, 0xd7, 0xd8, 0x6b, 0xb9, 0xe9, 0x87, 0xb9, 0x0b, 0x6d, 0x9f, - 0x08, 0xcc, 0x69, 0xa4, 0x94, 0xad, 0x79, 0xbd, 0x57, 0x15, 0x99, 0x5b, 0xb0, 0x94, 0xe6, 0x81, - 0xfa, 0x56, 0x43, 0x6f, 0xdf, 0xd2, 0xdf, 0x27, 0xbe, 0xf9, 0x09, 0xac, 0xd2, 0x90, 0x4a, 0x8a, - 0x02, 0x6f, 0x48, 0xd4, 0x6d, 0x58, 0xcd, 0x5d, 0x63, 0xaf, 0x7d, 0x70, 0xd7, 0xa1, 0x7d, 0xec, - 0xa8, 0x0b, 0x74, 0xb2, 0x6b, 0x4b, 0xf6, 0x9d, 0x87, 0x5a, 0xe3, 0xa8, 0xf9, 0xcd, 0x77, 0x3b, - 0x73, 0xee, 0x4a, 0x66, 0x97, 0x0a, 0xcd, 0x7b, 0xb0, 0x3c, 0x20, 0x21, 0x11, 0x54, 0x78, 0x43, - 0x24, 0x86, 0xd6, 0xc2, 0xae, 0xb1, 0xb7, 0xec, 0xb6, 0x33, 0xd9, 0x43, 0x24, 0x86, 0xe6, 0x0e, - 0xb4, 0xfb, 0x34, 0x44, 0x7c, 0x9c, 0x6a, 0x2c, 0x6a, 0x0d, 0x48, 0x45, 0x5a, 0xa1, 0x07, 0x20, - 0x22, 0xf4, 0x3c, 0xf4, 0x54, 0xb6, 0xad, 0x5b, 0x59, 0x20, 0x69, 0xa6, 0x9d, 0x3c, 0xd3, 0xce, - 0x45, 0x5e, 0x0a, 0x47, 0x4b, 0x2a, 0x90, 0x2f, 0xbf, 0xdf, 0x31, 0xdc, 0x96, 0xb6, 0x53, 0x3b, - 0xe6, 0x67, 0xb0, 0x1e, 0x87, 0x7d, 0x16, 0xfa, 0x34, 0x1c, 0x78, 0x11, 0xe1, 0x94, 0xf9, 0xd6, - 0x92, 0x86, 0xda, 0xba, 0x06, 0x75, 0x9c, 0x15, 0x4d, 0x8a, 0xf4, 0x95, 0x42, 0x5a, 0x2b, 0x8c, - 0xcf, 0xb4, 0xad, 0xf9, 0x39, 0x98, 0x18, 0x27, 0x3a, 0x24, 0x16, 0xcb, 0x1c, 0xb1, 0x35, 0x3b, - 0xe2, 0x3a, 0xc6, 0xc9, 0x45, 0x6a, 0x9d, 0x41, 0xfe, 0x1e, 0xee, 0x48, 0x8e, 0x42, 0xf1, 0x94, - 0xf0, 0x49, 0x5c, 0x98, 0x1d, 0xf7, 0x8d, 0x1c, 0xa3, 0x0e, 0xfe, 0x10, 0x76, 0x71, 0x56, 0x40, - 0x1e, 0x27, 0x3e, 0x15, 0x92, 0xd3, 0x7e, 0xac, 0x6c, 0xbd, 0xa7, 0x1c, 0x61, 0x5d, 0x23, 0x6d, - 0x5d, 0x04, 0x9d, 0x5c, 0xcf, 0xad, 0xa9, 0x7d, 0x9c, 0x69, 0x99, 0x8f, 0xe1, 0x27, 0xfd, 0x80, - 0xe1, 0x2b, 0xa1, 0x82, 0xf3, 0x6a, 0x48, 0xda, 0xf5, 0x88, 0x0a, 0xa1, 0xd0, 0x96, 0x77, 0x8d, - 0xbd, 0x86, 0x7b, 0x2f, 0xd5, 0x3d, 0x23, 0xfc, 0xb8, 0xa2, 0x79, 0x51, 0x51, 0x34, 0xdf, 0x03, - 0x73, 0x48, 0x85, 0x64, 0x9c, 0x62, 0x14, 0x78, 0x24, 0x94, 0x9c, 0x12, 0x61, 0xad, 0x68, 0xf3, - 0xdb, 0xe5, 0xce, 0x83, 0x74, 0xc3, 0x7c, 0x04, 0xf7, 0xa6, 0x3a, 0xf5, 0xf0, 0x10, 0x85, 0x21, - 0x09, 0xac, 0x55, 0x7d, 0x94, 0x1d, 0x7f, 0x8a, 0xcf, 0x5e, 0xaa, 0x76, 0x7f, 0xe9, 0x8f, 0x5f, - 0xef, 0xcc, 0x7d, 0xf5, 0xf5, 0xce, 0x9c, 0xfd, 0x77, 0x03, 0xee, 0xf4, 0x8a, 0x83, 0x8f, 0x58, - 0x82, 0x82, 0xff, 0x67, 0x83, 0x1d, 0x42, 0x4b, 0x48, 0x16, 0xa5, 0x25, 0xdd, 0x7c, 0x8d, 0x92, - 0x5e, 0x52, 0x66, 0x6a, 0xc3, 0xfe, 0x8b, 0x01, 0x9b, 0x0f, 0x9e, 0xc5, 0x34, 0x61, 0x18, 0xfd, - 0x4f, 0xf8, 0xe0, 0x14, 0x56, 0x48, 0x05, 0x4f, 0x58, 0x8d, 0xdd, 0xc6, 0x5e, 0xfb, 0xe0, 0x6d, - 0x27, 0x25, 0x27, 0xa7, 0xe0, 0xac, 0x8c, 0xa0, 0x9c, 0xaa, 0x77, 0xb7, 0x6e, 0x7b, 0x7f, 0xde, - 0x32, 0xec, 0xbf, 0x19, 0x70, 0x57, 0xdd, 0xf4, 0x80, 0xb8, 0xe4, 0x39, 0xe2, 0xfe, 0x31, 0x09, - 0xd9, 0x48, 0xfc, 0xe0, 0x38, 0x6d, 0x58, 0xf1, 0x35, 0x92, 0x27, 0x99, 0x87, 0x7c, 0x5f, 0xc7, - 0xa9, 0x75, 0x94, 0xf0, 0x82, 0x1d, 0xfa, 0xbe, 0xb9, 0x07, 0xeb, 0xa5, 0x0e, 0x57, 0xf9, 0x54, - 0xd7, 0xac, 0xd4, 0x56, 0x73, 0x35, 0x9d, 0x65, 0x62, 0xff, 0xdb, 0x80, 0xf5, 0x4f, 0x02, 0xd6, - 0x47, 0xc1, 0x79, 0x80, 0xc4, 0x50, 0x55, 0xd9, 0x58, 0xa5, 0x87, 0x93, 0xac, 0xbd, 0x75, 0x78, - 0x33, 0xa7, 0x47, 0x99, 0x69, 0xc2, 0xf9, 0x08, 0x6e, 0x17, 0x0d, 0x57, 0x54, 0x81, 0x3e, 0xcd, - 0xd1, 0xc6, 0x8b, 0xef, 0x76, 0xd6, 0xf2, 0x62, 0xeb, 0xe9, 0x8a, 0x38, 0x76, 0xd7, 0x70, 0x4d, - 0xe0, 0x9b, 0x1d, 0x68, 0xd3, 0x3e, 0xf6, 0x04, 0x79, 0xe6, 0x85, 0xf1, 0x48, 0x17, 0x50, 0xd3, - 0x6d, 0xd1, 0x3e, 0x3e, 0x27, 0xcf, 0x3e, 0x8b, 0x47, 0xe6, 0xfb, 0xf0, 0x66, 0x3e, 0x58, 0xbd, - 0x04, 0x05, 0x9e, 0xb2, 0x57, 0xd7, 0xc1, 0x75, 0x3d, 0x2d, 0xbb, 0x1b, 0xf9, 0xee, 0x25, 0x0a, - 0x94, 0xb3, 0x43, 0xdf, 0xe7, 0xf6, 0x3f, 0x16, 0x60, 0xf1, 0x0c, 0x71, 0x34, 0x12, 0xe6, 0x05, - 0xac, 0x49, 0x32, 0x8a, 0x02, 0x24, 0x89, 0x97, 0x92, 0x79, 0x76, 0xd2, 0x77, 0x35, 0xc9, 0x57, - 0x87, 0xa0, 0x53, 0x19, 0x7b, 0xc9, 0xbe, 0xd3, 0xd3, 0xd2, 0x73, 0x89, 0x24, 0x71, 0x57, 0x73, - 0x8c, 0x54, 0x68, 0x7e, 0x00, 0x96, 0xe4, 0xb1, 0x90, 0x25, 0xcd, 0x96, 0xfc, 0x92, 0xe6, 0xf2, - 0xcd, 0x7c, 0x3f, 0x65, 0xa6, 0x82, 0x57, 0x6e, 0x66, 0xd4, 0xc6, 0x0f, 0x61, 0xd4, 0x73, 0xd8, - 0x50, 0xe3, 0x68, 0x12, 0xb3, 0x39, 0x3b, 0xe6, 0x6d, 0x65, 0x5f, 0x07, 0xfd, 0x1c, 0xcc, 0x44, - 0xe0, 0x49, 0xcc, 0x85, 0xd7, 0x88, 0x33, 0x11, 0xb8, 0x0e, 0xe9, 0xc3, 0xb6, 0x50, 0xc5, 0xe7, - 0x8d, 0x88, 0xd4, 0xfc, 0x1c, 0x05, 0x24, 0xa4, 0x62, 0x98, 0x83, 0x2f, 0xce, 0x0e, 0xbe, 0xa5, - 0x81, 0x3e, 0x55, 0x38, 0x6e, 0x0e, 0x93, 0x79, 0xe9, 0x41, 0xe7, 0x66, 0x2f, 0x45, 0x82, 0x6e, - 0xe9, 0x04, 0xfd, 0xe8, 0x06, 0x88, 0x22, 0x4b, 0x02, 0xde, 0xa9, 0xcc, 0x11, 0xd5, 0xd5, 0x9e, - 0x6e, 0x28, 0x8f, 0x93, 0x81, 0x22, 0x5b, 0x94, 0x8e, 0x14, 0x42, 0x8a, 0x59, 0x98, 0xb1, 0x87, - 0x7a, 0xda, 0x14, 0xcc, 0xd1, 0x63, 0x34, 0xcc, 0x1e, 0x0c, 0x76, 0x39, 0x6e, 0x0a, 0x8e, 0x70, - 0x2b, 0x58, 0x1f, 0x13, 0xf2, 0xa8, 0xb9, 0xb4, 0xb4, 0xde, 0xb2, 0x7f, 0x06, 0x2d, 0xdd, 0xa2, - 0x87, 0xf8, 0x4a, 0x98, 0xdb, 0xd0, 0x52, 0xb5, 0x4e, 0x84, 0x20, 0xc2, 0x32, 0x74, 0x67, 0x97, - 0x02, 0x5b, 0xc2, 0xd6, 0xb4, 0xe7, 0x92, 0x30, 0x9f, 0xc0, 0xad, 0x88, 0xe8, 0x59, 0xae, 0x0d, - 0xdb, 0x07, 0x1f, 0x3a, 0x33, 0xbc, 0x5c, 0x9d, 0x69, 0x80, 0x6e, 0x8e, 0x66, 0xf3, 0xf2, 0x91, - 0x36, 0x31, 0x42, 0x84, 0x79, 0x39, 0xe9, 0xf4, 0x57, 0xaf, 0xe5, 0x74, 0x02, 0xaf, 0xf4, 0xf9, - 0x2e, 0xb4, 0x0f, 0xd3, 0x63, 0xff, 0x9a, 0x0a, 0x79, 0xfd, 0x5a, 0x96, 0xab, 0xd7, 0xf2, 0x08, - 0x56, 0xb3, 0xc9, 0x77, 0xc1, 0x34, 0xcd, 0x98, 0x3f, 0x06, 0xc8, 0x46, 0xa6, 0xa2, 0xa7, 0x94, - 0x88, 0x5b, 0x99, 0xe4, 0xc4, 0xaf, 0x4d, 0xb0, 0xf9, 0xda, 0x04, 0xb3, 0x5d, 0x58, 0xbb, 0x14, - 0xf8, 0x37, 0xf9, 0xb3, 0xe8, 0x71, 0x24, 0xcc, 0x37, 0x60, 0x51, 0x75, 0x46, 0x06, 0xd4, 0x74, - 0x17, 0x12, 0x81, 0x4f, 0x34, 0x17, 0x97, 0x4f, 0x2f, 0x16, 0x79, 0xd4, 0x17, 0xd6, 0xfc, 0x6e, - 0x63, 0xaf, 0xe9, 0xae, 0xc6, 0xa5, 0xf9, 0x89, 0x2f, 0xec, 0xdf, 0x42, 0xbb, 0x02, 0x68, 0xae, - 0xc2, 0x7c, 0x81, 0x35, 0x4f, 0x7d, 0xf3, 0x3e, 0x6c, 0x95, 0x40, 0x75, 0x72, 0x4d, 0x11, 0x5b, - 0xee, 0x9d, 0x42, 0xa1, 0xc6, 0xaf, 0xc2, 0x7e, 0x0c, 0x9b, 0x27, 0x65, 0x2b, 0x17, 0xd4, 0x5d, - 0x3b, 0xa1, 0x51, 0x9f, 0xd1, 0xdb, 0xd0, 0x2a, 0x7e, 0x5f, 0xe8, 0xd3, 0x37, 0xdd, 0x52, 0x60, - 0x8f, 0x60, 0xfd, 0x52, 0xe0, 0x73, 0x12, 0xfa, 0x25, 0xd8, 0x94, 0x0b, 0x38, 0x9a, 0x04, 0x9a, - 0xf9, 0xfd, 0x5a, 0xba, 0x63, 0xb0, 0x75, 0x89, 0x02, 0xea, 0x23, 0xc9, 0xf8, 0x39, 0x91, 0xe9, - 0x58, 0x3d, 0x43, 0xf8, 0x8a, 0x48, 0x61, 0xba, 0xd0, 0x0c, 0xa8, 0x90, 0x59, 0x65, 0x7d, 0x30, - 0xb5, 0xb2, 0x92, 0x7d, 0x67, 0x1a, 0xc8, 0x31, 0x92, 0x28, 0xeb, 0x48, 0x8d, 0x65, 0xff, 0x14, - 0x36, 0x3e, 0x45, 0x32, 0xe6, 0xc4, 0xaf, 0xe5, 0x78, 0x1d, 0x1a, 0x2a, 0x7f, 0x86, 0xce, 0x9f, - 0x5a, 0xaa, 0x29, 0x6f, 0x3d, 0xf8, 0x22, 0x62, 0x5c, 0x12, 0xff, 0xda, 0x8d, 0xbc, 0xe2, 0x7a, - 0xaf, 0x60, 0x43, 0x5d, 0x96, 0x20, 0xa1, 0xef, 0x15, 0xe7, 0x4c, 0xf3, 0xd8, 0x3e, 0xf8, 0xe5, - 0x4c, 0xdd, 0x31, 0xe9, 0x2e, 0x3b, 0xc0, 0xed, 0x64, 0x42, 0x2e, 0xec, 0x3f, 0x19, 0x60, 0x9d, - 0x92, 0xf1, 0xa1, 0x10, 0x74, 0x10, 0x8e, 0x48, 0x28, 0x15, 0xb3, 0x21, 0x4c, 0xd4, 0xd2, 0x7c, - 0x0b, 0x56, 0x8a, 0x49, 0xaa, 0x07, 0xa8, 0xa1, 0x07, 0xe8, 0x72, 0x2e, 0x54, 0x0d, 0x66, 0xde, - 0x07, 0x88, 0x38, 0x49, 0x3c, 0xec, 0x5d, 0x91, 0x71, 0x96, 0xc5, 0xed, 0xea, 0x60, 0x4c, 0x7f, - 0xfd, 0x39, 0x67, 0x71, 0x3f, 0xa0, 0xf8, 0x94, 0x8c, 0xdd, 0x25, 0xa5, 0xdf, 0x3b, 0x25, 0x63, - 0xf5, 0xd2, 0x89, 0xd8, 0x73, 0xc2, 0xf5, 0x34, 0x6b, 0xb8, 0xe9, 0x87, 0xfd, 0x67, 0x03, 0xee, - 0x14, 0xe9, 0xc8, 0xcb, 0xf5, 0x2c, 0xee, 0x2b, 0x8b, 0x57, 0xdc, 0xdb, 0xb5, 0x68, 0xe7, 0x6f, - 0x88, 0xf6, 0x23, 0x58, 0x2e, 0x1a, 0x44, 0xc5, 0xdb, 0x98, 0x21, 0xde, 0x76, 0x6e, 0x71, 0x4a, - 0xc6, 0xf6, 0x1f, 0x2a, 0xb1, 0x1d, 0x8d, 0x2b, 0xdc, 0xc7, 0xff, 0x4b, 0x6c, 0x85, 0xdb, 0x6a, - 0x6c, 0xb8, 0x6a, 0x7f, 0xed, 0x00, 0x8d, 0xeb, 0x07, 0xb0, 0xff, 0x6a, 0xc0, 0x66, 0xd5, 0xab, - 0xb8, 0x60, 0x67, 0x3c, 0x0e, 0xc9, 0xab, 0xbc, 0x97, 0xed, 0x37, 0x5f, 0x6d, 0xbf, 0x27, 0xb0, - 0x5a, 0x0b, 0x4a, 0x64, 0xb7, 0xf1, 0xf3, 0x99, 0x6a, 0xac, 0xc2, 0xae, 0xee, 0x4a, 0xf5, 0x1c, - 0xe2, 0xe8, 0xc9, 0x37, 0x2f, 0x3a, 0xc6, 0xb7, 0x2f, 0x3a, 0xc6, 0xbf, 0x5e, 0x74, 0x8c, 0x2f, - 0x5f, 0x76, 0xe6, 0xbe, 0x7d, 0xd9, 0x99, 0xfb, 0xe7, 0xcb, 0xce, 0xdc, 0xef, 0x3e, 0x1c, 0x50, - 0x39, 0x8c, 0xfb, 0x0e, 0x66, 0xa3, 0x6e, 0xf6, 0xd3, 0xbe, 0xf4, 0xf5, 0x5e, 0xf1, 0xbf, 0x47, - 0xf2, 0x8b, 0xee, 0x17, 0xf5, 0x7f, 0x55, 0xe4, 0x38, 0x22, 0xa2, 0xbf, 0xa8, 0x59, 0xe1, 0xfd, - 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x9f, 0x08, 0x80, 0x3d, 0x86, 0x11, 0x00, 0x00, + // 1712 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4f, 0x73, 0xdb, 0xc6, + 0x15, 0x17, 0x44, 0x4a, 0x16, 0x1f, 0xf5, 0xcf, 0x90, 0x12, 0x43, 0xae, 0x4a, 0xd1, 0x48, 0x93, + 0xaa, 0x93, 0x09, 0x58, 0x29, 0xed, 0x4c, 0xc6, 0xd3, 0x4c, 0x46, 0xa2, 0x9c, 0x58, 0x56, 0x63, + 0x2b, 0x90, 0x2a, 0x4f, 0xdb, 0x03, 0x66, 0xb9, 0x58, 0x93, 0x3b, 0x02, 0xb1, 0xf0, 0xee, 0x02, + 0x0e, 0x2f, 0x3d, 0xf7, 0x98, 0xde, 0x32, 0xbd, 0x34, 0xed, 0x17, 0xe8, 0xb9, 0xdf, 0x20, 0xc7, + 0x1c, 0x7b, 0x4a, 0x3b, 0xf2, 0xb1, 0xd7, 0x7e, 0x80, 0xce, 0x2e, 0xfe, 0x92, 0x12, 0x5d, 0x7a, + 0xdc, 0xde, 0x80, 0xb7, 0xef, 0xfd, 0xde, 0xdb, 0xf7, 0xe7, 0xf7, 0x40, 0xc2, 0x3e, 0x0d, 0x25, + 0xe1, 0x78, 0x80, 0x68, 0xe8, 0x09, 0x82, 0x63, 0x4e, 0xe5, 0xa8, 0x83, 0x71, 0xd2, 0x89, 0x38, + 0x4b, 0xa8, 0x4f, 0x78, 0x27, 0xd9, 0x2b, 0x9e, 0x9d, 0x88, 0x33, 0xc9, 0xcc, 0x77, 0x6e, 0xb0, + 0x71, 0x30, 0x4e, 0x9c, 0x42, 0x2f, 0xd9, 0xbb, 0xfb, 0xee, 0x34, 0xe0, 0x64, 0xaf, 0xf3, 0x82, + 0x72, 0x92, 0x62, 0xdd, 0xdd, 0xec, 0xb3, 0x3e, 0xd3, 0x8f, 0x1d, 0xf5, 0x94, 0x49, 0x77, 0xfa, + 0x8c, 0xf5, 0x03, 0xd2, 0xd1, 0x6f, 0xbd, 0xf8, 0x59, 0x47, 0xd2, 0x21, 0x11, 0x12, 0x0d, 0xa3, + 0x4c, 0xa1, 0x35, 0xa9, 0xe0, 0xc7, 0x1c, 0x49, 0xca, 0xc2, 0x1c, 0x80, 0xf6, 0x70, 0x07, 0x33, + 0x4e, 0x3a, 0x38, 0xa0, 0x24, 0x94, 0xca, 0x6b, 0xfa, 0x94, 0x29, 0x74, 0x94, 0x42, 0x40, 0xfb, + 0x03, 0x99, 0x8a, 0x45, 0x47, 0x92, 0xd0, 0x27, 0x7c, 0x48, 0x53, 0xe5, 0xf2, 0x2d, 0x33, 0xd8, + 0xae, 0x9c, 0x63, 0x3e, 0x8a, 0x24, 0xeb, 0x5c, 0x92, 0x91, 0xc8, 0x4e, 0xdf, 0xc3, 0x4c, 0x0c, + 0x99, 0xe8, 0x10, 0x75, 0xff, 0x10, 0x93, 0x4e, 0xb2, 0xd7, 0x23, 0x12, 0xed, 0x15, 0x82, 0x3c, + 0xee, 0x4c, 0xaf, 0x87, 0x44, 0xa9, 0x83, 0x19, 0xcd, 0xe2, 0xb6, 0xff, 0xbd, 0x08, 0x56, 0x97, + 0x85, 0x22, 0x1e, 0x12, 0x7e, 0xe0, 0xfb, 0x54, 0x5d, 0xe9, 0x94, 0xb3, 0x88, 0x09, 0x14, 0x98, + 0x9b, 0xb0, 0x20, 0xa9, 0x0c, 0x88, 0x65, 0xb4, 0x8d, 0xdd, 0x86, 0x9b, 0xbe, 0x98, 0x6d, 0x68, + 0xfa, 0x44, 0x60, 0x4e, 0x23, 0xa5, 0x6c, 0xcd, 0xeb, 0xb3, 0xaa, 0xc8, 0xdc, 0x82, 0xa5, 0xb4, + 0x0e, 0xd4, 0xb7, 0x6a, 0xfa, 0xf8, 0x96, 0x7e, 0x3f, 0xf6, 0xcd, 0xcf, 0x60, 0x95, 0x86, 0x54, + 0x52, 0x14, 0x78, 0x03, 0xa2, 0xb2, 0x61, 0xd5, 0xdb, 0xc6, 0x6e, 0x73, 0xff, 0xae, 0x43, 0x7b, + 0xd8, 0x51, 0x09, 0x74, 0xb2, 0xb4, 0x25, 0x7b, 0xce, 0x43, 0xad, 0x71, 0x58, 0xff, 0xf6, 0xfb, + 0x9d, 0x39, 0x77, 0x25, 0xb3, 0x4b, 0x85, 0xe6, 0x3d, 0x58, 0xee, 0x93, 0x90, 0x08, 0x2a, 0xbc, + 0x01, 0x12, 0x03, 0x6b, 0xa1, 0x6d, 0xec, 0x2e, 0xbb, 0xcd, 0x4c, 0xf6, 0x10, 0x89, 0x81, 0xb9, + 0x03, 0xcd, 0x1e, 0x0d, 0x11, 0x1f, 0xa5, 0x1a, 0x8b, 0x5a, 0x03, 0x52, 0x91, 0x56, 0xe8, 0x02, + 0x88, 0x08, 0xbd, 0x08, 0x3d, 0x55, 0x6d, 0xeb, 0x56, 0x16, 0x48, 0x5a, 0x69, 0x27, 0xaf, 0xb4, + 0x73, 0x9e, 0xb7, 0xc2, 0xe1, 0x92, 0x0a, 0xe4, 0xab, 0x7f, 0xec, 0x18, 0x6e, 0x43, 0xdb, 0xa9, + 0x13, 0xf3, 0x31, 0xac, 0xc7, 0x61, 0x8f, 0x85, 0x3e, 0x0d, 0xfb, 0x5e, 0x44, 0x38, 0x65, 0xbe, + 0xb5, 0xa4, 0xa1, 0xb6, 0xae, 0x41, 0x1d, 0x65, 0x4d, 0x93, 0x22, 0x7d, 0xad, 0x90, 0xd6, 0x0a, + 0xe3, 0x53, 0x6d, 0x6b, 0x7e, 0x01, 0x26, 0xc6, 0x89, 0x0e, 0x89, 0xc5, 0x32, 0x47, 0x6c, 0xcc, + 0x8e, 0xb8, 0x8e, 0x71, 0x72, 0x9e, 0x5a, 0x67, 0x90, 0xbf, 0x85, 0x3b, 0x92, 0xa3, 0x50, 0x3c, + 0x23, 0x7c, 0x12, 0x17, 0x66, 0xc7, 0x7d, 0x2b, 0xc7, 0x18, 0x07, 0x7f, 0x08, 0x6d, 0x9c, 0x35, + 0x90, 0xc7, 0x89, 0x4f, 0x85, 0xe4, 0xb4, 0x17, 0x2b, 0x5b, 0xef, 0x19, 0x47, 0x58, 0xf7, 0x48, + 0x53, 0x37, 0x41, 0x2b, 0xd7, 0x73, 0xc7, 0xd4, 0x3e, 0xcd, 0xb4, 0xcc, 0x27, 0xf0, 0xa3, 0x5e, + 0xc0, 0xf0, 0xa5, 0x50, 0xc1, 0x79, 0x63, 0x48, 0xda, 0xf5, 0x90, 0x0a, 0xa1, 0xd0, 0x96, 0xdb, + 0xc6, 0x6e, 0xcd, 0xbd, 0x97, 0xea, 0x9e, 0x12, 0x7e, 0x54, 0xd1, 0x3c, 0xaf, 0x28, 0x9a, 0x1f, + 0x80, 0x39, 0xa0, 0x42, 0x32, 0x4e, 0x31, 0x0a, 0x3c, 0x12, 0x4a, 0x4e, 0x89, 0xb0, 0x56, 0xb4, + 0xf9, 0xed, 0xf2, 0xe4, 0x41, 0x7a, 0x60, 0x3e, 0x82, 0x7b, 0x53, 0x9d, 0x7a, 0x78, 0x80, 0xc2, + 0x90, 0x04, 0xd6, 0xaa, 0xbe, 0xca, 0x8e, 0x3f, 0xc5, 0x67, 0x37, 0x55, 0x33, 0x37, 0x60, 0x41, + 0xb2, 0xc8, 0x7b, 0x6c, 0xad, 0xb5, 0x8d, 0xdd, 0x15, 0xb7, 0x2e, 0x59, 0xf4, 0xf8, 0xfe, 0xd2, + 0xef, 0xbf, 0xd9, 0x99, 0xfb, 0xfa, 0x9b, 0x9d, 0x39, 0xfb, 0xaf, 0x06, 0xdc, 0xe9, 0x16, 0xd9, + 0x18, 0xb2, 0x04, 0x05, 0xff, 0xcf, 0xa9, 0x3b, 0x80, 0x86, 0x50, 0xe1, 0xe8, 0x3e, 0xaf, 0xbf, + 0x46, 0x9f, 0x2f, 0x29, 0x33, 0x75, 0x60, 0xff, 0xc9, 0x80, 0xcd, 0x07, 0xcf, 0x63, 0x9a, 0x30, + 0x8c, 0xfe, 0x27, 0x24, 0x71, 0x02, 0x2b, 0xa4, 0x82, 0x27, 0xac, 0x5a, 0xbb, 0xb6, 0xdb, 0xdc, + 0x7f, 0xd7, 0x49, 0x19, 0xcb, 0x29, 0x88, 0x2c, 0x63, 0x2d, 0xa7, 0xea, 0xdd, 0x1d, 0xb7, 0xbd, + 0x3f, 0x6f, 0x19, 0xf6, 0x5f, 0x0c, 0xb8, 0xab, 0xd2, 0xdf, 0x27, 0x2e, 0x79, 0x81, 0xb8, 0x7f, + 0x44, 0x42, 0x36, 0x14, 0x6f, 0x1c, 0xa7, 0x0d, 0x2b, 0xbe, 0x46, 0xf2, 0x24, 0xf3, 0x90, 0xef, + 0xeb, 0x38, 0xb5, 0x8e, 0x12, 0x9e, 0xb3, 0x03, 0xdf, 0x37, 0x77, 0x61, 0xbd, 0xd4, 0xe1, 0xaa, + 0x9e, 0x2a, 0xcd, 0x4a, 0x6d, 0x35, 0x57, 0xd3, 0x55, 0x26, 0xf6, 0xbf, 0x0c, 0x58, 0xff, 0x2c, + 0x60, 0x3d, 0x14, 0x9c, 0x05, 0x48, 0x0c, 0x54, 0xeb, 0x8d, 0x54, 0x79, 0x38, 0xc9, 0x66, 0x5e, + 0x87, 0x37, 0x73, 0x79, 0x94, 0x99, 0x66, 0xa1, 0x4f, 0xe0, 0x76, 0x31, 0x85, 0x45, 0x17, 0xe8, + 0xdb, 0x1c, 0x6e, 0x5c, 0x7d, 0xbf, 0xb3, 0x96, 0x37, 0x5b, 0x57, 0x77, 0xc4, 0x91, 0xbb, 0x86, + 0xc7, 0x04, 0xbe, 0xd9, 0x82, 0x26, 0xed, 0x61, 0x4f, 0x90, 0xe7, 0x5e, 0x18, 0x0f, 0x75, 0x03, + 0xd5, 0xdd, 0x06, 0xed, 0xe1, 0x33, 0xf2, 0xfc, 0x71, 0x3c, 0x34, 0x3f, 0x84, 0xb7, 0xf3, 0x6d, + 0xeb, 0x25, 0x28, 0xf0, 0x94, 0xbd, 0x4a, 0x07, 0xd7, 0xfd, 0xb4, 0xec, 0x6e, 0xe4, 0xa7, 0x17, + 0x28, 0x50, 0xce, 0x0e, 0x7c, 0x9f, 0xdb, 0x7f, 0x5b, 0x80, 0xc5, 0x53, 0xc4, 0xd1, 0x50, 0x98, + 0xe7, 0xb0, 0x26, 0xc9, 0x30, 0x0a, 0x90, 0x24, 0x5e, 0xca, 0xf0, 0xd9, 0x4d, 0xdf, 0xd7, 0xcc, + 0x5f, 0xdd, 0x8c, 0x4e, 0x65, 0x17, 0x26, 0x7b, 0x4e, 0x57, 0x4b, 0xcf, 0x24, 0x92, 0xc4, 0x5d, + 0xcd, 0x31, 0x52, 0xa1, 0xf9, 0x11, 0x58, 0x92, 0xc7, 0x42, 0x96, 0xdc, 0x5b, 0x92, 0x4e, 0x5a, + 0xcb, 0xb7, 0xf3, 0xf3, 0x94, 0xae, 0x0a, 0xb2, 0xb9, 0x99, 0x66, 0x6b, 0x6f, 0x42, 0xb3, 0x67, + 0xb0, 0xa1, 0x76, 0xd4, 0x24, 0x66, 0x7d, 0x76, 0xcc, 0xdb, 0xca, 0x7e, 0x1c, 0xf4, 0x0b, 0x30, + 0x13, 0x81, 0x27, 0x31, 0x17, 0x5e, 0x23, 0xce, 0x44, 0xe0, 0x71, 0x48, 0x1f, 0xb6, 0x85, 0x6a, + 0x3e, 0x6f, 0x48, 0xa4, 0x26, 0xed, 0x28, 0x20, 0x21, 0x15, 0x83, 0x1c, 0x7c, 0x71, 0x76, 0xf0, + 0x2d, 0x0d, 0xf4, 0xb9, 0xc2, 0x71, 0x73, 0x98, 0xcc, 0x4b, 0x17, 0x5a, 0x37, 0x7b, 0x29, 0x0a, + 0x74, 0x4b, 0x17, 0xe8, 0x07, 0x37, 0x40, 0x14, 0x55, 0x12, 0xf0, 0x5e, 0x65, 0xb9, 0xa8, 0xa9, + 0xf6, 0xf4, 0x40, 0x79, 0x9c, 0xf4, 0x15, 0x03, 0xa3, 0x74, 0xcf, 0x10, 0x52, 0x2c, 0xc8, 0x8c, + 0x3d, 0xd4, 0xf7, 0x4e, 0xc1, 0x1c, 0x5d, 0x46, 0xc3, 0xec, 0x2b, 0xc2, 0x2e, 0x77, 0x50, 0xc1, + 0x11, 0x6e, 0x05, 0xeb, 0x53, 0x42, 0x1e, 0xd5, 0x97, 0x96, 0xd6, 0x1b, 0xf6, 0x4f, 0xa0, 0xa1, + 0x47, 0xf4, 0x00, 0x5f, 0x0a, 0x73, 0x1b, 0x1a, 0xaa, 0xd7, 0x89, 0x10, 0x44, 0x58, 0x86, 0x9e, + 0xec, 0x52, 0x60, 0x4b, 0xd8, 0x9a, 0xf6, 0x0d, 0x25, 0xcc, 0xa7, 0x70, 0x2b, 0x22, 0x7a, 0xc1, + 0x6b, 0xc3, 0xe6, 0xfe, 0xc7, 0xce, 0x0c, 0x9f, 0xb3, 0xce, 0x34, 0x40, 0x37, 0x47, 0xb3, 0x79, + 0xf9, 0xe5, 0x36, 0xb1, 0x42, 0x84, 0x79, 0x31, 0xe9, 0xf4, 0x17, 0xaf, 0xe5, 0x74, 0x02, 0xaf, + 0xf4, 0xf9, 0x3e, 0x34, 0x0f, 0xd2, 0x6b, 0xff, 0x92, 0x0a, 0x79, 0x3d, 0x2d, 0xcb, 0xd5, 0xb4, + 0x3c, 0x82, 0xd5, 0x6c, 0x1d, 0x9e, 0x33, 0x4d, 0x33, 0xe6, 0x0f, 0x01, 0xb2, 0x3d, 0xaa, 0xe8, + 0x29, 0x25, 0xe2, 0x46, 0x26, 0x39, 0xf6, 0xc7, 0x36, 0xd8, 0xfc, 0xd8, 0x06, 0xb3, 0x5d, 0x58, + 0xbb, 0x10, 0xf8, 0x57, 0xf9, 0xb7, 0xd2, 0x93, 0x48, 0x98, 0x6f, 0xc1, 0xa2, 0x9a, 0x8c, 0x0c, + 0xa8, 0xee, 0x2e, 0x24, 0x02, 0x1f, 0x6b, 0x2e, 0x2e, 0xbf, 0xc7, 0x58, 0xe4, 0x51, 0x5f, 0x58, + 0xf3, 0xed, 0xda, 0x6e, 0xdd, 0x5d, 0x8d, 0x4b, 0xf3, 0x63, 0x5f, 0xd8, 0xbf, 0x86, 0x66, 0x05, + 0xd0, 0x5c, 0x85, 0xf9, 0x02, 0x6b, 0x9e, 0xfa, 0xe6, 0x7d, 0xd8, 0x2a, 0x81, 0xc6, 0xc9, 0x35, + 0x45, 0x6c, 0xb8, 0x77, 0x0a, 0x85, 0x31, 0x7e, 0x15, 0xf6, 0x13, 0xd8, 0x3c, 0x2e, 0x47, 0xb9, + 0xa0, 0xee, 0xb1, 0x1b, 0x1a, 0xe3, 0x3b, 0x7a, 0x1b, 0x1a, 0xc5, 0x8f, 0x0e, 0x7d, 0xfb, 0xba, + 0x5b, 0x0a, 0xec, 0x21, 0xac, 0x5f, 0x08, 0x7c, 0x46, 0x42, 0xbf, 0x04, 0x9b, 0x92, 0x80, 0xc3, + 0x49, 0xa0, 0x99, 0x3f, 0x6a, 0x4b, 0x77, 0x0c, 0xb6, 0x2e, 0x50, 0x40, 0x7d, 0x24, 0x19, 0x3f, + 0x23, 0x32, 0x5d, 0xab, 0xa7, 0x08, 0x5f, 0x12, 0x29, 0x4c, 0x17, 0xea, 0x01, 0x15, 0x32, 0xeb, + 0xac, 0x8f, 0xa6, 0x76, 0x56, 0xb2, 0xe7, 0x4c, 0x03, 0x39, 0x42, 0x12, 0x65, 0x13, 0xa9, 0xb1, + 0xec, 0x1f, 0xc3, 0xc6, 0xe7, 0x48, 0xc6, 0x9c, 0xf8, 0x63, 0x35, 0x5e, 0x87, 0x9a, 0xaa, 0x9f, + 0xa1, 0xeb, 0xa7, 0x1e, 0xd5, 0x96, 0xb7, 0x1e, 0x7c, 0x19, 0x31, 0x2e, 0x89, 0x7f, 0x2d, 0x23, + 0xaf, 0x48, 0xef, 0x25, 0x6c, 0xa8, 0x64, 0x09, 0x12, 0xfa, 0x5e, 0x71, 0xcf, 0xb4, 0x8e, 0xcd, + 0xfd, 0x9f, 0xcf, 0x34, 0x1d, 0x93, 0xee, 0xb2, 0x0b, 0xdc, 0x4e, 0x26, 0xe4, 0xc2, 0xfe, 0x83, + 0x01, 0xd6, 0x09, 0x19, 0x1d, 0x08, 0x41, 0xfb, 0xe1, 0x90, 0x84, 0x52, 0x31, 0x1b, 0xc2, 0x44, + 0x3d, 0x9a, 0xef, 0xc0, 0x4a, 0xb1, 0x49, 0xf5, 0x02, 0x35, 0xf4, 0x02, 0x5d, 0xce, 0x85, 0x6a, + 0xc0, 0xcc, 0xfb, 0x00, 0x11, 0x27, 0x89, 0x87, 0xbd, 0x4b, 0x32, 0xca, 0xaa, 0xb8, 0x5d, 0x5d, + 0x8c, 0xe9, 0x4f, 0x42, 0xe7, 0x34, 0xee, 0x05, 0x14, 0x9f, 0x90, 0x91, 0xbb, 0xa4, 0xf4, 0xbb, + 0x27, 0x64, 0xa4, 0xbe, 0x74, 0x22, 0xf6, 0x82, 0x70, 0xbd, 0xcd, 0x6a, 0x6e, 0xfa, 0x62, 0xff, + 0xd1, 0x80, 0x3b, 0x45, 0x39, 0xf2, 0x76, 0x3d, 0x8d, 0x7b, 0xca, 0xe2, 0x15, 0x79, 0xbb, 0x16, + 0xed, 0xfc, 0x0d, 0xd1, 0x7e, 0x02, 0xcb, 0xc5, 0x80, 0xa8, 0x78, 0x6b, 0x33, 0xc4, 0xdb, 0xcc, + 0x2d, 0x4e, 0xc8, 0xc8, 0xfe, 0x5d, 0x25, 0xb6, 0xc3, 0x51, 0x85, 0xfb, 0xf8, 0x7f, 0x89, 0xad, + 0x70, 0x5b, 0x8d, 0x0d, 0x57, 0xed, 0xaf, 0x5d, 0xa0, 0x76, 0xfd, 0x02, 0xf6, 0x9f, 0x0d, 0xd8, + 0xac, 0x7a, 0x15, 0xe7, 0xec, 0x94, 0xc7, 0x21, 0x79, 0x95, 0xf7, 0x72, 0xfc, 0xe6, 0xab, 0xe3, + 0xf7, 0x14, 0x56, 0xc7, 0x82, 0x12, 0x59, 0x36, 0x7e, 0x3a, 0x53, 0x8f, 0x55, 0xd8, 0xd5, 0x5d, + 0xa9, 0xde, 0x43, 0x1c, 0x3e, 0xfd, 0xf6, 0xaa, 0x65, 0x7c, 0x77, 0xd5, 0x32, 0xfe, 0x79, 0xd5, + 0x32, 0xbe, 0x7a, 0xd9, 0x9a, 0xfb, 0xee, 0x65, 0x6b, 0xee, 0xef, 0x2f, 0x5b, 0x73, 0xbf, 0xf9, + 0xb8, 0x4f, 0xe5, 0x20, 0xee, 0x39, 0x98, 0x0d, 0x3b, 0xd9, 0xef, 0xfd, 0xd2, 0xd7, 0x07, 0xc5, + 0x9f, 0x21, 0xc9, 0xcf, 0x3a, 0x5f, 0x8e, 0xff, 0xd5, 0x22, 0x47, 0x11, 0x11, 0xbd, 0x45, 0xcd, + 0x0a, 0x1f, 0xfe, 0x27, 0x00, 0x00, 0xff, 0xff, 0xa8, 0x99, 0xdf, 0x57, 0x9b, 0x11, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -1544,6 +1550,11 @@ func (m *ConsumerAdditionProposal) MarshalToSizedBuffer(dAtA []byte) (int, error _ = i var l int _ = l + if m.Top_N != 0 { + i = encodeVarintProvider(dAtA, i, uint64(m.Top_N)) + i-- + dAtA[i] = 0x78 + } if len(m.DistributionTransmissionChannel) > 0 { i -= len(m.DistributionTransmissionChannel) copy(dAtA[i:], m.DistributionTransmissionChannel) @@ -2646,6 +2657,9 @@ func (m *ConsumerAdditionProposal) Size() (n int) { if l > 0 { n += 1 + l + sovProvider(uint64(l)) } + if m.Top_N != 0 { + n += 1 + sovProvider(uint64(m.Top_N)) + } return n } @@ -3519,6 +3533,25 @@ func (m *ConsumerAdditionProposal) Unmarshal(dAtA []byte) error { } m.DistributionTransmissionChannel = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Top_N", wireType) + } + m.Top_N = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Top_N |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipProvider(dAtA[iNdEx:]) From 02dab52785e44ca9061d6dc5bbe6a8832b7ffe98 Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 7 Feb 2024 11:35:28 +0100 Subject: [PATCH 10/18] feat!: introduce MsgOptIn and MsgOptOut (#1620) * init commit * cleaning up * changed cons to val address * Revert "changed cons to val address" This reverts commit a32e8829fee3cbbe50e363a0aa91ad62117a8a1d. * Update x/ccv/provider/keeper/keeper.go Co-authored-by: Simon Noetzlin * took into account comments * added key assignment * add contraint such that opt out only works if the chain is running --------- Co-authored-by: insumity Co-authored-by: Simon Noetzlin --- .../ccv/provider/v1/tx.proto | 28 + x/ccv/provider/client/cli/tx.go | 87 ++ x/ccv/provider/keeper/keeper.go | 141 ++- x/ccv/provider/keeper/keeper_test.go | 191 +++- x/ccv/provider/keeper/key_assignment.go | 48 + x/ccv/provider/keeper/msg_server.go | 101 +- x/ccv/provider/keeper/partial_set_security.go | 69 ++ .../keeper/partial_set_security_test.go | 111 +++ x/ccv/provider/types/codec.go | 8 + x/ccv/provider/types/keys.go | 30 + x/ccv/provider/types/keys_test.go | 3 + x/ccv/provider/types/msg.go | 113 +++ x/ccv/provider/types/provider.pb.go | 8 +- x/ccv/provider/types/tx.pb.go | 875 +++++++++++++++++- x/ccv/types/events.go | 2 + 15 files changed, 1721 insertions(+), 94 deletions(-) create mode 100644 x/ccv/provider/keeper/partial_set_security.go create mode 100644 x/ccv/provider/keeper/partial_set_security_test.go diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 3294807015..51003bdf05 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -15,6 +15,8 @@ service Msg { rpc AssignConsumerKey(MsgAssignConsumerKey) returns (MsgAssignConsumerKeyResponse); rpc SubmitConsumerMisbehaviour(MsgSubmitConsumerMisbehaviour) returns (MsgSubmitConsumerMisbehaviourResponse); rpc SubmitConsumerDoubleVoting(MsgSubmitConsumerDoubleVoting) returns (MsgSubmitConsumerDoubleVotingResponse); + rpc OptIn(MsgOptIn) returns (MsgOptInResponse); + rpc OptOut(MsgOptOut) returns (MsgOptOutResponse); } message MsgAssignConsumerKey { @@ -61,3 +63,29 @@ message MsgSubmitConsumerDoubleVoting { } message MsgSubmitConsumerDoubleVotingResponse {} + +message MsgOptIn { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + // the chain id of the consumer chain to opt in to + string chain_id = 1; + // the validator address on the provider + string provider_addr = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ]; + // (optional) the consensus public key to use on the consumer in json string format corresponding to proto-any, + // for example `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}` + // we can set `consumer_key = ""` if we do not consider the `consumer_key` + string consumer_key = 3; +} + +message MsgOptInResponse {} + +message MsgOptOut { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + // the chain id of the consumer chain to opt out from + string chain_id = 1; + // the validator address on the provider + string provider_addr = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ]; +} + +message MsgOptOutResponse {} \ No newline at end of file diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 379e55a792..2d55b602dd 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -34,6 +34,8 @@ func GetTxCmd() *cobra.Command { cmd.AddCommand(NewAssignConsumerKeyCmd()) cmd.AddCommand(NewSubmitConsumerMisbehaviourCmd()) cmd.AddCommand(NewSubmitConsumerDoubleVotingCmd()) + cmd.AddCommand(NewOptInCmd()) + cmd.AddCommand(NewOptOutCmd()) return cmd } @@ -202,3 +204,88 @@ Example: return cmd } + +func NewOptInCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "opt-in [consumer-chain-id] [consumer-pubkey]", + Short: "opts in validator to the consumer chain, and if given uses the " + + "provided consensus public key for this consumer chain", + Args: cobra.RangeArgs(1, 2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) + if err != nil { + return err + } + txf = txf.WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + providerValAddr := clientCtx.GetFromAddress() + + var consumerPubKey string + if len(args) == 2 { + // consumer public key was provided + consumerPubKey = args[1] + } else { + consumerPubKey = "" + } + msg, err := types.NewMsgOptIn(args[0], sdk.ValAddress(providerValAddr), consumerPubKey) + + 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 +} + +func NewOptOutCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "opt-out [consumer-chain-id]", + Short: "opts out validator from this consumer chain", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) + if err != nil { + return err + } + txf = txf.WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + providerValAddr := clientCtx.GetFromAddress() + + msg, err := types.NewMsgOptOut(args[0], sdk.ValAddress(providerValAddr)) + 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/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 1a7fe69ee5..ea052ec5fd 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -1160,7 +1160,7 @@ func (k Keeper) DeleteTopN( store.Delete(types.TopNKey(chainID)) } -// GetTopN returns (N, true) if chain `chainID` has a top N associated, and (0, false) otherwise. +// GetTopN returns (N, true) if chain `chainID` has a top N associated, and (0, false) otherwise. func (k Keeper) GetTopN( ctx sdk.Context, chainID string, @@ -1184,3 +1184,142 @@ func (k Keeper) IsOptIn(ctx sdk.Context, chainID string) bool { topN, found := k.GetTopN(ctx, chainID) return !found || topN == 0 } + +func (k Keeper) SetOptedIn( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, + blockHeight uint64, +) { + store := ctx.KVStore(k.storeKey) + + // validator is considered opted in + blockHeightBytes := make([]byte, 8) + binary.BigEndian.PutUint64(blockHeightBytes, blockHeight) + + store.Set(types.OptedInKey(chainID, providerAddr), blockHeightBytes) +} + +func (k Keeper) DeleteOptedIn( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.OptedInKey(chainID, providerAddr)) +} + +func (k Keeper) IsOptedIn( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) bool { + store := ctx.KVStore(k.storeKey) + return store.Get(types.OptedInKey(chainID, providerAddr)) != nil +} + +func (k Keeper) GetOptedIn( + ctx sdk.Context, + chainID string) (optedInValidators []OptedInValidator) { + store := ctx.KVStore(k.storeKey) + key := types.ChainIdWithLenKey(types.OptedInBytePrefix, chainID) + iterator := sdk.KVStorePrefixIterator(store, key) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + optedInValidators = append(optedInValidators, OptedInValidator{ + ProviderAddr: types.NewProviderConsAddress(iterator.Key()[len(key):]), + BlockHeight: binary.BigEndian.Uint64(iterator.Value()), + }) + } + + return optedInValidators +} + +func (k Keeper) SetToBeOptedIn( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Set(types.ToBeOptedInKey(chainID, providerAddr), []byte{}) +} + +func (k Keeper) DeleteToBeOptedIn( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.ToBeOptedInKey(chainID, providerAddr)) +} + +func (k Keeper) IsToBeOptedIn( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) bool { + store := ctx.KVStore(k.storeKey) + return store.Get(types.ToBeOptedInKey(chainID, providerAddr)) != nil +} + +func (k Keeper) GetToBeOptedIn( + ctx sdk.Context, + chainID string) (addresses []types.ProviderConsAddress) { + + store := ctx.KVStore(k.storeKey) + key := types.ChainIdWithLenKey(types.ToBeOptedInBytePrefix, chainID) + iterator := sdk.KVStorePrefixIterator(store, key) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + providerAddr := types.NewProviderConsAddress(iterator.Key()[len(key):]) + addresses = append(addresses, providerAddr) + } + + return addresses +} + +func (k Keeper) SetToBeOptedOut( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Set(types.ToBeOptedOutKey(chainID, providerAddr), []byte{}) +} + +func (k Keeper) DeleteToBeOptedOut( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.ToBeOptedOutKey(chainID, providerAddr)) +} + +func (k Keeper) IsToBeOptedOut( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) bool { + store := ctx.KVStore(k.storeKey) + return store.Get(types.ToBeOptedOutKey(chainID, providerAddr)) != nil +} + +func (k Keeper) GetToBeOptedOut( + ctx sdk.Context, + chainID string) (addresses []types.ProviderConsAddress) { + + store := ctx.KVStore(k.storeKey) + key := types.ChainIdWithLenKey(types.ToBeOptedOutBytePrefix, chainID) + iterator := sdk.KVStorePrefixIterator(store, key) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + providerAddr := types.NewProviderConsAddress(iterator.Key()[len(key):]) + addresses = append(addresses, providerAddr) + } + + return addresses +} diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index 4dc73b7f9e..6eb28675ba 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -1,7 +1,9 @@ package keeper_test import ( + "bytes" "fmt" + "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" "sort" "testing" "time" @@ -629,7 +631,7 @@ func TestGetAllProposedConsumerChainIDs(t *testing.T) { } } -// TestTopN tests `SetTopN`, `GetTopN`, `IsTopN`, and `IsOptIn` methods +// TestTopN tests the `SetTopN`, `GetTopN`, `IsTopN`, and `IsOptIn` methods func TestTopN(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() @@ -659,3 +661,190 @@ func TestTopN(t *testing.T) { } } } + +func TestGetOptedIn(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + expectedOptedInValidators := []keeper.OptedInValidator{ + { + ProviderAddr: types.NewProviderConsAddress([]byte("providerAddr1")), + BlockHeight: 1, + }, + { + ProviderAddr: types.NewProviderConsAddress([]byte("providerAddr2")), + BlockHeight: 2, + }, + { + ProviderAddr: types.NewProviderConsAddress([]byte("providerAddr3")), + BlockHeight: 3, + }, + } + + for _, expectedOptedInValidator := range expectedOptedInValidators { + providerKeeper.SetOptedIn(ctx, "chainID", + expectedOptedInValidator.ProviderAddr, expectedOptedInValidator.BlockHeight) + } + + actualOptedInValidators := providerKeeper.GetOptedIn(ctx, "chainID") + + // sort validators first to be able to compare + sortOptedInValidators := func(optedInValidators []keeper.OptedInValidator) { + sort.Slice(optedInValidators, func(i int, j int) bool { + a := optedInValidators[i] + b := optedInValidators[j] + return a.BlockHeight < b.BlockHeight || + (a.BlockHeight == b.BlockHeight && bytes.Compare(a.ProviderAddr.ToSdkConsAddr(), b.ProviderAddr.ToSdkConsAddr()) < 0) + }) + } + sortOptedInValidators(expectedOptedInValidators) + sortOptedInValidators(actualOptedInValidators) + require.Equal(t, expectedOptedInValidators, actualOptedInValidators) +} + +// TestOptedIn tests the `SetOptedIn`, `IsOptedIn`, and `RemoveOptedIn` methods +func TestOptedIn(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + optedInValidator := keeper.OptedInValidator{ + ProviderAddr: types.NewProviderConsAddress([]byte("providerAddr")), + BlockHeight: 1, + } + + require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", optedInValidator.ProviderAddr)) + providerKeeper.SetOptedIn(ctx, "chainID", optedInValidator.ProviderAddr, optedInValidator.BlockHeight) + require.True(t, providerKeeper.IsOptedIn(ctx, "chainID", optedInValidator.ProviderAddr)) + providerKeeper.DeleteOptedIn(ctx, "chainID", optedInValidator.ProviderAddr) + require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", optedInValidator.ProviderAddr)) +} + +func TestGetToBeOptedIn(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + expectedAddresses := []types.ProviderConsAddress{ + types.NewProviderConsAddress([]byte("providerAddr1")), + types.NewProviderConsAddress([]byte("providerAddr2")), + types.NewProviderConsAddress([]byte("providerAddr3"))} + + for _, addr := range expectedAddresses { + providerKeeper.SetToBeOptedIn(ctx, "chainID", addr) + } + + actualAddresses := providerKeeper.GetToBeOptedIn(ctx, "chainID") + + // sort addresses first to be able to compare + sortAddresses := func(addresses []types.ProviderConsAddress) { + sort.Slice(addresses, func(i int, j int) bool { + a := addresses[i] + b := addresses[j] + return bytes.Compare(a.Address.Bytes(), b.Address.Bytes()) < 0 + }) + } + sortAddresses(expectedAddresses) + sortAddresses(actualAddresses) + require.Equal(t, expectedAddresses, actualAddresses) + + for _, addr := range expectedAddresses { + require.True(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", addr)) + providerKeeper.DeleteToBeOptedIn(ctx, "chainID", addr) + require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", addr)) + } +} + +func TestBeOptedIn(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + expectedAddresses := []types.ProviderConsAddress{ + types.NewProviderConsAddress([]byte("providerAddr1")), + types.NewProviderConsAddress([]byte("providerAddr2")), + types.NewProviderConsAddress([]byte("providerAddr3"))} + + for _, addr := range expectedAddresses { + providerKeeper.SetToBeOptedIn(ctx, "chainID", addr) + } + + actualAddresses := providerKeeper.GetToBeOptedIn(ctx, "chainID") + + // sort addresses first to be able to compare + sortAddresses := func(addresses []types.ProviderConsAddress) { + sort.Slice(addresses, func(i int, j int) bool { + a := addresses[i] + b := addresses[j] + return bytes.Compare(a.Address.Bytes(), b.Address.Bytes()) < 0 + }) + } + sortAddresses(expectedAddresses) + sortAddresses(actualAddresses) + require.Equal(t, expectedAddresses, actualAddresses) + + for _, addr := range expectedAddresses { + require.True(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", addr)) + providerKeeper.DeleteToBeOptedIn(ctx, "chainID", addr) + require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", addr)) + } +} + +// TestToBeOptedIn tests the `SetToBeOptedIn`, `IsToBeOptedIn`, and `DeleteToBeOptedIn` methods +func TestToBeOptedIn(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + providerAddr := types.NewProviderConsAddress([]byte("providerAddr1")) + + require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) + providerKeeper.SetToBeOptedIn(ctx, "chainID", providerAddr) + require.True(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) + providerKeeper.DeleteToBeOptedIn(ctx, "chainID", providerAddr) + require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) +} + +func TestGetToBeOptedOut(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + expectedAddresses := []types.ProviderConsAddress{ + types.NewProviderConsAddress([]byte("providerAddr1")), + types.NewProviderConsAddress([]byte("providerAddr2")), + types.NewProviderConsAddress([]byte("providerAddr3"))} + + for _, addr := range expectedAddresses { + providerKeeper.SetToBeOptedOut(ctx, "chainID", addr) + } + + actualAddresses := providerKeeper.GetToBeOptedOut(ctx, "chainID") + + // sort addresses first to be able to compare + sortAddresses := func(addresses []types.ProviderConsAddress) { + sort.Slice(addresses, func(i int, j int) bool { + a := addresses[i] + b := addresses[j] + return bytes.Compare(a.Address.Bytes(), b.Address.Bytes()) < 0 + }) + } + sortAddresses(expectedAddresses) + sortAddresses(actualAddresses) + require.Equal(t, expectedAddresses, actualAddresses) + + for _, addr := range expectedAddresses { + require.True(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", addr)) + providerKeeper.DeleteToBeOptedOut(ctx, "chainID", addr) + require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", addr)) + } +} + +// TestToBeOptedOut tests the `SetToBeOptedOut`, `IsToBeOptedOut`, and `DeleteToBeOptedOut` methods +func TestToBeOptedOut(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + providerAddr := types.NewProviderConsAddress([]byte("providerAddr1")) + + require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) + providerKeeper.SetToBeOptedOut(ctx, "chainID", providerAddr) + require.True(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) + providerKeeper.DeleteToBeOptedOut(ctx, "chainID", providerAddr) + require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) +} diff --git a/x/ccv/provider/keeper/key_assignment.go b/x/ccv/provider/keeper/key_assignment.go index c54d922f0f..fc864c6417 100644 --- a/x/ccv/provider/keeper/key_assignment.go +++ b/x/ccv/provider/keeper/key_assignment.go @@ -1,6 +1,7 @@ package keeper import ( + "encoding/base64" "fmt" errorsmod "cosmossdk.io/errors" @@ -15,6 +16,53 @@ import ( ccvtypes "github.com/cosmos/interchain-security/v4/x/ccv/types" ) +// ParseConsumerKey parses the ED25519 PubKey`consumerKey` from a JSON string +// and constructs its corresponding `tmprotocrypto.PublicKey` +func (k Keeper) ParseConsumerKey(consumerKey string) (tmprotocrypto.PublicKey, error) { + // parse consumer key as long as it's in the right format + pkType, keyStr, err := types.ParseConsumerKeyFromJson(consumerKey) + if err != nil { + return tmprotocrypto.PublicKey{}, err + } + + // Note: the correct way to decide if a key type is supported is to check the + // consensus params. However this functionality was disabled in https://github.com/cosmos/interchain-security/pull/916 + // as a quick way to get ed25519 working, avoiding amino/proto-any marshalling issues. + + // make sure the consumer key type is supported + // cp := ctx.ConsensusParams() + // if cp != nil && cp.Validator != nil { + // if !tmstrings.StringInSlice(pkType, cp.Validator.PubKeyTypes) { + // return nil, errorsmod.Wrapf( + // stakingtypes.ErrValidatorPubKeyTypeNotSupported, + // "got: %s, expected one of: %s", pkType, cp.Validator.PubKeyTypes, + // ) + // } + // } + + // For now, only accept ed25519. + // TODO: decide what types should be supported. + if pkType != "/cosmos.crypto.ed25519.PubKey" { + return tmprotocrypto.PublicKey{}, errorsmod.Wrapf( + stakingtypes.ErrValidatorPubKeyTypeNotSupported, + "got: %s, expected: %s", pkType, "/cosmos.crypto.ed25519.PubKey", + ) + } + + pubKeyBytes, err := base64.StdEncoding.DecodeString(keyStr) + if err != nil { + return tmprotocrypto.PublicKey{}, err + } + + consumerTMPublicKey := tmprotocrypto.PublicKey{ + Sum: &tmprotocrypto.PublicKey_Ed25519{ + Ed25519: pubKeyBytes, + }, + } + + return consumerTMPublicKey, nil +} + // GetValidatorConsumerPubKey returns a validator's public key assigned for a consumer chain func (k Keeper) GetValidatorConsumerPubKey( ctx sdk.Context, diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index 9a27d1d144..4863cd0d66 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -2,15 +2,12 @@ package keeper import ( "context" - "encoding/base64" - errorsmod "cosmossdk.io/errors" 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/v4/x/ccv/provider/types" @@ -45,47 +42,11 @@ func (k msgServer) AssignConsumerKey(goCtx context.Context, msg *types.MsgAssign return nil, stakingtypes.ErrNoValidatorFound } - // parse consumer key as long as it's in the right format - pkType, keyStr, err := types.ParseConsumerKeyFromJson(msg.ConsumerKey) - if err != nil { - return nil, err - } - - // Note: the correct way to decide if a key type is supported is to check the - // consensus params. However this functionality was disabled in https://github.com/cosmos/interchain-security/pull/916 - // as a quick way to get ed25519 working, avoiding amino/proto-any marshalling issues. - - // make sure the consumer key type is supported - // cp := ctx.ConsensusParams() - // if cp != nil && cp.Validator != nil { - // if !tmstrings.StringInSlice(pkType, cp.Validator.PubKeyTypes) { - // return nil, errorsmod.Wrapf( - // stakingtypes.ErrValidatorPubKeyTypeNotSupported, - // "got: %s, expected one of: %s", pkType, cp.Validator.PubKeyTypes, - // ) - // } - // } - - // For now, only accept ed25519. - // TODO: decide what types should be supported. - if pkType != "/cosmos.crypto.ed25519.PubKey" { - return nil, errorsmod.Wrapf( - stakingtypes.ErrValidatorPubKeyTypeNotSupported, - "got: %s, expected: %s", pkType, "/cosmos.crypto.ed25519.PubKey", - ) - } - - pubKeyBytes, err := base64.StdEncoding.DecodeString(keyStr) + consumerTMPublicKey, err := k.ParseConsumerKey(msg.ConsumerKey) if err != nil { return nil, err } - consumerTMPublicKey := tmprotocrypto.PublicKey{ - Sum: &tmprotocrypto.PublicKey_Ed25519{ - Ed25519: pubKeyBytes, - }, - } - if err := k.Keeper.AssignConsumerKey(ctx, msg.ChainId, validator, consumerTMPublicKey); err != nil { return nil, err } @@ -174,3 +135,63 @@ func (k msgServer) SubmitConsumerDoubleVoting(goCtx context.Context, msg *types. return &types.MsgSubmitConsumerDoubleVotingResponse{}, nil } + +func (k msgServer) OptIn(goCtx context.Context, msg *types.MsgOptIn) (*types.MsgOptInResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + valAddress, err := sdk.ConsAddressFromBech32(msg.ProviderAddr) + if err != nil { + return nil, err + } + providerAddr := types.NewProviderConsAddress(valAddress) + if err != nil { + return nil, err + } + + if msg.ConsumerKey != "" { + err = k.Keeper.HandleOptIn(ctx, msg.ChainId, providerAddr, &msg.ConsumerKey) + } else { + err = k.Keeper.HandleOptIn(ctx, msg.ChainId, providerAddr, nil) + } + + if err != nil { + return nil, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + ccvtypes.EventTypeOptIn, + sdk.NewAttribute(types.AttributeProviderValidatorAddress, msg.ProviderAddr), + sdk.NewAttribute(types.AttributeConsumerConsensusPubKey, msg.ConsumerKey), + ), + }) + + return &types.MsgOptInResponse{}, nil +} + +func (k msgServer) OptOut(goCtx context.Context, msg *types.MsgOptOut) (*types.MsgOptOutResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + valAddress, err := sdk.ConsAddressFromBech32(msg.ProviderAddr) + if err != nil { + return nil, err + } + providerAddr := types.NewProviderConsAddress(valAddress) + if err != nil { + return nil, err + } + + err = k.Keeper.HandleOptOut(ctx, msg.ChainId, providerAddr) + if err != nil { + return nil, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + ccvtypes.EventTypeOptOut, + sdk.NewAttribute(types.AttributeProviderValidatorAddress, msg.ProviderAddr), + ), + }) + + return &types.MsgOptOutResponse{}, nil +} diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go new file mode 100644 index 0000000000..e1bf9cc14f --- /dev/null +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -0,0 +1,69 @@ +package keeper + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" +) + +type OptedInValidator struct { + ProviderAddr types.ProviderConsAddress + // block height the validator opted in at + BlockHeight uint64 +} + +func (k Keeper) HandleOptIn(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress, consumerKey *string) error { + if !k.IsConsumerProposedOrRegistered(ctx, chainID) { + return errorsmod.Wrapf( + types.ErrUnknownConsumerChainId, + "opting in to an unknown consumer chain, with id: %s", chainID) + } + + if k.IsToBeOptedOut(ctx, chainID, providerAddr) { + // a validator to be opted in cancels out with a validator to be opted out + k.DeleteToBeOptedOut(ctx, chainID, providerAddr) + } else if !k.IsToBeOptedIn(ctx, chainID, providerAddr) && !k.IsOptedIn(ctx, chainID, providerAddr) { + // a validator can only be set for opt in if it is not opted in and not already set for opt in + k.SetToBeOptedIn(ctx, chainID, providerAddr) + } + + if consumerKey != nil { + consumerTMPublicKey, err := k.ParseConsumerKey(*consumerKey) + if err != nil { + return err + } + + validator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.Address) + if !found { + return stakingtypes.ErrNoValidatorFound + } + + err = k.AssignConsumerKey(ctx, chainID, validator, consumerTMPublicKey) + if err != nil { + return err + } + } + + return nil +} + +func (k Keeper) HandleOptOut(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress) error { + if _, found := k.GetConsumerClientId(ctx, chainID); !found { + // A validator can only opt out from a running chain. We check this by checking the consumer client id, because + // `SetConsumerClientId` is set when the chain starts in `CreateConsumerClientInCachedCtx` of `BeginBlockInit`. + return errorsmod.Wrapf( + types.ErrUnknownConsumerChainId, + "opting out of an unknown or not running consumer chain, with id: %s", chainID) + } + + if k.IsToBeOptedIn(ctx, chainID, providerAddr) { + // a validator to be opted out cancels out a validator to be opted in + k.DeleteToBeOptedIn(ctx, chainID, providerAddr) + } else if !k.IsToBeOptedOut(ctx, chainID, providerAddr) && k.IsOptedIn(ctx, chainID, providerAddr) { + // a validator can only be set for opt out if it is opted in and not already set for opt out + k.SetToBeOptedOut(ctx, chainID, providerAddr) + } + + return nil +} diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go new file mode 100644 index 0000000000..4831723bec --- /dev/null +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -0,0 +1,111 @@ +package keeper_test + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + testkeeper "github.com/cosmos/interchain-security/v4/testutil/keeper" + "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v4/x/ccv/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "testing" +) + +func TestHandleOptIn(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + providerAddr := types.NewProviderConsAddress([]byte("providerAddr")) + + // trying to opt in to a non-proposed and non-registered chain returns an error + require.Error(t, providerKeeper.HandleOptIn(ctx, "unknownChainID", providerAddr, nil)) + + // if validator (`providerAddr`) is to be opted out, then we cancel that the validator is about + // to be opted out and do not consider the validator to opt in + providerKeeper.SetToBeOptedOut(ctx, "chainID", providerAddr) + providerKeeper.SetProposedConsumerChain(ctx, "chainID", 1) + require.True(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) + providerKeeper.HandleOptIn(ctx, "chainID", providerAddr, nil) + require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) + require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) + + // if validator (`providerAddr`) is already opted in, then the validator cannot be opted in + providerKeeper.SetOptedIn(ctx, "chainID", providerAddr, 1) + providerKeeper.HandleOptIn(ctx, "chainID", providerAddr, nil) + require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) +} + +func TestHandleOptInWithConsumerKey(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // generate a consensus public key for the provider + providerConsPubKey := ed25519.GenPrivKeyFromSecret([]byte{1}).PubKey() + consAddr := sdk.ConsAddress(providerConsPubKey.Address()) + providerAddr := types.NewProviderConsAddress(consAddr) + + calls := []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT(). + GetValidatorByConsAddr(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx sdk.Context, addr sdk.ConsAddress) (stakingtypes.Validator, bool) { + if addr.Equals(providerAddr.Address) { + // Given `providerAddr`, `GetValidatorByConsAddr` returns a validator with the + // exact same `ConsensusPubkey` + pkAny, _ := codectypes.NewAnyWithValue(providerConsPubKey) + return stakingtypes.Validator{ConsensusPubkey: pkAny}, true + } else { + // for any other consensus address, we cannot find a validator + return stakingtypes.Validator{}, false + } + }).Times(2), + } + + gomock.InOrder(calls...) + providerKeeper.SetProposedConsumerChain(ctx, "chainID", 1) + + // create a sample consumer key to assign to the `providerAddr` validator + // on the consumer chain with id `chainID` + consumerKey := "{\"@type\":\"/cosmos.crypto.ed25519.PubKey\",\"key\":\"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is=\"}" + expectedConsumerPubKey, _ := providerKeeper.ParseConsumerKey(consumerKey) + + err := providerKeeper.HandleOptIn(ctx, "chainID", providerAddr, &consumerKey) + require.NoError(t, err) + + // assert that the consumeKey was assigned to `providerAddr` validator on chain with id `chainID` + actualConsumerPubKey, found := providerKeeper.GetValidatorConsumerPubKey(ctx, "chainID", providerAddr) + require.True(t, found) + require.Equal(t, expectedConsumerPubKey, actualConsumerPubKey) + + // assert that the `consumerAddr` to `providerAddr` association was set as well + consumerAddr, _ := ccvtypes.TMCryptoPublicKeyToConsAddr(actualConsumerPubKey) + actualProviderConsAddr, found := providerKeeper.GetValidatorByConsumerAddr(ctx, "chainID", types.NewConsumerConsAddress(consumerAddr)) + require.Equal(t, providerAddr, actualProviderConsAddr) +} + +func TestHandleOptOut(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + providerAddr := types.NewProviderConsAddress([]byte("providerAddr")) + + // trying to opt out from a not running chain returns an error + require.Error(t, providerKeeper.HandleOptOut(ctx, "unknownChainID", providerAddr)) + + // if validator (`providerAddr`) is to be opted in, then we cancel that the validator is about + // to be opted out and do not consider the validator to opt out + providerKeeper.SetToBeOptedIn(ctx, "chainID", providerAddr) + providerKeeper.SetConsumerClientId(ctx, "chainID", "clientID") + require.True(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) + err := providerKeeper.HandleOptOut(ctx, "chainID", providerAddr) + require.NoError(t, err) + require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) + require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) + + // if validator (`providerAddr`) is not opted in, then the validator cannot be opted out + providerKeeper.DeleteOptedIn(ctx, "chainID", providerAddr) + err = providerKeeper.HandleOptOut(ctx, "chainID", providerAddr) + require.NoError(t, err) + require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) +} diff --git a/x/ccv/provider/types/codec.go b/x/ccv/provider/types/codec.go index 6ab621f0e1..c0ab4f5aea 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -45,6 +45,14 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { (*sdk.Msg)(nil), &MsgSubmitConsumerDoubleVoting{}, ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgOptIn{}, + ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgOptOut{}, + ) registry.RegisterImplementations( (*exported.ClientMessage)(nil), &tendermint.Misbehaviour{}, diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 47ad0417b2..20824454cb 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -148,6 +148,18 @@ const ( // TopNBytePrefix is the byte prefix storing the mapping from a consumer chain to the N value of this chain, // that corresponds to the N% of the top validators that have to validate this consumer chain TopNBytePrefix + + // OptedInBytePrefix is the byte prefix used when storing for each consumer chain all the opted in validators + OptedInBytePrefix + + // ToBeOptedInBytePrefix is the byte prefix used when storing for each consumer chain the validators that + // are about to be opted in + ToBeOptedInBytePrefix + + // ToBeOptedOutBytePrefix is the byte prefix used when storing for each consumer chain the validators that + // are about to be opted out + ToBeOptedOutBytePrefix + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -525,6 +537,24 @@ func TopNKey(chainID string) []byte { return ChainIdWithLenKey(TopNBytePrefix, chainID) } +// OptedInKey returns the key of consumer chain `chainID` and validator with `providerAddr` +func OptedInKey(chainID string, providerAddr ProviderConsAddress) []byte { + prefix := ChainIdWithLenKey(OptedInBytePrefix, chainID) + return append(prefix, providerAddr.ToSdkConsAddr().Bytes()...) +} + +// ToBeOptedInKey returns the key of consumer chain `chainID` and validator with `providerAddr` +func ToBeOptedInKey(chainID string, providerAddr ProviderConsAddress) []byte { + prefix := ChainIdWithLenKey(ToBeOptedInBytePrefix, chainID) + return append(prefix, providerAddr.ToSdkConsAddr().Bytes()...) +} + +// ToBeOptedOutKey returns the key of consumer chain `chainID` and validator with `providerAddr` +func ToBeOptedOutKey(chainID string, providerAddr ProviderConsAddress) []byte { + prefix := ChainIdWithLenKey(ToBeOptedOutBytePrefix, chainID) + return append(prefix, providerAddr.ToSdkConsAddr().Bytes()...) +} + // // End of generic helpers section // diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index f0f316bf75..62ca2b9fef 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -57,6 +57,9 @@ func getAllKeyPrefixes() []byte { providertypes.EquivocationEvidenceMinHeightBytePrefix, providertypes.ProposedConsumerChainByteKey, providertypes.TopNBytePrefix, + providertypes.OptedInBytePrefix, + providertypes.ToBeOptedInBytePrefix, + providertypes.ToBeOptedOutBytePrefix, } } diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 55e6b2fae4..20aae43f6c 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -22,12 +22,16 @@ const ( TypeMsgAssignConsumerKey = "assign_consumer_key" TypeMsgSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" TypeMsgSubmitConsumerDoubleVoting = "submit_consumer_double_vote" + TypeMsgOptIn = "opt_in" + TypeMsgOptOut = "opt_out" ) var ( _ sdk.Msg = &MsgAssignConsumerKey{} _ sdk.Msg = &MsgSubmitConsumerMisbehaviour{} _ sdk.Msg = &MsgSubmitConsumerDoubleVoting{} + _ sdk.Msg = &MsgOptIn{} + _ sdk.Msg = &MsgOptOut{} ) // NewMsgAssignConsumerKey creates a new MsgAssignConsumerKey instance. @@ -203,3 +207,112 @@ func (msg MsgSubmitConsumerDoubleVoting) GetSigners() []sdk.AccAddress { } return []sdk.AccAddress{addr} } + +// NewMsgOptIn creates a new NewMsgOptIn instance. +func NewMsgOptIn(chainID string, providerValidatorAddress sdk.ValAddress, consumerConsensusPubKey string) (*MsgOptIn, error) { + return &MsgOptIn{ + ChainId: chainID, + ProviderAddr: providerValidatorAddress.String(), + ConsumerKey: consumerConsensusPubKey, + }, nil +} + +// Route implements the sdk.Msg interface. +func (msg MsgOptIn) Route() string { return RouterKey } + +// GetSigners implements the sdk.Msg interface. It returns the address(es) that +// must sign over msg.GetSignBytes(). +func (msg MsgOptIn) GetSigners() []sdk.AccAddress { + valAddr, err := sdk.ValAddressFromBech32(msg.ProviderAddr) + if err != nil { + // same behavior as in cosmos-sdk + panic(err) + } + return []sdk.AccAddress{valAddr.Bytes()} +} + +// GetSignBytes returns the message bytes to sign over. +func (msg MsgOptIn) GetSignBytes() []byte { + bz := ccvtypes.ModuleCdc.MustMarshalJSON(&msg) + return sdk.MustSortJSON(bz) +} + +// ValidateBasic implements the sdk.Msg interface. +func (msg MsgOptIn) ValidateBasic() error { + if strings.TrimSpace(msg.ChainId) == "" { + return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot be blank") + } + // It is possible to assign keys for consumer chains that are not yet approved. + // This can only be done by a signing validator, but it is still sensible + // to limit the chainID size to prevent abuse. + if 128 < len(msg.ChainId) { + return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot exceed 128 length") + } + _, err := sdk.ValAddressFromBech32(msg.ProviderAddr) + if err != nil { + return ErrInvalidProviderAddress + } + + if msg.ConsumerKey != "" { + if _, _, err := ParseConsumerKeyFromJson(msg.ConsumerKey); err != nil { + return ErrInvalidConsumerConsensusPubKey + } + } + return nil +} + +// NewMsgOptOut creates a new NewMsgOptIn instance. +func NewMsgOptOut(chainID string, providerValidatorAddress sdk.ValAddress) (*MsgOptOut, error) { + return &MsgOptOut{ + ChainId: chainID, + ProviderAddr: providerValidatorAddress.String(), + }, nil +} + +// Route implements the sdk.Msg interface. +func (msg MsgOptOut) Route() string { return RouterKey } + +// Type implements the sdk.Msg interface. +func (msg MsgOptIn) Type() string { + return TypeMsgOptIn +} + +// GetSigners implements the sdk.Msg interface. It returns the address(es) that +// must sign over msg.GetSignBytes(). +func (msg MsgOptOut) GetSigners() []sdk.AccAddress { + valAddr, err := sdk.ValAddressFromBech32(msg.ProviderAddr) + if err != nil { + // same behavior as in cosmos-sdk + panic(err) + } + return []sdk.AccAddress{valAddr.Bytes()} +} + +// GetSignBytes returns the message bytes to sign over. +func (msg MsgOptOut) GetSignBytes() []byte { + bz := ccvtypes.ModuleCdc.MustMarshalJSON(&msg) + return sdk.MustSortJSON(bz) +} + +// ValidateBasic implements the sdk.Msg interface. +func (msg MsgOptOut) ValidateBasic() error { + if strings.TrimSpace(msg.ChainId) == "" { + return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot be blank") + } + // It is possible to assign keys for consumer chains that are not yet approved. + // This can only be done by a signing validator, but it is still sensible + // to limit the chainID size to prevent abuse. + if 128 < len(msg.ChainId) { + return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot exceed 128 length") + } + _, err := sdk.ValAddressFromBech32(msg.ProviderAddr) + if err != nil { + return ErrInvalidProviderAddress + } + return nil +} + +// Type implements the sdk.Msg interface. +func (msg MsgOptOut) Type() string { + return TypeMsgOptOut +} diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index edea9f4e02..fd9d63bc7e 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -90,10 +90,10 @@ type ConsumerAdditionProposal struct { // chain. it is most relevant for chains performing a sovereign to consumer // changeover in order to maintain the existing ibc transfer channel DistributionTransmissionChannel string `protobuf:"bytes,14,opt,name=distribution_transmission_channel,json=distributionTransmissionChannel,proto3" json:"distribution_transmission_channel,omitempty"` - // Corresponds to the percentage of validators that join under the Top N case. - // For example, 53 corresponds to a Top 53% chain, meaning that the top 53% provider validators - // have to validate the proposed consumer chain. top_N can be 0 or include any value in [50, 100]. - // A chain can join with top_N == 0 as an Opt In, or with top_N ∈ [50, 100] as a Top N chain. + // Corresponds to the percentage of validators that have to validate the chain under the Top N case. + // For example, 53 corresponds to a Top 53% chain, meaning that the top 53% provider validators by voting power + // have to validate the proposed consumer chain. top_N can either be 0 or any value in [50, 100]. + // A chain can join with top_N == 0 as an Opt In chain, or with top_N ∈ [50, 100] as a Top N chain. Top_N uint32 `protobuf:"varint,15,opt,name=top_N,json=topN,proto3" json:"top_N,omitempty"` } diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index f72e95acb2..647f5f49dc 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -270,6 +270,162 @@ func (m *MsgSubmitConsumerDoubleVotingResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSubmitConsumerDoubleVotingResponse proto.InternalMessageInfo +type MsgOptIn struct { + // the chain id of the consumer chain to opt in to + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + // the validator address on the provider + ProviderAddr string `protobuf:"bytes,2,opt,name=provider_addr,json=providerAddr,proto3" json:"provider_addr,omitempty" yaml:"address"` + // (optional) the consensus public key to use on the consumer in json string format corresponding to proto-any, + // for example `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}` + // we can set `consumer_key = ""` if we do not consider the `consumer_key` + ConsumerKey string `protobuf:"bytes,3,opt,name=consumer_key,json=consumerKey,proto3" json:"consumer_key,omitempty"` +} + +func (m *MsgOptIn) Reset() { *m = MsgOptIn{} } +func (m *MsgOptIn) String() string { return proto.CompactTextString(m) } +func (*MsgOptIn) ProtoMessage() {} +func (*MsgOptIn) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{6} +} +func (m *MsgOptIn) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgOptIn) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgOptIn.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 *MsgOptIn) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgOptIn.Merge(m, src) +} +func (m *MsgOptIn) XXX_Size() int { + return m.Size() +} +func (m *MsgOptIn) XXX_DiscardUnknown() { + xxx_messageInfo_MsgOptIn.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgOptIn proto.InternalMessageInfo + +type MsgOptInResponse struct { +} + +func (m *MsgOptInResponse) Reset() { *m = MsgOptInResponse{} } +func (m *MsgOptInResponse) String() string { return proto.CompactTextString(m) } +func (*MsgOptInResponse) ProtoMessage() {} +func (*MsgOptInResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{7} +} +func (m *MsgOptInResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgOptInResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgOptInResponse.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 *MsgOptInResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgOptInResponse.Merge(m, src) +} +func (m *MsgOptInResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgOptInResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgOptInResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgOptInResponse proto.InternalMessageInfo + +type MsgOptOut struct { + // the chain id of the consumer chain to opt out from + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + // the validator address on the provider + ProviderAddr string `protobuf:"bytes,2,opt,name=provider_addr,json=providerAddr,proto3" json:"provider_addr,omitempty" yaml:"address"` +} + +func (m *MsgOptOut) Reset() { *m = MsgOptOut{} } +func (m *MsgOptOut) String() string { return proto.CompactTextString(m) } +func (*MsgOptOut) ProtoMessage() {} +func (*MsgOptOut) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{8} +} +func (m *MsgOptOut) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgOptOut) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgOptOut.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 *MsgOptOut) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgOptOut.Merge(m, src) +} +func (m *MsgOptOut) XXX_Size() int { + return m.Size() +} +func (m *MsgOptOut) XXX_DiscardUnknown() { + xxx_messageInfo_MsgOptOut.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgOptOut proto.InternalMessageInfo + +type MsgOptOutResponse struct { +} + +func (m *MsgOptOutResponse) Reset() { *m = MsgOptOutResponse{} } +func (m *MsgOptOutResponse) String() string { return proto.CompactTextString(m) } +func (*MsgOptOutResponse) ProtoMessage() {} +func (*MsgOptOutResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{9} +} +func (m *MsgOptOutResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgOptOutResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgOptOutResponse.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 *MsgOptOutResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgOptOutResponse.Merge(m, src) +} +func (m *MsgOptOutResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgOptOutResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgOptOutResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgOptOutResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgAssignConsumerKey)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKey") proto.RegisterType((*MsgAssignConsumerKeyResponse)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKeyResponse") @@ -277,6 +433,10 @@ func init() { 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") + proto.RegisterType((*MsgOptIn)(nil), "interchain_security.ccv.provider.v1.MsgOptIn") + proto.RegisterType((*MsgOptInResponse)(nil), "interchain_security.ccv.provider.v1.MsgOptInResponse") + proto.RegisterType((*MsgOptOut)(nil), "interchain_security.ccv.provider.v1.MsgOptOut") + proto.RegisterType((*MsgOptOutResponse)(nil), "interchain_security.ccv.provider.v1.MsgOptOutResponse") } func init() { @@ -284,46 +444,50 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 610 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xcf, 0x4f, 0xd4, 0x4e, - 0x14, 0xdf, 0x42, 0xf2, 0xfd, 0xc2, 0x80, 0x26, 0x36, 0x10, 0x60, 0x83, 0x5d, 0x5d, 0xa3, 0x78, - 0xc0, 0x99, 0x80, 0x26, 0x46, 0x12, 0x0f, 0xac, 0x98, 0xf8, 0x23, 0x9b, 0x98, 0x9a, 0x60, 0xe2, - 0xc1, 0xa6, 0x9d, 0x3e, 0xba, 0x13, 0xda, 0x99, 0xcd, 0xcc, 0xb4, 0xa1, 0xff, 0x01, 0x47, 0x3d, - 0x19, 0x6f, 0xfc, 0x01, 0xfe, 0x21, 0x1e, 0x39, 0x7a, 0x32, 0x06, 0x2e, 0x9e, 0xbd, 0x78, 0x35, - 0x3b, 0x6d, 0xd9, 0x12, 0x2b, 0x10, 0xbc, 0xf5, 0xbd, 0xf7, 0x79, 0xef, 0x7d, 0x3e, 0x6f, 0x5e, - 0x1f, 0x5a, 0x65, 0x5c, 0x83, 0xa4, 0x03, 0x9f, 0x71, 0x4f, 0x01, 0x4d, 0x25, 0xd3, 0x39, 0xa1, - 0x34, 0x23, 0x43, 0x29, 0x32, 0x16, 0x82, 0x24, 0xd9, 0x1a, 0xd1, 0x7b, 0x78, 0x28, 0x85, 0x16, - 0xf6, 0xad, 0x06, 0x34, 0xa6, 0x34, 0xc3, 0x15, 0x1a, 0x67, 0x6b, 0xed, 0xb9, 0x48, 0x44, 0xc2, - 0xe0, 0xc9, 0xe8, 0xab, 0x48, 0x6d, 0x2f, 0x51, 0xa1, 0x12, 0xa1, 0xbc, 0x22, 0x50, 0x18, 0x55, - 0x28, 0x12, 0x22, 0x8a, 0x81, 0x18, 0x2b, 0x48, 0x77, 0x88, 0xcf, 0xf3, 0x32, 0x44, 0x58, 0x40, - 0x49, 0xcc, 0xa2, 0x81, 0xa6, 0x31, 0x03, 0xae, 0x15, 0xd1, 0xc0, 0x43, 0x90, 0x09, 0xe3, 0xda, - 0x30, 0x3b, 0xb1, 0xca, 0x84, 0x4e, 0x2d, 0xae, 0xf3, 0x21, 0x28, 0x02, 0x23, 0x62, 0x9c, 0x42, - 0x01, 0xe8, 0x7e, 0xb4, 0xd0, 0x5c, 0x5f, 0x45, 0x9b, 0x4a, 0xb1, 0x88, 0x3f, 0x11, 0x5c, 0xa5, - 0x09, 0xc8, 0x97, 0x90, 0xdb, 0x4b, 0x68, 0xaa, 0x10, 0xc6, 0xc2, 0x45, 0xeb, 0x86, 0x75, 0x77, - 0xda, 0xfd, 0xdf, 0xd8, 0xcf, 0x43, 0xfb, 0x21, 0xba, 0x52, 0x09, 0xf4, 0xfc, 0x30, 0x94, 0x8b, - 0x13, 0xa3, 0x78, 0xcf, 0xfe, 0xf9, 0xad, 0x73, 0x35, 0xf7, 0x93, 0x78, 0xa3, 0x3b, 0xf2, 0x82, - 0x52, 0x5d, 0x77, 0xb6, 0x02, 0x6e, 0x86, 0xa1, 0xb4, 0x6f, 0xa2, 0x59, 0x5a, 0xb6, 0xf0, 0x76, - 0x21, 0x5f, 0x9c, 0x34, 0x75, 0x67, 0xe8, 0xb8, 0xed, 0xc6, 0xd4, 0xfe, 0x41, 0xa7, 0xf5, 0xe3, - 0xa0, 0xd3, 0xea, 0x3a, 0x68, 0xb9, 0x89, 0x98, 0x0b, 0x6a, 0x28, 0xb8, 0x82, 0xee, 0x27, 0x0b, - 0x5d, 0xef, 0xab, 0xe8, 0x75, 0x1a, 0x24, 0x4c, 0x57, 0x80, 0x3e, 0x53, 0x01, 0x0c, 0xfc, 0x8c, - 0x89, 0x54, 0xda, 0xcb, 0x68, 0x5a, 0x99, 0xa8, 0x06, 0x59, 0x6a, 0x18, 0x3b, 0xec, 0x57, 0x68, - 0x36, 0xa9, 0xa1, 0x8d, 0x88, 0x99, 0xf5, 0x55, 0xcc, 0x02, 0x8a, 0xeb, 0x23, 0xc6, 0xb5, 0xa1, - 0x66, 0x6b, 0xb8, 0xde, 0xc1, 0x3d, 0x55, 0xa1, 0xc6, 0x7d, 0x05, 0xdd, 0x3e, 0x93, 0xda, 0x89, - 0x88, 0xfd, 0x89, 0x06, 0x11, 0x5b, 0x22, 0x0d, 0x62, 0xd8, 0x16, 0x9a, 0xf1, 0xe8, 0x1c, 0x11, - 0x1e, 0x5a, 0x08, 0xd3, 0x61, 0xcc, 0xa8, 0xaf, 0xc1, 0xcb, 0x84, 0x06, 0xaf, 0x7a, 0xdf, 0x52, - 0xcf, 0x4a, 0x9d, 0xbe, 0xd9, 0x00, 0xbc, 0x55, 0x25, 0x6c, 0x0b, 0x0d, 0x4f, 0x4b, 0xb8, 0x3b, - 0x1f, 0x36, 0xb9, 0xed, 0x77, 0x68, 0x81, 0xf1, 0x1d, 0xe9, 0x53, 0xcd, 0x04, 0xf7, 0x82, 0x58, - 0xd0, 0x5d, 0x6f, 0x00, 0x7e, 0x08, 0xd2, 0xbc, 0xde, 0xcc, 0xfa, 0x9d, 0xf3, 0x06, 0xf6, 0xcc, - 0xa0, 0xdd, 0xf9, 0x71, 0x99, 0xde, 0xa8, 0x4a, 0xe1, 0x3e, 0x67, 0x66, 0xf5, 0x49, 0x54, 0x33, - 0x5b, 0xff, 0x35, 0x89, 0x26, 0xfb, 0x2a, 0xb2, 0x3f, 0x58, 0xe8, 0xda, 0x9f, 0x7b, 0xfb, 0x08, - 0x5f, 0xe0, 0xa7, 0xc4, 0x4d, 0x9b, 0xd5, 0xde, 0xbc, 0x74, 0x6a, 0xc5, 0xcd, 0xfe, 0x6c, 0xa1, - 0xf6, 0x19, 0x1b, 0xd9, 0xbb, 0x68, 0x87, 0xbf, 0xd7, 0x68, 0xbf, 0xf8, 0xf7, 0x1a, 0x67, 0xd0, - 0x3d, 0xb5, 0x7b, 0x97, 0xa4, 0x5b, 0xaf, 0x71, 0x59, 0xba, 0x4d, 0x2f, 0xdf, 0x7b, 0xf3, 0xe5, - 0xc8, 0xb1, 0x0e, 0x8f, 0x1c, 0xeb, 0xfb, 0x91, 0x63, 0xbd, 0x3f, 0x76, 0x5a, 0x87, 0xc7, 0x4e, - 0xeb, 0xeb, 0xb1, 0xd3, 0x7a, 0xfb, 0x38, 0x62, 0x7a, 0x90, 0x06, 0x98, 0x8a, 0xa4, 0x3c, 0xa6, - 0x64, 0xdc, 0xf6, 0xde, 0xc9, 0x25, 0xcf, 0x1e, 0x90, 0xbd, 0xd3, 0xe7, 0xdc, 0xfc, 0x12, 0xc1, - 0x7f, 0xe6, 0x18, 0xde, 0xff, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x74, 0xf3, 0x13, 0x1f, 0xff, 0x05, - 0x00, 0x00, + // 683 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x55, 0x4d, 0x4f, 0xd4, 0x40, + 0x18, 0xde, 0x42, 0x40, 0x18, 0xd0, 0x48, 0x85, 0x00, 0x1b, 0xec, 0xea, 0x1a, 0xc5, 0x03, 0xcc, + 0x04, 0xfc, 0x8a, 0x24, 0x1e, 0x58, 0x31, 0x11, 0xcd, 0x06, 0xb3, 0x26, 0x98, 0x78, 0xb0, 0x69, + 0xa7, 0x43, 0x77, 0x42, 0x3b, 0xd3, 0xcc, 0x4c, 0x1b, 0xf6, 0x1f, 0x90, 0x78, 0xd1, 0x93, 0xf1, + 0xc6, 0x0f, 0xf0, 0x87, 0x78, 0xe4, 0xc8, 0xc9, 0x18, 0xb8, 0x78, 0xf6, 0x17, 0x98, 0x9d, 0x7e, + 0x6c, 0x89, 0x2b, 0x5f, 0xc6, 0x78, 0xeb, 0xbc, 0xef, 0x33, 0xcf, 0xf3, 0xbc, 0x6f, 0xe7, 0x9d, + 0x01, 0x0b, 0x94, 0x29, 0x22, 0x70, 0xdb, 0xa1, 0xcc, 0x96, 0x04, 0xc7, 0x82, 0xaa, 0x0e, 0xc2, + 0x38, 0x41, 0x91, 0xe0, 0x09, 0xf5, 0x88, 0x40, 0xc9, 0x12, 0x52, 0x3b, 0x30, 0x12, 0x5c, 0x71, + 0xf3, 0x56, 0x1f, 0x34, 0xc4, 0x38, 0x81, 0x39, 0x1a, 0x26, 0x4b, 0xd5, 0x49, 0x9f, 0xfb, 0x5c, + 0xe3, 0x51, 0xf7, 0x2b, 0xdd, 0x5a, 0x9d, 0xc5, 0x5c, 0x86, 0x5c, 0xda, 0x69, 0x22, 0x5d, 0xe4, + 0x29, 0x9f, 0x73, 0x3f, 0x20, 0x48, 0xaf, 0xdc, 0x78, 0x0b, 0x39, 0xac, 0x93, 0xa5, 0x10, 0x75, + 0x31, 0x0a, 0xa8, 0xdf, 0x56, 0x38, 0xa0, 0x84, 0x29, 0x89, 0x14, 0x61, 0x1e, 0x11, 0x21, 0x65, + 0x4a, 0x3b, 0x2b, 0x56, 0xd9, 0x86, 0x5a, 0x29, 0xaf, 0x3a, 0x11, 0x91, 0x88, 0x74, 0x8d, 0x31, + 0x4c, 0x52, 0x40, 0xfd, 0x93, 0x01, 0x26, 0x9b, 0xd2, 0x5f, 0x95, 0x92, 0xfa, 0xec, 0x29, 0x67, + 0x32, 0x0e, 0x89, 0x78, 0x49, 0x3a, 0xe6, 0x2c, 0x18, 0x49, 0x0b, 0xa3, 0xde, 0x8c, 0x71, 0xc3, + 0xb8, 0x3b, 0xda, 0xba, 0xa4, 0xd7, 0xeb, 0x9e, 0xf9, 0x08, 0x5c, 0xce, 0x0b, 0xb4, 0x1d, 0xcf, + 0x13, 0x33, 0x03, 0xdd, 0x7c, 0xc3, 0xfc, 0xf9, 0xad, 0x76, 0xa5, 0xe3, 0x84, 0xc1, 0x4a, 0xbd, + 0x1b, 0x25, 0x52, 0xd6, 0x5b, 0xe3, 0x39, 0x70, 0xd5, 0xf3, 0x84, 0x79, 0x13, 0x8c, 0xe3, 0x4c, + 0xc2, 0xde, 0x26, 0x9d, 0x99, 0x41, 0xcd, 0x3b, 0x86, 0x7b, 0xb2, 0x2b, 0x23, 0xbb, 0x7b, 0xb5, + 0xca, 0x8f, 0xbd, 0x5a, 0xa5, 0x6e, 0x81, 0xb9, 0x7e, 0xc6, 0x5a, 0x44, 0x46, 0x9c, 0x49, 0x52, + 0xff, 0x6c, 0x80, 0xeb, 0x4d, 0xe9, 0xbf, 0x8e, 0xdd, 0x90, 0xaa, 0x1c, 0xd0, 0xa4, 0xd2, 0x25, + 0x6d, 0x27, 0xa1, 0x3c, 0x16, 0xe6, 0x1c, 0x18, 0x95, 0x3a, 0xab, 0x88, 0xc8, 0x6a, 0xe8, 0x05, + 0xcc, 0x57, 0x60, 0x3c, 0x2c, 0xa1, 0x75, 0x11, 0x63, 0xcb, 0x0b, 0x90, 0xba, 0x18, 0x96, 0x5b, + 0x0c, 0x4b, 0x4d, 0x4d, 0x96, 0x60, 0x59, 0xa1, 0x75, 0x8c, 0xa1, 0xe4, 0x7d, 0x1e, 0xdc, 0x3e, + 0xd1, 0x5a, 0x51, 0xc4, 0xee, 0x40, 0x9f, 0x22, 0xd6, 0x78, 0xec, 0x06, 0x64, 0x93, 0x2b, 0xca, + 0xfc, 0x53, 0x8a, 0xb0, 0xc1, 0xb4, 0x17, 0x47, 0x01, 0xc5, 0x8e, 0x22, 0x76, 0xc2, 0x15, 0xb1, + 0xf3, 0xff, 0x9b, 0xd5, 0x33, 0x5f, 0xb6, 0xaf, 0x4f, 0x00, 0x5c, 0xcb, 0x37, 0x6c, 0x72, 0x45, + 0x9e, 0x65, 0xf0, 0xd6, 0x94, 0xd7, 0x2f, 0x6c, 0xbe, 0x03, 0xd3, 0x94, 0x6d, 0x09, 0x07, 0x2b, + 0xca, 0x99, 0xed, 0x06, 0x1c, 0x6f, 0xdb, 0x6d, 0xe2, 0x78, 0x44, 0xe8, 0xbf, 0x37, 0xb6, 0x7c, + 0xe7, 0xb4, 0x86, 0x3d, 0xd7, 0xe8, 0xd6, 0x54, 0x8f, 0xa6, 0xd1, 0x65, 0x49, 0xc3, 0xa7, 0xf4, + 0xac, 0xdc, 0x89, 0xa2, 0x67, 0xef, 0x0d, 0x30, 0xd2, 0x94, 0xfe, 0x46, 0xa4, 0xd6, 0xd9, 0xff, + 0x3f, 0xa6, 0x26, 0xb8, 0x9a, 0x9b, 0x29, 0x1c, 0x52, 0x30, 0x9a, 0xc6, 0x36, 0x62, 0xf5, 0x2f, + 0x1c, 0x96, 0xe4, 0xaf, 0x81, 0x89, 0x42, 0x2a, 0xd7, 0x5f, 0x3e, 0x18, 0x02, 0x83, 0x4d, 0xe9, + 0x9b, 0x1f, 0x0d, 0x30, 0xf1, 0xfb, 0x64, 0x3f, 0x86, 0x67, 0xb8, 0xb6, 0x60, 0xbf, 0xd9, 0xab, + 0xae, 0x5e, 0x78, 0x6b, 0xee, 0xcd, 0xfc, 0x62, 0x80, 0xea, 0x09, 0x33, 0xdb, 0x38, 0xab, 0xc2, + 0x9f, 0x39, 0xaa, 0x2f, 0xfe, 0x9e, 0xe3, 0x04, 0xbb, 0xc7, 0xa6, 0xf3, 0x82, 0x76, 0xcb, 0x1c, + 0x17, 0xb5, 0xdb, 0x6f, 0x36, 0xcc, 0x10, 0x0c, 0xa5, 0x73, 0xb1, 0x78, 0x56, 0x52, 0x0d, 0xaf, + 0x3e, 0x38, 0x17, 0xbc, 0x90, 0x8b, 0xc0, 0x70, 0x76, 0xca, 0xe1, 0x39, 0x08, 0x36, 0x62, 0x55, + 0x7d, 0x78, 0x3e, 0x7c, 0xae, 0xd8, 0x78, 0xf3, 0xf5, 0xd0, 0x32, 0xf6, 0x0f, 0x2d, 0xe3, 0xfb, + 0xa1, 0x65, 0x7c, 0x38, 0xb2, 0x2a, 0xfb, 0x47, 0x56, 0xe5, 0xe0, 0xc8, 0xaa, 0xbc, 0x7d, 0xe2, + 0x53, 0xd5, 0x8e, 0x5d, 0x88, 0x79, 0x98, 0xbd, 0xa7, 0xa8, 0x27, 0xb1, 0x58, 0x3c, 0xe6, 0xc9, + 0x7d, 0xb4, 0x73, 0xfc, 0x45, 0xd7, 0xb7, 0xa2, 0x3b, 0xac, 0xdf, 0xc3, 0x7b, 0xbf, 0x02, 0x00, + 0x00, 0xff, 0xff, 0x3b, 0x47, 0x43, 0x85, 0x02, 0x08, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -341,6 +505,8 @@ type MsgClient interface { AssignConsumerKey(ctx context.Context, in *MsgAssignConsumerKey, opts ...grpc.CallOption) (*MsgAssignConsumerKeyResponse, error) SubmitConsumerMisbehaviour(ctx context.Context, in *MsgSubmitConsumerMisbehaviour, opts ...grpc.CallOption) (*MsgSubmitConsumerMisbehaviourResponse, error) SubmitConsumerDoubleVoting(ctx context.Context, in *MsgSubmitConsumerDoubleVoting, opts ...grpc.CallOption) (*MsgSubmitConsumerDoubleVotingResponse, error) + OptIn(ctx context.Context, in *MsgOptIn, opts ...grpc.CallOption) (*MsgOptInResponse, error) + OptOut(ctx context.Context, in *MsgOptOut, opts ...grpc.CallOption) (*MsgOptOutResponse, error) } type msgClient struct { @@ -378,11 +544,31 @@ func (c *msgClient) SubmitConsumerDoubleVoting(ctx context.Context, in *MsgSubmi return out, nil } +func (c *msgClient) OptIn(ctx context.Context, in *MsgOptIn, opts ...grpc.CallOption) (*MsgOptInResponse, error) { + out := new(MsgOptInResponse) + err := c.cc.Invoke(ctx, "/interchain_security.ccv.provider.v1.Msg/OptIn", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) OptOut(ctx context.Context, in *MsgOptOut, opts ...grpc.CallOption) (*MsgOptOutResponse, error) { + out := new(MsgOptOutResponse) + err := c.cc.Invoke(ctx, "/interchain_security.ccv.provider.v1.Msg/OptOut", 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) SubmitConsumerMisbehaviour(context.Context, *MsgSubmitConsumerMisbehaviour) (*MsgSubmitConsumerMisbehaviourResponse, error) SubmitConsumerDoubleVoting(context.Context, *MsgSubmitConsumerDoubleVoting) (*MsgSubmitConsumerDoubleVotingResponse, error) + OptIn(context.Context, *MsgOptIn) (*MsgOptInResponse, error) + OptOut(context.Context, *MsgOptOut) (*MsgOptOutResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -398,6 +584,12 @@ func (*UnimplementedMsgServer) SubmitConsumerMisbehaviour(ctx context.Context, r func (*UnimplementedMsgServer) SubmitConsumerDoubleVoting(ctx context.Context, req *MsgSubmitConsumerDoubleVoting) (*MsgSubmitConsumerDoubleVotingResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SubmitConsumerDoubleVoting not implemented") } +func (*UnimplementedMsgServer) OptIn(ctx context.Context, req *MsgOptIn) (*MsgOptInResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OptIn not implemented") +} +func (*UnimplementedMsgServer) OptOut(ctx context.Context, req *MsgOptOut) (*MsgOptOutResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OptOut not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -457,6 +649,42 @@ func _Msg_SubmitConsumerDoubleVoting_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _Msg_OptIn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgOptIn) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).OptIn(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/interchain_security.ccv.provider.v1.Msg/OptIn", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).OptIn(ctx, req.(*MsgOptIn)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_OptOut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgOptOut) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).OptOut(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/interchain_security.ccv.provider.v1.Msg/OptOut", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).OptOut(ctx, req.(*MsgOptOut)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "interchain_security.ccv.provider.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -473,6 +701,14 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "SubmitConsumerDoubleVoting", Handler: _Msg_SubmitConsumerDoubleVoting_Handler, }, + { + MethodName: "OptIn", + Handler: _Msg_OptIn_Handler, + }, + { + MethodName: "OptOut", + Handler: _Msg_OptOut_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "interchain_security/ccv/provider/v1/tx.proto", @@ -687,6 +923,133 @@ func (m *MsgSubmitConsumerDoubleVotingResponse) MarshalToSizedBuffer(dAtA []byte return len(dAtA) - i, nil } +func (m *MsgOptIn) 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 *MsgOptIn) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgOptIn) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ConsumerKey) > 0 { + i -= len(m.ConsumerKey) + copy(dAtA[i:], m.ConsumerKey) + i = encodeVarintTx(dAtA, i, uint64(len(m.ConsumerKey))) + i-- + dAtA[i] = 0x1a + } + if len(m.ProviderAddr) > 0 { + i -= len(m.ProviderAddr) + copy(dAtA[i:], m.ProviderAddr) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProviderAddr))) + i-- + dAtA[i] = 0x12 + } + if len(m.ChainId) > 0 { + i -= len(m.ChainId) + copy(dAtA[i:], m.ChainId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChainId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgOptInResponse) 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 *MsgOptInResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgOptInResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgOptOut) 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 *MsgOptOut) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgOptOut) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ProviderAddr) > 0 { + i -= len(m.ProviderAddr) + copy(dAtA[i:], m.ProviderAddr) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProviderAddr))) + i-- + dAtA[i] = 0x12 + } + if len(m.ChainId) > 0 { + i -= len(m.ChainId) + copy(dAtA[i:], m.ChainId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChainId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgOptOutResponse) 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 *MsgOptOutResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgOptOutResponse) 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 @@ -784,14 +1147,70 @@ func (m *MsgSubmitConsumerDoubleVotingResponse) Size() (n int) { return n } -func sovTx(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozTx(x uint64) (n int) { - return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *MsgAssignConsumerKey) Unmarshal(dAtA []byte) error { - l := len(dAtA) +func (m *MsgOptIn) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ChainId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProviderAddr) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ConsumerKey) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgOptInResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgOptOut) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ChainId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProviderAddr) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgOptOutResponse) 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 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgAssignConsumerKey) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1358,6 +1777,366 @@ func (m *MsgSubmitConsumerDoubleVotingResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgOptIn) 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: MsgOptIn: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgOptIn: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", 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.ChainId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProviderAddr", 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.ProviderAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsumerKey", 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.ConsumerKey = 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 *MsgOptInResponse) 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: MsgOptInResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgOptInResponse: 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 (m *MsgOptOut) 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: MsgOptOut: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgOptOut: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", 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.ChainId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProviderAddr", 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.ProviderAddr = 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 *MsgOptOutResponse) 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: MsgOptOutResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgOptOutResponse: 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 4c597ded56..2fa584dd40 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -12,6 +12,8 @@ const ( EventTypeRemoveConsumerRewardDenom = "remove_consumer_reward_denom" EventTypeSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" EventTypeSubmitConsumerDoubleVoting = "submit_consumer_double_voting" + EventTypeOptIn = "opt_in" + EventTypeOptOut = "opt_out" EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash" EventTypeFeeDistribution = "fee_distribution" EventTypeConsumerSlashRequest = "consumer_slash_request" From 6e075652d3f6a876dad1cee1f1c13954714a31e5 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> Date: Thu, 8 Feb 2024 10:39:49 +0100 Subject: [PATCH 11/18] test: MBT: Add partial set security to model (feature branch version) (#1627) * Port changes from branch to main * Add model analysis changes to Makefile --- Makefile | 4 +- tests/mbt/model/README.md | 35 +- tests/mbt/model/ccv.qnt | 517 ++++++++-------------- tests/mbt/model/ccv_boundeddrift.qnt | 65 ++- tests/mbt/model/ccv_model.qnt | 325 +++++++++++++- tests/mbt/model/ccv_pss.qnt | 157 +++++++ tests/mbt/model/ccv_pss_model.qnt | 113 +++++ tests/mbt/model/ccv_pss_test.qnt | 34 ++ tests/mbt/model/ccv_sync.qnt | 2 +- tests/mbt/model/ccv_test.qnt | 23 +- tests/mbt/model/ccv_utils.qnt | 487 ++++++++++++++++++++ tests/mbt/model/libraries/extraSpells.qnt | 121 +++++ tests/mbt/model/run_invariants.sh | 6 + tests/mbt/run_invariants.sh | 5 - 14 files changed, 1512 insertions(+), 382 deletions(-) create mode 100644 tests/mbt/model/ccv_pss.qnt create mode 100644 tests/mbt/model/ccv_pss_model.qnt create mode 100644 tests/mbt/model/ccv_pss_test.qnt create mode 100644 tests/mbt/model/ccv_utils.qnt create mode 100755 tests/mbt/model/run_invariants.sh delete mode 100755 tests/mbt/run_invariants.sh diff --git a/Makefile b/Makefile index 350c66f8af..04df703370 100644 --- a/Makefile +++ b/Makefile @@ -131,7 +131,9 @@ test-trace: verify-models: quint test tests/mbt/model/ccv_test.qnt;\ quint test tests/mbt/model/ccv_model.qnt;\ - quint run --invariant "all{ValidatorUpdatesArePropagatedInv,ValidatorSetHasExistedInv,SameVscPacketsInv,MatureOnTimeInv,EventuallyMatureOnProviderInv}" tests/mbt/model/ccv_model.qnt --max-steps 200 --max-samples 200 + quint test tests/mbt/model/ccv_pss_test.qnt;\ + quint run --invariant "all{ValidatorUpdatesArePropagatedInv,ValidatorSetHasExistedInv,SameVscPacketsInv,MatureOnTimeInv,EventuallyMatureOnProviderInv}" tests/mbt/model/ccv_model.qnt --max-steps 200 --max-samples 200;\ + quint run --invariant "all{ValidatorUpdatesArePropagatedKeyAssignmentInv,ValidatorSetHasExistedKeyAssignmentInv,SameVscPacketsKeyAssignmentInv,MatureOnTimeInv,EventuallyMatureOnProviderInv,KeyAssignmentRulesInv}" tests/mbt/model/ccv_model.qnt --step stepKeyAssignment --max-steps 200 --max-samples 200 diff --git a/tests/mbt/model/README.md b/tests/mbt/model/README.md index f53900e77f..d466605bda 100644 --- a/tests/mbt/model/README.md +++ b/tests/mbt/model/README.md @@ -31,16 +31,31 @@ All the logic in EndBlock/BeginBlock happens here, like updating the validator s * `EndAndBeginBlockForConsumer(chain: Chain, timeAdvancement: Time)`: On the consumer `chain`, ends the current block, and begins a new one. Again, all the logic in EndBlock/BeginBlock happens here, like validator set change maturations. * `DeliverVscPacket(receiver: Chain)`: Delivers a pending VSCPacket from the provider to the consumer `receiver`. * `DeliverVscMaturedPacket(receiver: Chain)`: Delivers a pending VSCMaturedPacket from the consumer `receiver` to the provider. +* `KeyAssignment(chain: Chain, validator: Node, consumerAddr: ConsumerAddr)` (only when running with `--step stepKeyAssignment`): Assigns the `consumerAddr` to the `validator` on the `chain`. Note that we use "key" and "consumerAddr" pretty much interchangeably, as the model makes no differentiation between private keys, public keys, addresses, etc, as it doesn't model the cryptography. ### State machines There are 3 different "state machine layers" that can be put on top of the core logic. +Some layers include extra logic, need other invariants, ... #### ccv_model.qnt This is the most general state machine layer. It allows the most behaviour, in particular it allows abitrary clock drift between chains, it allows starting and stopping consumer chains during runtime, etc. This layer is most useful for model checking, because it encompasses the most behaviour. +As an optional module, it can also include KeyAssignment. + +##### KeyAssignment + +To run with key assignment, specify the step flag: `--step stepKeyAssignment`. + +KeyAssignment also needs some different invariants, see below. + +#### Partial Set Security + +To run with Partial Set Security, specify the step flag `--step stepBoundedDriftKeyAndPSS`. +This runs both PSS and Key Assignment. +It also requires running with `ccv_boundeddrift.qnt`, see below. #### ccv_boundeddrift.qnt This state machine layer is more restricted to generate more interesting traces: @@ -66,10 +81,7 @@ traces are not very useful for testing. To run unit tests, run ``` -quint test ccv_test.qnt -``` -and -``` +quint test ccv_test.qnt; quint test ccv_model.qnt ``` @@ -94,6 +106,13 @@ with a timestamp >= t + UnbondingPeriod on that consumer. - [X] EventuallyMatureOnProviderInv: If we send a VscPacket, this is eventually responded to by all consumers that were running at the time the packet was sent (and are still running). +Invariants only relevant when running with key assignment (`--step stepKeyAssignment`): +- [X] ValidatorSetHasExistedKeyAssignmentInv: Should replace ValidatorSetHasExistedInv when running with `--step stepKeyAssignment`. Validator sets are checked for equality under key assignment when checking whether they have existed. +- [X] SameVscPacketsKeyAssignmentInv: Should replace SameVscPacketsInv when running with `--step stepKeyAssignment`. VscPackets are checked for equality under key assignment when ensuring consumers receive the same ones. +- [X] KeyAssignmentRulesInv: Ensures the rules of key assignment are never violated. The two rules relevant for the model are: 1) validator A cannot assign consumer key K to consumer chain X if there is already a validator B (B!=A) +using K on the provider, and 2) validator A cannot assign consumer key K to consumer chain X if there is already a validator B using K on X + + Invariants can also be model-checked by Apalache, using this command: ``` quint verify --invariant ValidatorUpdatesArePropagatedInv,ValidatorSetHasExistedInv,SameVscPacketsInv,MatureOnTimeInv,EventuallyMatureOnProviderInv \ @@ -113,4 +132,10 @@ The available sanity checks are: - CanStopConsumer - CanTimeoutConsumer - CanSendVscPackets -- CanSendVscMaturedPackets \ No newline at end of file +- CanSendVscMaturedPackets +- CanAssignConsumerKey (only with `--step stepKeyAssignment`) +- CanHaveConsumerAddresses (only with `--step stepKeyAssignment`) +- CanOptIn (only with `--step stepBoundedDriftKeyAndPSS` on `ccv_boundeddrift.qnt`) +- CanOptOut (only with `--step stepBoundedDriftKeyAndPSS` on `ccv_boundeddrift.qnt`) +- CanFailOptOut (only with `--step stepBoundedDriftKeyAndPSS` on `ccv_boundeddrift.qnt`) +- CanHaveOptIn (only with `--step stepBoundedDriftKeyAndPSS` on `ccv_boundeddrift.qnt`) \ No newline at end of file diff --git a/tests/mbt/model/ccv.qnt b/tests/mbt/model/ccv.qnt index 6d9450b6f0..f780e4c9d5 100644 --- a/tests/mbt/model/ccv.qnt +++ b/tests/mbt/model/ccv.qnt @@ -1,6 +1,7 @@ // -*- mode: Bluespec; -*- module ccv_types { import Time.* from "./libraries/Time" + import extraSpells.* from "./libraries/extraSpells" type Node = str type Chain = str @@ -11,6 +12,11 @@ module ccv_types { // a list of validator sets per blocks, ordered by recency type VotingPowerHistory = List[ValidatorSet] + // For key assignment, to differentiate Nodes + // (on the provider) from the assigned + // keys/addresses on consumers + type ConsumerAddr = str + type VscPacket = { // the identifier for this packet @@ -93,6 +99,39 @@ module ccv_types { // a monotonic strictly increasing and positive ID that is used // to uniquely identify the Vscs sent to the consumer chains. runningVscId: int, + + // For every consumer chain, stores the consumer address assigned by each validator. + validatorToConsumerAddr: Chain -> (Node -> ConsumerAddr), + + // For every consumer chain, holds the provider validator for each assigned consumer address. + // Note that this is *not* precisely the reverse of validatorToConsumerAddr, + // because when a validator changes their consumer addr, + // the old one stays in this map until pruned. + consumerAddrToValidator: Chain -> (ConsumerAddr -> Node), + + // For every consumer chain, stores whether the key assignment for the consumer chain has changed in this block. + consumersWithAddrAssignmentChangesInThisBlock: Set[Chain], + + // the history of validator sets on the provider, but with the key assignments applied. + // This is needed to check invariants about the validator set when key assignments are in play. + keyAssignedValSetHistory: Chain -> VotingPowerHistory, + + // Stores the mapping from VSC ids to consumer validators addresses. Needed for pruning consumerAddrToValidator. + consumerAddrsToPrune: Chain -> VscId -> List[ConsumerAddr], + + // For every sent VSCPacket, stores the key assignments that were applied to send it. + keyAssignmentsForVSCPackets: VscId -> (Chain -> (Node -> ConsumerAddr)), + + // For each consumer chain, + // stores the set of validators that are opted into running the chain. + optedInVals: Chain -> Set[Node], + + // for each consumer, stores the top N for that consumer. + // The top N% of the validator set by voting power + // is obliged to run a topN chain. + // If the chain is a pure opt-in chain (where noone is forced to run it), + // this is 0. + topNByConsumer: Chain -> int, } // utility function: returns a provider state that is initialized minimally. @@ -105,6 +144,14 @@ module ccv_types { providerValidatorSetChangedInThisBlock: false, consumerStatus: Map(), runningVscId: 0, + validatorToConsumerAddr: Map(), + keyAssignedValSetHistory: Map(), + consumerAddrToValidator: Map(), + consumerAddrsToPrune: Map(), + keyAssignmentsForVSCPackets: Map(), + optedInVals: Map(), + topNByConsumer: Map(), + consumersWithAddrAssignmentChangesInThisBlock: Set() } @@ -192,6 +239,33 @@ module ccv_types { // given as a pure val so that we can switch cases based on // whether a chain is the provider or not pure val PROVIDER_CHAIN = "provider" + + // A record that keeps the information needed to add a new consumer. + // In particular, holds: + // the chain name/identifier, + // and the top N factor for the chain. + type ConsumerAdditionMsg = { + chain: Chain, + topN: int + } + + // Creates a new ConsumerAdditionMsg with a given top N. + pure def NewTopNConsumer(chain: Chain, topN: int): ConsumerAdditionMsg = { + { + chain: chain, + topN: topN + } + } + + // Creates a new ConsumerAdditionMsg with topN = 0. + pure def NewOptInConsumer(chain: Chain): ConsumerAdditionMsg = { + NewTopNConsumer(chain, 0) + } + + // Creates a new ConsumerAdditionMsg with top N = 100%. + pure def NewFullConsumer(chain: Chain): ConsumerAdditionMsg = { + NewTopNConsumer(chain, 100) + } } module ccv { @@ -212,6 +286,8 @@ module ccv { import Time.* from "./libraries/Time" import extraSpells.* from "./libraries/extraSpells" import ccv_types.* + import ccv_pss.* from "./ccv_pss" + import ccv_utils.* from "./ccv_utils" // =================== @@ -356,7 +432,7 @@ module ccv { } } else { // the packet has not timed out, so receive it on the consumer - val result = recvPacketOnConsumer(currentState, receiver, packet) + val result = recvPacketOnConsumer(currentState, receiver, packet, UnbondingPeriodPerChain.get(receiver)) val tmpState = result.newState if (result.hasError()) { (result, false) @@ -378,81 +454,96 @@ module ccv { // i.e. the timestamp for the next block is oldTimestamp + timeAdvancement timeAdvancement: Time, // a set of consumers that were not consumers before, but should be set to running now. - consumersToStart: Set[Chain], + consumersToStart: Set[ConsumerAdditionMsg], // a set of consumers that were running before, but should be set to stopped now. // This argument only needs to contain "voluntary" stops - // forced stops, e.g. because a consumer timed out, // will be added automatically. consumersToStop: Set[Chain]): Result = { val currentProviderState = currentState.providerState + val curValSet = currentProviderState.chainState.currentValidatorSet // check for vsc timeouts val timedOutConsumers = getRunningConsumers(currentProviderState).filter( consumer => - val res = TimeoutDueToVscTimeout(currentState, consumer) + val res = TimeoutDueToVscTimeout(currentState, consumer, VscTimeout) res._1 ) - // run the shared core chainState logic val newChainState = currentProviderState.chainState.endAndBeginBlockShared(timeAdvancement) - val providerStateAfterTimeAdvancement = currentProviderState.with( - "chainState", newChainState - ) - + val providerStateAfterTimeAdvancement = + {...currentProviderState, chainState: newChainState} val tmpState = currentState.with( "providerState", providerStateAfterTimeAdvancement ) - // send vsc packets + // send vsc packets (will be a noop if no sends are necessary) val providerStateAfterSending = - if (currentProviderState.providerValidatorSetChangedInThisBlock and - // important: check this on the provider state after the consumer advancement, not on the current state. - getRunningConsumers(providerStateAfterTimeAdvancement).size() > 0) { - // need to use the old timestamp because this happens during EndBlock - providerStateAfterTimeAdvancement.sendVscPackets(currentProviderState.chainState.runningTimestamp) - } else { - providerStateAfterTimeAdvancement - } + providerStateAfterTimeAdvancement.sendVscPackets( + currentProviderState.chainState.runningTimestamp, + CcvTimeout.get(PROVIDER_CHAIN) + ) // start/stop chains - val res = providerStateAfterSending.consumerStatus.StartStopConsumers( + val res = providerStateAfterSending.StartStopConsumers( consumersToStart, consumersToStop, timedOutConsumers ) - val newConsumerStatus = res._1 + val providerStateAfterConsumerAdvancement = res._1.with("providerValidatorSetChangedInThisBlock", false) val err = res._2 - val providerStateAfterConsumerAdvancement = providerStateAfterSending.with( - "consumerStatus", newConsumerStatus - ).with( - "providerValidatorSetChangedInThisBlock", false - ) + + val consumerAdditions = consumersToStart.map(consumer => consumer.chain) + + // for each running consumer chain, opt in validators that are in the top N + val providerStateAfterPSS = providerStateAfterConsumerAdvancement.endBlockPSS() if (err != "") { Err(err) } else { - // for each consumer we just set to running, set its initial validator set to be the current one on the provider. - val valSet = providerStateAfterConsumerAdvancement.chainState.currentValidatorSet + // for each consumer chain, apply the key assignment to the current validator set + val currentValSets = getRunningConsumers(providerStateAfterPSS).mapBy( + (consumer) => + providerStateAfterPSS.applyKeyAssignmentToValSet( + consumer, + // get the validator set after partial set security has been applied + GetPSSValidatorSet(providerStateAfterPSS, curValSet, consumer) + ) + ) + + // store the current validator set with the key assignments applied in the history + val newKeyAssignedValSetHistory = currentValSets.keys().mapBy( + (consumer) => + providerStateAfterPSS.keyAssignedValSetHistory + .getOrElse(consumer, List()) // get the existing history (empty list if no history yet) + .prepend(currentValSets.get(consumer)) // prepend the current validator set with key assignments applied + ) + + val providerStateAfterStoringValSets = providerStateAfterPSS.with( + "keyAssignedValSetHistory", newKeyAssignedValSetHistory + ) + val newConsumerStateMap = tmpState.consumerStates.keys().mapBy( (consumer) => - if (consumersToStart.contains(consumer)) { + if (consumerAdditions.contains(consumer)) { val currentConsumerState: ConsumerState = tmpState.consumerStates.get(consumer) + // correctly set the state for the new consumer val newConsumerState: ConsumerState = currentConsumerState.with( "chainState", currentConsumerState.chainState.with( - "currentValidatorSet", valSet + "currentValidatorSet", currentValSets.get(consumer) ).with( "votingPowerHistory", - List(valSet) + List(currentValSets.get(consumer)) ).with( "lastTimestamp", - providerStateAfterConsumerAdvancement.chainState.lastTimestamp + providerStateAfterStoringValSets.chainState.lastTimestamp ).with( "runningTimestamp", - providerStateAfterConsumerAdvancement.chainState.runningTimestamp + providerStateAfterStoringValSets.chainState.runningTimestamp ) ) newConsumerState @@ -461,7 +552,7 @@ module ccv { } ) val newState = tmpState.with( - "providerState", providerStateAfterConsumerAdvancement + "providerState", providerStateAfterStoringValSets ).with( "consumerStates", newConsumerStateMap ) @@ -523,308 +614,90 @@ module ccv { } } - // =================== - // UTILITY FUNCTIONS - // which do not hold the core logic of the protocol, but are still part of it - // =================== - - // Returns the new ConsumerStatusMap according to the consumers to stop - // and the consumers to time out. - // If a consumer is both stopped and timed out, it will be timed out. - // The second return is an error string: If it is not equal to "", - // it contains an error message, and the first return should be ignored. - pure def stopConsumers( - currentConsumerStatusMap: Chain -> str, - consumersToStop: Set[Chain], - consumersToTimeout: Set[Chain]): (Chain -> str, str) = { - val runningConsumers = currentConsumerStatusMap.keys().filter( - chain => currentConsumerStatusMap.get(chain) == RUNNING - ) - // all consumers to stop must be running right now, else we have an error - if (consumersToStop.exclude(runningConsumers).size() > 0) { - (currentConsumerStatusMap, "Cannot stop a consumer that is not running") + // Validator providerNode assigns their address for the consumer to be the consumerAddress. + pure def assignConsumerKey(currentState: ProtocolState, consumer: Chain, providerNode: Node, consumerAddr: ConsumerAddr): Result = { + // rule 1: validator A cannot assign consumer key K to consumer chain X + // if there is already a validator B (B!=A) using K on the provider + pure val provCurValSet = currentState.providerState.chainState.currentValidatorSet + if (provCurValSet.keys().exists(node => node != providerNode and node == consumerAddr)) { + Err("validator A cannot assign consumer key K to consumer chain X + if there is already a validator B (B!=A) using K on the provider") + } else { + // rule 2: validator A cannot assign consumer key K to consumer chain X if + // there is already a validator B using K on X + pure val valByConsAddr = currentState.providerState.consumerAddrToValidator.getOrElse(consumer, Map()) + if (valByConsAddr.keys().contains(consumerAddr)) { + Err("consumer key is already in use on the consumer chain") } else { - val newConsumerStatusMap = currentConsumerStatusMap.keys().mapBy( - (chain) => - if (consumersToTimeout.contains(chain)) { - TIMEDOUT - } else if (consumersToStop.contains(chain)) { - STOPPED + // this key can be assigned + + // get the old assigned key + pure val consKeyByVal = currentState.providerState.validatorToConsumerAddr.getOrElse(consumer, Map()) + pure val p = if (consKeyByVal.keys().contains(providerNode)) { + // providerNode had previously assigned a consumer key + (consKeyByVal.get(providerNode), true) + } else { + (providerNode, false) + } + // the consumer address that was previously associated with the node + pure val oldConsAddr = p._1 + // whether the old address was explicitly assigned, or the default key + pure val prevAssigned = p._2 + + // set the old address for pruning, if it was assigned + pure val tmpState = if (prevAssigned) { + AppendConsumerAddrToPrune(currentState, oldConsAddr, consumer) + } else { + currentState + } + + // check whether the validator has positive power + pure val provValSet = currentState.providerState.chainState.currentValidatorSet + pure val provValPower = if (provValSet.keys().contains(providerNode)) provValSet.get(providerNode) else 0 + pure val consumersWithAddrAssignmentChangesInThisBlock = + if (provValPower > 0) { + // if the consumer has positive power, the relevant key assignment for the consumer changed + currentState.providerState.consumersWithAddrAssignmentChangesInThisBlock.union(Set(consumer)) } else { - currentConsumerStatusMap.get(chain) + // otherwise, the consumer doesn't need to know about the change, so no change + currentState.providerState.consumersWithAddrAssignmentChangesInThisBlock } + pure val tmpStateAfterKeyAssignmentReplacement = tmpState.with( + "providerState", tmpState.providerState.with( + "consumersWithAddrAssignmentChangesInThisBlock", consumersWithAddrAssignmentChangesInThisBlock + ) ) - (newConsumerStatusMap, "") - } - } - - // Returns the new ConsumerStatusMap according to the consumers to start. - // The second return is an error string: If it is not equal to "", - // it contains an error message, and the first return should be ignored. - pure def startConsumers( - currentConsumerStatusMap: Chain -> str, - consumersToStart: Set[Chain]): (Chain -> str, str) = { - val nonConsumers = currentConsumerStatusMap.keys().filter( - chain => currentConsumerStatusMap.get(chain) == NOT_CONSUMER - ) - // all consumers to start must be nonConsumers right now, otherwise we have an error - if (consumersToStart.exclude(nonConsumers).size() > 0) { - (currentConsumerStatusMap, "cannot start a consumer that is stopped or already a consumer") - } else { - val newConsumerStatusMap = currentConsumerStatusMap.keys().mapBy( - (chain) => - if (consumersToStart.contains(chain)) { - RUNNING - } else { - currentConsumerStatusMap.get(chain) - } + + pure val newvalidatorToConsumerAddr = currentState.providerState.validatorToConsumerAddr.put( + consumer, + currentState.providerState.validatorToConsumerAddr.getOrElse(consumer, Map()).put( + providerNode, + consumerAddr + ) ) - (newConsumerStatusMap, "") - } - } - - pure def StartStopConsumers( - currentConsumerStatusMap: Chain -> str, - consumersToStart: Set[Chain], - consumersToStop: Set[Chain], - consumersToTimeout: Set[Chain] - ): (Chain -> str, str) = { - // check if any consumer is both started and stopped - if (consumersToStart.intersect(consumersToStop).size() > 0) { - (currentConsumerStatusMap, "Cannot start and stop a consumer at the same time") - } else { - val res1 = currentConsumerStatusMap.startConsumers(consumersToStart) - val newConsumerStatus = res1._1 - val err1 = res1._2 - val res2 = newConsumerStatus.stopConsumers(consumersToStop, consumersToTimeout) - val err2 = res2._2 - if (err1 != "") { - (currentConsumerStatusMap, err1) - } else if (err2 != "") { - (currentConsumerStatusMap, err2) - } else { - (res2._1, "") - } - } - } - - // Takes the currentValidatorSet and puts it as the newest set of the voting history - pure def enterCurValSetIntoBlock(chainState: ChainState): ChainState = { - chainState.with( - "votingPowerHistory", chainState.votingPowerHistory.prepend( - chainState.currentValidatorSet - ) - ) - } - - // Advances the timestamp in the chainState by timeAdvancement - pure def advanceTime(chainState: ChainState, timeAdvancement: Time): ChainState = - { - ...chainState, - lastTimestamp: chainState.runningTimestamp, - runningTimestamp: chainState.runningTimestamp + timeAdvancement, - } - - // common logic to update the chain state, used by both provider and consumers. - pure def endAndBeginBlockShared(chainState: ChainState, timeAdvancement: Time): ChainState = { - chainState.enterCurValSetIntoBlock().advanceTime(timeAdvancement) - } - - // returns the providerState with the following modifications: - // * sends VscPackets to all running consumers, using the provided timestamp as sending time - // * increments the runningVscId - // This should only be called when the provider chain is ending a block, - // and only when the running validator set is considered to have changed - // and there is a consumer to send a packet to. - pure def sendVscPackets(providerState: ProviderState, sendingTimestamp: Time): ProviderState = { - val newSentPacketsPerConsumer = ConsumerChains.mapBy( - (consumer) => - // if validator set changed and the consumer is running, send a packet - if (providerState.providerValidatorSetChangedInThisBlock and - isRunningConsumer(consumer, providerState)) { - List({ - id: providerState.runningVscId, - validatorSet: providerState.chainState.currentValidatorSet, - sendingTime: sendingTimestamp, - timeoutTime: sendingTimestamp + CcvTimeout.get(PROVIDER_CHAIN) - }) - } else { - List() - } - ) - val newOutstandingPacketsToConsumer = ConsumerChains.mapBy( - (consumer) => - providerState.outstandingPacketsToConsumer.get(consumer).concat( - newSentPacketsPerConsumer.get(consumer) - ) - ) - val newSentVscPackets = ConsumerChains.mapBy( - (consumer) => - providerState.sentVscPacketsToConsumer.get(consumer).concat( - newSentPacketsPerConsumer.get(consumer) + pure val newconsumerAddrToValidator = currentState.providerState.consumerAddrToValidator.put( + consumer, + currentState.providerState.consumerAddrToValidator.getOrElse(consumer, Map()).put( + consumerAddr, + providerNode ) ) - { - ...providerState, - outstandingPacketsToConsumer: newOutstandingPacketsToConsumer, - sentVscPacketsToConsumer: newSentVscPackets, - providerValidatorSetChangedInThisBlock: false, - runningVscId: providerState.runningVscId + 1, - } - } - // receives a given packet (sent by the provider) on the consumer. The arguments are the consumer chain that is receiving the packet, and the packet itself. - // To receive a packet, modify the running validator set (not the one entered into the block yet, - // but the candidate that would be put into the block if it ended now) - // and store the maturation time for the packet. - pure def recvPacketOnConsumer(currentState: ProtocolState, receiver: Chain, packet: VscPacket): Result = { - if(not(isRunningConsumer(receiver, currentState.providerState))) { - Err("Receiver is not currently a consumer - must have 'running' status!") - } else { - // update the running validator set, but not the history yet, - // as that only happens when the next block is started - val currentConsumerState: ConsumerState = currentState.consumerStates.get(receiver) - val newConsumerState: ConsumerState = - { - ...currentConsumerState, - chainState: currentConsumerState.chainState.with( - "currentValidatorSet", packet.validatorSet - ), - maturationTimes: currentConsumerState.maturationTimes.append( - ( - packet, - currentConsumerState.chainState.runningTimestamp + UnbondingPeriodPerChain.get(receiver) - ) - ), - receivedVscPackets: currentConsumerState.receivedVscPackets.prepend(packet) - } - val newConsumerStates = currentState.consumerStates.set(receiver, newConsumerState) - val newState = currentState.with( - "consumerStates", newConsumerStates - ) - Ok(newState) - } - } - - // receives a given packet on the provider. The arguments are the consumer chain that sent the packet, and the packet itself. - // To receive a packet, add it to the list of received maturations. - pure def recvPacketOnProvider(currentState: ProtocolState, sender: Chain, packet: VscMaturedPacket): Result = { - if (not(isRunningConsumer(sender, currentState.providerState))) { - Err("Sender is not currently a consumer - must have 'running' status!") - } else if (currentState.providerState.sentVscPacketsToConsumer.get(sender).head().id != packet.id) { - // the packet is not the oldest sentVscPacket, something went wrong - Err("Received maturation is not for the oldest sentVscPacket") - } else { - val currentReceivedMaturations = currentState.providerState.receivedMaturations - val newReceivedMaturations = currentReceivedMaturations.union(Set(packet)) - val newProviderState = currentState.providerState.with( - "receivedMaturations", newReceivedMaturations - ) - // prune the sentVscPacket - val newSentVscPacket = currentState.providerState.sentVscPacketsToConsumer.get(sender).tail() - val newState = currentState.with( - "providerState", - {...newProviderState, - sentVscPacketsToConsumer: currentState.providerState.sentVscPacketsToConsumer.set(sender, newSentVscPacket) - } - ) - Ok(newState) - } - } - - // removes the oldest outstanding packet from the consumer. on-chain, this would happen when the packet is acknowledged. - // only the oldest packet can be removed, since we model ordered channels. - pure def removeOutstandingPacketFromConsumer(currentState: ProtocolState, sender: Chain): ProtocolState = { - val currentOutstandingPackets = currentState.consumerStates.get(sender).outstandingPacketsToProvider - val newOutstandingPackets = currentOutstandingPackets.tail() - val newConsumerState = currentState.consumerStates.get(sender).with( - "outstandingPacketsToProvider", newOutstandingPackets - ) - val newConsumerStates = currentState.consumerStates.set(sender, newConsumerState) - val newState = currentState.with( - "consumerStates", newConsumerStates - ) - newState - } - - // removes the oldest outstanding packet (to the given consumer) from the provider. - // on-chain, this would happen when the packet is acknowledged. - // only the oldest packet can be removed, since we model ordered channels. - pure def removeOutstandingPacketFromProvider(currentState: ProtocolState, receiver: Chain): ProtocolState = { - val currentOutstandingPackets = currentState.providerState.outstandingPacketsToConsumer.get(receiver) - val newOutstandingPackets = currentOutstandingPackets.tail() - val newProviderState = currentState.providerState.with( - "outstandingPacketsToConsumer", - currentState.providerState.outstandingPacketsToConsumer.set(receiver, newOutstandingPackets) - ) - val newState = currentState.with( - "providerState", newProviderState - ) - newState - } - - // Returns a ProtocolState where the current validator set on the provider is set to - // newValidatorSet. - pure def setProviderValidatorSet(currentState: ProtocolState, newValidatorSet: ValidatorSet): ProtocolState = { - pure val newChainState = currentState.providerState.chainState.with( - "currentValidatorSet", newValidatorSet - ) - currentState.with( - "providerState", - currentState.providerState.with( - "chainState", newChainState - ) - ) - } - - // Returns true if the given chain is currently a running consumer, false otherwise. - pure def isRunningConsumer(chain: Chain, providerState: ProviderState): bool = { - val status = providerState.consumerStatus.get(chain) - status == RUNNING - } - - // Returns the set of all consumer chains that currently have the status RUNNING. - pure def getRunningConsumers(providerState: ProviderState): Set[Chain] = { - providerState.consumerStatus.keys().filter( - chain => providerState.consumerStatus.get(chain) == RUNNING - ) - } - - // Returns the set of all consumer chains that currently have the status NOT_CONSUMER. - pure def getNonConsumers(providerState: ProviderState): Set[Chain] = { - providerState.consumerStatus.keys().filter( - chain => providerState.consumerStatus.get(chain) == NOT_CONSUMER - ) - } + pure val newProviderState = tmpStateAfterKeyAssignmentReplacement.providerState.with( + "validatorToConsumerAddr", newvalidatorToConsumerAddr + ).with( + "consumerAddrToValidator", newconsumerAddrToValidator + ) - // Returns whether the consumer has timed out due to the VscTimeout, and an error message. - // If the second return is not equal to "", the first return should be ignored. - // If it is equal to "", the first return will be true if the consumer has timed out and should be dropped, - // or false otherwise. - pure def TimeoutDueToVscTimeout(currentState: ProtocolState, consumer: Chain): (bool, str) = - // check for errors: the consumer is not running - if (not(isRunningConsumer(consumer, currentState.providerState))) { - (false, "Consumer is not currently a consumer - must have 'running' status!") - } else { - val providerState = currentState.providerState - val consumerState: ConsumerState = currentState.consumerStates.get(consumer) - - // has a packet been sent on the provider more than VscTimeout ago, but we have not received an answer since then? - val sentVscPacketsToConsumer = providerState.sentVscPacketsToConsumer.get(consumer) - if(sentVscPacketsToConsumer.length() > 0) { - val oldestSentVscPacket = sentVscPacketsToConsumer.head() // if length is 0, this is undefined, but we check for this before we use it - if(oldestSentVscPacket.sendingTime + VscTimeout < providerState.chainState.runningTimestamp) { - (true, "") - } else { - // no timeout yet, it has not been VscTimeout since that packet was sent - (false, "") - } - } else { - // no packet has been sent yet, so no timeout - (false, "") + Ok( + tmpStateAfterKeyAssignmentReplacement.with( + "providerState", newProviderState + ) + ) } } + } // =================== // ASSUMPTIONS ON MODEL PARAMETERS diff --git a/tests/mbt/model/ccv_boundeddrift.qnt b/tests/mbt/model/ccv_boundeddrift.qnt index 21a53074c0..f99f92d350 100644 --- a/tests/mbt/model/ccv_boundeddrift.qnt +++ b/tests/mbt/model/ccv_boundeddrift.qnt @@ -1,9 +1,11 @@ module ccv_boundeddrift { import ccv_model.* from "ccv_model" import ccv_types as Ccvt from "ccv" + import ccv_utils.* from "ccv_utils" import ccv from "ccv" import Time.* from "./libraries/Time" import extraSpells.* from "./libraries/extraSpells" + import ccv_pss_model.* from "ccv_pss_model" // The boundeddrift module has its own step function. @@ -32,7 +34,7 @@ module ccv_boundeddrift { // Given the name of a chain, gets a set with the chain states of all other chains. def GetOtherChainStates(advancingChain: Ccvt::Chain): Set[Ccvt::ChainState] = - val runCons = ccv::getRunningConsumers(currentState.providerState) + val runCons = getRunningConsumers(currentState.providerState) if (advancingChain == Ccvt::PROVIDER_CHAIN) { runCons.map(c => currentState.consumerStates.get(c).chainState) } else { @@ -48,7 +50,7 @@ module ccv_boundeddrift { } def GetRunningChainStates(): Set[Ccvt::ChainState] = - val runCons = ccv::getRunningConsumers(currentState.providerState) + val runCons = getRunningConsumers(currentState.providerState) val consumerChainStates = runCons.map(c => currentState.consumerStates.get(c).chainState) consumerChainStates.union(Set(currentState.providerState.chainState)) @@ -59,19 +61,22 @@ module ccv_boundeddrift { stepCommon, // allow actions that do not influence time // advance a block for a consumer - all { - runningConsumers.size() > 0, // ensure there is a running consumer, otherwise this action does not make sense - nondet chain = runningConsumers.oneOf() - val maxAdv = findMaxTimeAdvancement(GetChainState(chain), GetOtherChainStates(chain), maxDrift) - val possibleAdvancements = timeAdvancements.filter(t => t <= maxAdv) - all { - possibleAdvancements.size() > 0, // ensure there is a possible advancement, otherwise this action does not make sense - nondet timeAdvancement = possibleAdvancements.oneOf() - EndAndBeginBlockForConsumer(chain, timeAdvancement), - } - }, + stepBoundedDriftConsumer, // advance a block for the provider + stepBoundedDriftProvider + } + + action stepBoundedDriftProvider: bool = { + stepBoundedDriftProvider_helper(allFullConsumers) + } + + action stepBoundedDriftProviderPSS: bool = { + stepBoundedDriftProvider_helper(variousPossibleTopN) + } + + // As an argument, takes a function that, when invoked, gives a top N value to use for a new consumer chain. + action stepBoundedDriftProvider_helper(topNOracle: Set[int]): bool = { val maxAdv = findMaxTimeAdvancement(GetChainState(Ccvt::PROVIDER_CHAIN), GetOtherChainStates(Ccvt::PROVIDER_CHAIN), maxDrift) val possibleAdvancements = timeAdvancements.filter(t => t <= maxAdv) all { @@ -79,14 +84,42 @@ module ccv_boundeddrift { // advance a block for the provider val consumerStatus = currentState.providerState.consumerStatus nondet consumersToStart = oneOf(nonConsumers.powerset()) + nondet topN = oneOf(topNOracle) + nondet consumerAdditions = consumersToStart.map(c => Ccvt::NewTopNConsumer(c, topN)) // make it so we stop consumers only with small likelihood: - nondet stopConsumers = oneOf(1.to(100)) - nondet consumersToStop = if (stopConsumers <= consumerStopChance) oneOf(runningConsumers.powerset()) else Set() + nondet stopConsumersRand = oneOf(1.to(100)) + nondet consumersToStop = if (stopConsumersRand <= consumerStopChance) oneOf(runningConsumers.powerset()) else Set() nondet timeAdvancement = oneOf(possibleAdvancements) - EndAndBeginBlockForProvider(timeAdvancement, consumersToStart, consumersToStop), + EndAndBeginBlockForProvider(timeAdvancement, consumerAdditions, consumersToStop), } } + action stepBoundedDriftConsumer = all { + runningConsumers.size() > 0, // ensure there is a running consumer, otherwise this action does not make sense + nondet chain = runningConsumers.oneOf() + val maxAdv = findMaxTimeAdvancement(GetChainState(chain), GetOtherChainStates(chain), maxDrift) + val possibleAdvancements = timeAdvancements.filter(t => t <= maxAdv) + all { + possibleAdvancements.size() > 0, // ensure there is a possible advancement, otherwise this action does not make sense + nondet timeAdvancement = possibleAdvancements.oneOf() + EndAndBeginBlockForConsumer(chain, timeAdvancement), + } + } + + action stepBoundedDriftKeyAssignment = any { + stepBoundedDrift, + nondetKeyAssignment, + } + + action stepBoundedDriftKeyAndPSS = any { + stepCommon, + stepBoundedDriftProviderPSS, + stepBoundedDriftConsumer, + nondetKeyAssignment, + StepOptIn, + StepOptOut, + } + // INVARIANT // The maxDrift between chains is never exceeded. // This *should* be ensured by the step function. diff --git a/tests/mbt/model/ccv_model.qnt b/tests/mbt/model/ccv_model.qnt index fba4ddea6c..dc6cd10188 100644 --- a/tests/mbt/model/ccv_model.qnt +++ b/tests/mbt/model/ccv_model.qnt @@ -4,6 +4,7 @@ module ccv_model { import ccv_types.* from "./ccv" import Time.* from "./libraries/Time" import extraSpells.* from "./libraries/extraSpells" + import ccv_utils.* from "./ccv_utils" pure val consumerChainList = List("consumer1", "consumer2", "consumer3") pure val consumerChains = consumerChainList.toSet() @@ -14,6 +15,8 @@ module ccv_model { pure val ccvTimeouts = chains.mapBy(chain => 3 * Week) pure val nodes = Set("node1", "node2", "node3", "node4", "node5", "node6", "node7", "node8", "node9", "node10") + // possible consumer addresses that nodes can assign their key to + pure val consumerAddresses = Set("consAddr1", "consAddr2", "consAddr3", "consAddr4", "consAddr5", "consAddr6", "consAddr7", "consAddr8", "consAddr9", "consAddr10") pure val InitialValidatorSet = nodes.mapBy(node => 100) import ccv( @@ -31,6 +34,7 @@ module ccv_model { TrustingPeriodPerChain: Chain -> Time, ConsumerChains: Set[Chain], Nodes: Set[Node], + ConsumerAddresses: Set[ConsumerAddr], InitialValidatorSet: Node -> int, } @@ -42,7 +46,13 @@ module ccv_model { var currentState: ProtocolState - // a type storing the parameters used in actions. + // a type storing the parameters used in actions, + // as well as return values that are not visible from the state, + // i.e. errors. + // Note that whether an error is returned, + // or whether the action is simply not possible when an error occurs, is + // a design choice that is different for each action, + // or can depend on the type of error. // this is used in the trace to store // the name of the last action, plus the parameters we passed to it. // Note: This type holds ALL parameters that are used in ANY action, @@ -52,10 +62,12 @@ module ccv_model { kind: str, consumerChain: Chain, timeAdvancement: Time, - consumersToStart: Set[Chain], + consumersToStart: Set[ConsumerAdditionMsg], consumersToStop: Set[Chain], validator: Node, changeAmount: int, + consumerAddr: ConsumerAddr, + expectedError: str, // if the action returns an error, it goes here. } @@ -70,7 +82,7 @@ module ccv_model { // otherwise connections will break down. pure val timeAdvancements = Set(1 * Second, 1 * Day, 1 * Week - 1 * Hour) - pure def emptyAction = + pure def emptyAction: Action = { kind: "", consumerChain: "", @@ -79,6 +91,8 @@ module ccv_model { consumersToStop: Set(), validator: "", changeAmount: 0, + consumerAddr: "", + expectedError: "", } @@ -106,6 +120,8 @@ module ccv_model { ).with( "currentValidatorSet", InitialValidatorSet ) + ).with( + "keyAssignedValSetHistory", ConsumerChains.mapBy(chain => List(InitialValidatorSet)) ) currentState' = { providerState: providerStateWithConsumers, @@ -120,6 +136,7 @@ module ccv_model { Nodes: nodes, InitialValidatorSet: InitialValidatorSet, TrustingPeriodPerChain: TrustingPeriodPerChain, + ConsumerAddresses: consumerAddresses, } } @@ -158,7 +175,7 @@ module ccv_model { action EndAndBeginBlockForProvider( timeAdvancement: Time, - consumersToStart: Set[Chain], + consumersToStart: Set[ConsumerAdditionMsg], consumersToStop: Set[Chain]): bool = val result = endAndBeginBlockForProvider(currentState, timeAdvancement, consumersToStart, consumersToStop) all { @@ -179,6 +196,7 @@ module ccv_model { params' = params, } + // stepCommon is the core functionality of steps that does not have anything to do with time. action stepCommon = any { nondet node = oneOf(nodes) @@ -218,17 +236,14 @@ module ccv_model { val consumerStatus = currentState.providerState.consumerStatus nondet consumersToStart = oneOf(nonConsumers.powerset()) + val consumerAdditions = consumersToStart.map(chain => NewFullConsumer(chain)) nondet consumersToStop = oneOf(runningConsumers.powerset()) nondet timeAdvancement = oneOf(timeAdvancements) - EndAndBeginBlockForProvider(timeAdvancement, consumersToStart, consumersToStop), + EndAndBeginBlockForProvider(timeAdvancement, consumerAdditions, consumersToStop), stepCommon } - // ================== - // UTILITY FUNCTIONS - // ================== - pure def oldest(packets: Set[VscPacket]): VscPacket = val newestPossiblePacket: VscPacket = { id: 0, @@ -290,9 +305,11 @@ module ccv_model { // Every validator set on any consumer chain MUST either be or have been // a validator set on the provider chain. val ValidatorSetHasExistedInv = - runningConsumers.forall(chain => + runningConsumers.forall(chain => // for all running consumers currentState.consumerStates.get(chain).chainState.votingPowerHistory.toSet().forall( + // go through all its historical and current validator sets validatorSet => providerValidatorHistory.toSet().contains(validatorSet) + // and check that they are also historical or current validator sets on the provider ) ) @@ -310,10 +327,6 @@ module ccv_model { consumer => currentState.providerState.sentVscPacketsToConsumer.get(consumer).toSet().exists( packet => packet.validatorSet == providerValSetInCurBlock ) - // or the consumer was just started, which we detect by the consumer having a timestamp of 0 - // and the consumer having the validator set that was just sent in the block - or - (currentState.consumerStates.get(consumer).chainState.lastTimestamp == 0 and currentState.consumerStates.get(consumer).chainState.currentValidatorSet == providerValSetInCurBlock) ) // Every consumer chain receives the same sequence of @@ -360,8 +373,11 @@ module ccv_model { val lastTimeAdvancement = trace[trace.length()-1].timeAdvancement val lastBlockTime = currentState.consumerStates.get(ConsumerWithPotentialMaturations).chainState.lastTimestamp - lastTimeAdvancement val MatureOnTimeInv = + // if a consumer ended a block MaturationPrecondition implies + // then all matured packets need to have been processed and removed from the packets + // waiting to mature currentState.consumerStates.get(ConsumerWithPotentialMaturations).maturationTimes.toSet().forall( pair => val maturationTime = pair._2 @@ -378,7 +394,7 @@ module ccv_model { val EventuallyMatureOnProviderInv = runningConsumers.forall( consumer => { - val sentPackets = currentState.providerState.sentVscPacketsToConsumer.get(consumer).toSet() + val sentPackets = currentState.providerState.sentVscPacketsToConsumer.getOrElse(consumer, List()).toSet() sentPackets.forall( packet => // consumer still has time to respond @@ -390,6 +406,7 @@ module ccv_model { } ) + // ================= // SANITY CHECKS // ================= @@ -421,7 +438,7 @@ module ccv_model { val CanSendVscPackets = not(ConsumerChains.exists( consumer => - currentState.providerState.outstandingPacketsToConsumer.get(consumer).length() > 0 + currentState.providerState.outstandingPacketsToConsumer.getOrElse(consumer, List()).length() > 0 )) val CanReceiveVscPackets = @@ -482,14 +499,14 @@ module ccv_model { // the validator set has changed assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet.put("node1", 150)), // start consumer1 - EndAndBeginBlockForProvider(1 * Second, Set("consumer1"), Set()) + EndAndBeginBlockForProvider(1 * Second, Set(NewFullConsumer("consumer1")), Set()) }) .then( all { // consumer1 was started assert(currentState.providerState.consumerStatus.get("consumer1") == RUNNING), // but no packet was sent to consumer 1 - assert(currentState.providerState.outstandingPacketsToConsumer.get("consumer1").length() == 0), + assert(currentState.providerState.outstandingPacketsToConsumer.getOrElse("consumer1", List()).length() == 0), // the validator set on the provider was entered into the history assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet.put("node1", 150), InitialValidatorSet)), // change voting power on provider again @@ -559,7 +576,7 @@ module ccv_model { run SameVscPacketsManualTest = init.then( // start all consumers except for consumer3 - EndAndBeginBlockForProvider(1 * Second, Set("consumer1", "consumer2"), Set()) + EndAndBeginBlockForProvider(1 * Second, Set(NewFullConsumer("consumer1"), NewFullConsumer("consumer2")), Set()) ).then( // change voting power VotingPowerChange("node1", 50) @@ -574,7 +591,7 @@ module ccv_model { DeliverVscPacket("consumer2") ).then( // start consumer3 - EndAndBeginBlockForProvider(1 * Second, Set("consumer3"), Set()) + EndAndBeginBlockForProvider(1 * Second, Set(NewFullConsumer("consumer3")), Set()) ).then( // do another voting power change VotingPowerChange("node2", 50) @@ -605,7 +622,7 @@ module ccv_model { init .then( // start all consumer chains - EndAndBeginBlockForProvider(1 * Second, ConsumerChains, Set()) + EndAndBeginBlockForProvider(1 * Second, ConsumerChains.map(c => NewFullConsumer(c)), Set()) ) .then( // change voting power @@ -632,4 +649,270 @@ module ccv_model { VotingPowerChange("node1", 50) // action needs to be there but does not matter what it is } ) + + + // ===== KEY ASSIGNMENT ======= + action stepKeyAssignment = + any { + step, + nondetKeyAssignment, + } + + action nondetKeyAssignment = + all { + runningConsumers.size() > 0, + nondet node = oneOf(nodes) + nondet consumerAddr = oneOf(consumerAddresses) + nondet consumer = oneOf(runningConsumers) + KeyAssignment(consumer, node, consumerAddr), + } + + action KeyAssignment( + chain: Chain, + validator: Node, + consumerAddr: ConsumerAddr + ): bool = + val result = assignConsumerKey(currentState, chain, validator, consumerAddr) + all { + hasError(result) == false, + currentState' = result.newState, + trace' = trace.append( + {...emptyAction, + kind: "KeyAssignment", + consumerChain: chain, + validator: validator, + consumerAddr: consumerAddr + } + ), + params' = params, + } + + // invariants for key assignment - some invariants are in addition, some need to be adjusted from the original model + + // Every validator set on any consumer chain MUST either be or have been + // a validator set on the provider chain, under the key assignment at the time. + val providerKeyAssignedValSetHistory = currentState.providerState.keyAssignedValSetHistory + + val ValidatorSetHasExistedKeyAssignmentInv = + runningConsumers.forall(chain => // for every running consumer + currentState.consumerStates.get(chain).chainState.votingPowerHistory.toSet().forall( + // for every validator set the consumer ever had + validatorSet => providerKeyAssignedValSetHistory.getOrElse(chain, List()).toSet().exists( + // that validator set needs to also have existed on the provider + provValSet => removeZeroPowers(provValSet) == removeZeroPowers(validatorSet) + ) + ) + ) + + // Any update in the power of a validator on the provider + // MUST be present in a ValidatorSetChangePacket that is sent to all registered consumer chains, + // and the key assignment of each validator should be applied in that VSCPacket. + val ValidatorUpdatesArePropagatedKeyAssignmentInv = + // when the provider has just entered a validator set into a block... + ValUpdatePrecondition and currentState.providerState.providerValidatorSetChangedInThisBlock + implies + val providerValSetInCurBlock = providerValidatorHistory.head() + // ... for each consumer that is running then ... + runningConsumers.forall( + // ...the validator set under key assignment is in a sent packet... + val providerState = currentState.providerState + consumer => providerState.sentVscPacketsToConsumer.get(consumer).toSet().exists( + packet => + packet.validatorSet == + applyKeyAssignmentToValSet(providerState, consumer, providerValSetInCurBlock) + ) + ) + + // Every consumer chain receives the same sequence of + // ValidatorSetChangePackets in the same order. + // NOTE: since not all consumer chains are running all the time, + // we need a slightly weaker invariant: + // For consumer chains c1, c2, if both c1 and c2 received a packet p1 sent at t1 and a packet p2 sent at t2, + // then both have received ALL packets that were sent between t1 and t2. + val SameVscPacketsKeyAssignmentInv = + runningConsumers.forall( + consumer1 => runningConsumers.forall( + consumer2 => { + val packets1 = currentState.consumerStates.get(consumer1).receivedVscPackets + val packets2 = currentState.consumerStates.get(consumer2).receivedVscPackets + val commonPackets = packets1.toSet().intersect(packets2.toSet()) + if (commonPackets.size() == 0) { + true // they don't share any packets, so nothing to check + } else { + val newestCommonPacket = newest(commonPackets) + val oldestCommonPacket = oldest(commonPackets) + // get all packets sent between the oldest and newest common packet + val packetsBetween1 = packets1.select( + packet => packet.sendingTime >= oldestCommonPacket.sendingTime and packet.sendingTime <= newestCommonPacket.sendingTime + ) + val packetsBetween2 = packets2.select( + packet => packet.sendingTime >= oldestCommonPacket.sendingTime and packet.sendingTime <= newestCommonPacket.sendingTime + ) + + // revert key assignments + val packetsBetween1noKeyAssignment = packetsBetween1.foldl( + List(), + (acc, packet) => + acc.concat(List({...packet, + validatorSet: revertKeyAssignment( + currentState.providerState.keyAssignmentsForVSCPackets + .getOrElse(packet.id, Map()) + .getOrElse(consumer1, Map()), + packet.validatorSet) + })) + ) + + val packetsBetween2noKeyAssignment = packetsBetween2.foldl( + List(), (acc, packet) => + acc.concat(List({...packet, + validatorSet: revertKeyAssignment( + currentState.providerState.keyAssignmentsForVSCPackets + .getOrElse(packet.id, Map()) + .getOrElse(consumer2, Map()), + packet.validatorSet) + })) + ) + // check that the packets between the common packets are equal + // when key assignment is reversed + packetsBetween1noKeyAssignment == packetsBetween2noKeyAssignment + } + } + ) + ) + + + // Rules for key assignment: + val KeyAssignmentRulesInv = + NoProviderReuse and NoDuplicationOnSameConsumer + + // validator A cannot assign consumer key K to consumer chain X if there is already a validator B (B!=A) using K on the provider + val NoProviderReuse = + consumerChains.forall( + consumer => + val valConsPk = currentState.providerState.validatorToConsumerAddr.getOrElse(consumer, Map()) + valConsPk.keys().forall( + node => + val consAddr = valConsPk.get(node) + // either the key is the nodes key itself (B == A) + consAddr == node or + // or the consAddr must not be a validator on the provider + not(currentState.providerState.chainState.currentValidatorSet.keys().contains(consAddr)) + ) + ) + + // validator A cannot assign consumer key K to consumer chain X if there is already a validator B using K on X + val NoDuplicationOnSameConsumer = + consumerChains.forall( + consumer => + val valConsPk = currentState.providerState.validatorToConsumerAddr.getOrElse(consumer, Map()) + valConsPk.keys().forall( + node => + val consAddr = valConsPk.get(node) + // no other node may use consAddr + not(valConsPk.keys().exists( + otherNode => otherNode != node and valConsPk.get(otherNode) == consAddr + )) + ) + ) + + // sanity checks + val CanAssignConsumerKey = + not(consumerChains.exists( + consumer => + currentState.providerState.consumerAddrToValidator.getOrElse(consumer, Map()).keys().size() > 0 + )) + + val CanHaveConsumerAddresses = + not(consumerChains.exists( + consumer => + currentState.consumerStates.get(consumer).chainState.currentValidatorSet.keys().exists( + addr => addr.in(consumerAddresses) + ) + )) + + // == tests for key assignment == + run KeyAssignmentTest = + init + .then( + // start all consumer chains + EndAndBeginBlockForProvider(1 * Second, consumerChains.map(c => NewFullConsumer(c)), Set()) + ) + .then( + // node 1 assigns a key on consumer1 + KeyAssignment("consumer1", "node1", "consAddr1") + ) + .then( + // end and begin block to make sure the key assignment is processed and the packet is sent + EndAndBeginBlockForProvider(1 * Second, Set(), Set()) + ) + .then( + // receive the packet on the consumer + DeliverVscPacket("consumer1") + ) + .then( + // end and begin block to make sure the packet is processed + EndAndBeginBlockForConsumer("consumer1", 1 * Second) + ) + .then( + all { + // the key should be present in the valset on the consumer, and the node itself should not + assert(currentState.consumerStates.get("consumer1").chainState.currentValidatorSet.getOrElse("node1", 0) == 0), + assert(currentState.consumerStates.get("consumer1").chainState.currentValidatorSet.get("consAddr1") == 100), + // try some key assignments that should fail/succeed without comitting to state + val res = assignConsumerKey(currentState, "consumer1", "node1", "consAddr1") + // fail - key already assigned (even if it is the same node) + assert(hasError(res)), + val res2 = assignConsumerKey(currentState, "consumer1", "node2", "consAddr1") + // fail - key assigned to other node + assert(hasError(res2)), + val res3 = assignConsumerKey(currentState, "consumer2", "node2", "consAddr1") + // ok - may reuse the key on a different consumer + assert(not(hasError(res3))), + val res4 = assignConsumerKey(currentState, "consumer1", "node2", "node1") + // fail - may not reuse a provider key of a different val + assert(hasError(res4)), + val res5 = assignConsumerKey(currentState, "consumer1", "node1", "consAddr2") + // ok - assigning unused key to node + assert(not(hasError(res5))), + val res6 = assignConsumerKey(currentState, "consumer1", "node1", "node1") + // ok - going back to original key + assert(not(hasError(res6))), + // mature the vsc packet on the consumer + EndAndBeginBlockForConsumer("consumer1", unbondingPeriods.get("consumer1") + 1 * Hour) + } + ) + .then( + // End a block to send the maturation + EndAndBeginBlockForConsumer("consumer1", 1 * Second) + ) + .then( + // deliver the vsc matured packet to the provider + DeliverVscMaturedPacket("consumer1") + ) + .then( + // the old key should have been pruned + all { + // check that pruning has been performed nicely + assert(currentState.providerState.consumerAddrToValidator.get("consumer1").get("consAddr1") == "node1"), + assert(currentState.providerState.consumerAddrsToPrune.get("consumer1").getOrElse(0, List()).length() == 0), + // action does not matter + VotingPowerChange("node1", 50) + } + ) + + run KeyAssignmentInvTest = + init.then( + KeyAssignment("consumer3", "node3", "consAddr6") + ).then( + VotingPowerChange("node1", 50) + ) + .then( + EndAndBeginBlockForProvider(1 * Second, Set(NewFullConsumer("consumer1"), NewFullConsumer("consumer2")), Set()) + ).then( + all { + ValidatorSetHasExistedKeyAssignmentInv, + // action doesn't matter + VotingPowerChange("node1", 50) + } + ) } \ No newline at end of file diff --git a/tests/mbt/model/ccv_pss.qnt b/tests/mbt/model/ccv_pss.qnt new file mode 100644 index 0000000000..4759fe6b34 --- /dev/null +++ b/tests/mbt/model/ccv_pss.qnt @@ -0,0 +1,157 @@ +// This module contains logic for PSS (Partial Set Security). +// PSS is a variant/extension of CCV that +// allows for only a subset of the validator set +// to secure a consumer chain. +// Not all logic related to PSS is inside this module, as some logic is +// too tightly coupled with the core CCV logic, +// which is instead found in ccv.qnt +module ccv_pss { + import ccv_types.* from "./ccv" + import extraSpells.* from "./libraries/extraSpells" + import ccv_utils.* from "./ccv_utils" + + // Given a base validator set, an N for a top N chain, and a set of validators that have opted in to the chain, + // returns the validator set that should be sent to the chain. + // Assumes that the value for N is valid. + pure def GetPSSValidatorSet(providerState: ProviderState, origValSet: ValidatorSet, consumer: Chain): ValidatorSet = { + pure val optedInVals = providerState.optedInVals.getOrElse(consumer, Set()) + GetPSSValidatorSet_helper(origValSet, optedInVals) + } + + pure def GetPSSValidatorSet_helper(origValSet: ValidatorSet, optedInVals: Set[Node]): ValidatorSet = { + origValSet.mapFilter(v => optedInVals.contains(v)) + } + + // Given a validator set and N, returns the top N% of validators by power. + // Note that in the edge case of multiple validators having the same power, + // this will always include all validators with the same power as the lowest top N validator. + pure def GetTopNVals(origValSet: ValidatorSet, N: int): Set[Node] = { + // == sort validators by power == + // define a comparator that compares validators by power + pure def powerCompare(a: Node, b: Node): Ordering = { + pure val powA = origValSet.get(a) + pure val powB = origValSet.get(b) + intCompare(powB, powA) + } + // get a sorted list of validators by power + pure val sortedVals = origValSet.keys().toSortedList(powerCompare) + + // == compute the threshold of how much power the top N have == + pure val totalPower = origValSet.mapValuesSum() + pure val topNPower = totalPower * N / 100 + + // == construct the validator set by going through the sorted vals == + pure val res = sortedVals.foldl( + // accumulator carries 4 values: + // * set of vals in top N (starts with empty set) + // * total power added so far (starts with 0) + // * whether we should add the next validator if it has the same power as the previous one, + // regardless of total power (starts with false) + // * the power of the last validator added (starts with 0) + (Set(), 0, false, 0), + (acc, validator) => + pure val curValSet = acc._1 + pure val accPower = acc._2 + pure val shouldAddSamePow = acc._3 + pure val lastPow = acc._4 + + pure val validatorPower = origValSet.get(validator) + if (validatorPower == lastPow and shouldAddSamePow) { + // we should add the validator because it has the same power as the previous one, + // and we add regardless of total power because we need to include all + // vals with the same power if we include one of them + pure val newAccPower = accPower + validatorPower + (curValSet.union(Set(validator)), newAccPower, true, validatorPower) + } else if (validatorPower > 0 and accPower < topNPower) { + // if we don't have enough power yet, add the validator to the set + pure val newAccPower = accPower + validatorPower + (curValSet.union(Set(validator)), newAccPower, true, validatorPower) + } else { + // if we have enough power and we also are done adding + // all validators with the same power as the lowest top N validator, + // don't add them + (curValSet, accPower, false, 0) + } + ) + res._1 + } + + // Opts a validator in for a consumer chain the provider. + // Possible before the consumer chain starts running, + // and will then be applied when the consumer chain starts running. + pure def OptIn(currentState: ProtocolState, consumer: Chain, validator: Node): Result = { + pure val optedInVals = currentState.providerState.optedInVals.get(consumer) + pure val newOptedInVals = optedInVals.union(Set(validator)) + Ok({ + ...currentState, + providerState: { + ...currentState.providerState, + optedInVals: currentState.providerState.optedInVals.put(consumer, newOptedInVals) + } + }) + } + + // Returns true if the given validator is in the top N for the given consumer chain, + // and false otherwise. + pure def IsTopN(currentState: ProtocolState, validator: Node, consumer: Chain): bool = { + val proviValSet = currentState.providerState.chainState.currentValidatorSet + val N = currentState.providerState.topNByConsumer.get(consumer) + + val topNValSet = GetTopNVals(proviValSet, N) + + topNValSet.contains(validator) + } + + // Returns true if the given validator has opted in to the given consumer chain, + pure def IsOptedIn(currentState: ProtocolState, validator: Node, consumer: Chain): bool = { + currentState.providerState.optedInVals.getOrElse(consumer, Set()).contains(validator) + } + + // Opts a validator out. Safe to call before the consumer chain even runs. + // Will not stop the validator set from being forced to validate when in the top N. + // Validators that are in the top N will not be able to opt out, and + // an error will be returned. + // Similarly, if the validator is not opted in, an error will be returned. + pure def OptOut(currentState: ProtocolState, consumer: Chain, validator: Node): Result = { + if (currentState.IsTopN(validator, consumer)) { + Err("Cannot opt out a validator that is in the top N") + } else if (not(currentState.IsOptedIn(validator, consumer))) { + Err("Cannot opt out a validator that is not opted in") + } else { + pure val optedInVals = currentState.providerState.optedInVals.get(consumer) + pure val newOptedInVals = optedInVals.exclude(Set(validator)) + Ok({ + ...currentState, + providerState: { + ...currentState.providerState, + optedInVals: currentState.providerState.optedInVals.put(consumer, newOptedInVals) + } + }) + } + } + + // Runs the PSS logic that needs to run on endblock. + // Concretely, this will forcefully opt in all validators that are in the top N + // for each chain. + pure def endBlockPSS(providerState: ProviderState): ProviderState = { + val runningConsumers = providerState.getRunningConsumers() + runningConsumers.fold( + providerState, + (acc, consumer) => endBlockPSS_helper(acc, consumer) + ) + } + + // Runs the PSS logic for a single consumer. + // Should only be run for running chains. + pure def endBlockPSS_helper(providerState: ProviderState, consumer: Chain): ProviderState = { + val proviValSet = providerState.chainState.currentValidatorSet + val topNVals = GetTopNVals(proviValSet, providerState.topNByConsumer.get(consumer)) + val prevOptedInVals = providerState.optedInVals.getOrElse(consumer, Set()) + // opt in all the top N validators, i.e. union the top N vals with the previous opted in vals + val newOptedInVals = providerState.optedInVals.put(consumer, prevOptedInVals.union(topNVals)) + { + ...providerState, + optedInVals: newOptedInVals + } + } +} \ No newline at end of file diff --git a/tests/mbt/model/ccv_pss_model.qnt b/tests/mbt/model/ccv_pss_model.qnt new file mode 100644 index 0000000000..76c4873b43 --- /dev/null +++ b/tests/mbt/model/ccv_pss_model.qnt @@ -0,0 +1,113 @@ +module ccv_pss_model { + import ccv_types.* from "./ccv" + import ccv_model.* from "./ccv_model" + import ccv_pss.* from "./ccv_pss" + import extraSpells.* from "./libraries/extraSpells" + + action StepOptIn(): bool = { + all { + runningConsumers.size() > 0, + nondet consumer = oneOf(runningConsumers) + nondet validator = oneOf(nodes) + OptIn_Deterministic(consumer, validator) + } + } + + action OptIn_Deterministic(consumer: Chain, validator: Node): bool = { + val res = OptIn(currentState, consumer, validator) + all { + currentState' = res.newState, + trace' = trace.append( + { + ...emptyAction, + kind: "OptIn", + consumerChain: consumer, + validator: validator, + expectedError: res.error + } + ), + params' = params, + } + } + + action StepOptOut(): bool = { + all { + runningConsumers.size() > 0, + nondet consumer = oneOf(runningConsumers) + nondet validator = oneOf(nodes) + OptOut_Deterministic(consumer, validator) + } + } + + action OptOut_Deterministic(consumer: Chain, validator: Node): bool = { + val res = OptOut(currentState, consumer, validator) + all { + currentState' = res.newState, + trace' = trace.append( + { + ...emptyAction, + kind: "OptOut", + consumerChain: consumer, + validator: validator, + expectedError: res.error + } + ), + params' = params, + } + } + + // Different sets of possible values for the topN parameter. + val allFullConsumers: Set[int] = Set(100) + val allOptIn: Set[int] = Set(0) + // only choose a few values for top N here to not make the "edge cases" of 0 and 100 too unlikely + val variousPossibleTopN: Set[int] = Set(0, 50, 70, 80, 90, 100) + + // INVARIANTS + + // For a consumer chain with a given top N value, + // the total VP on the consumer is at least N% of the total VP of some historical val set on the provider. + val AtLeastTopNPower: bool = + runningConsumers.forall(consumer => { + val topN = currentState.providerState.topNByConsumer.get(consumer) + val totalPowerConsu = currentState.consumerStates.get(consumer).chainState.currentValidatorSet.mapValuesSum() + currentState.providerState.chainState.votingPowerHistory.toSet().exists( + valSet => { + val totalPowerProvi = valSet.mapValuesSum() + + totalPowerConsu >= totalPowerProvi * topN / 100 + } + ) + }) + + // SANITY CHECKS + + val CanOptIn = { + not( + trace[length(trace)-1].kind == "OptIn" + and + trace[length(trace)-1].expectedError == "" + ) + } + + val CanOptOut = { + not( + trace[length(trace)-1].kind == "OptOut" + and + trace[length(trace)-1].expectedError == "" + ) + } + + val CanFailOptOut = { + not( + trace[length(trace)-1].kind == "OptOut" + and + trace[length(trace)-1].expectedError != "" + ) + } + + val CanHaveOptIn = { + currentState.providerState.topNByConsumer.keys().exists(consumer => { + currentState.providerState.topNByConsumer.get(consumer) != 100 + }) + } +} \ No newline at end of file diff --git a/tests/mbt/model/ccv_pss_test.qnt b/tests/mbt/model/ccv_pss_test.qnt new file mode 100644 index 0000000000..2a84f6dbd4 --- /dev/null +++ b/tests/mbt/model/ccv_pss_test.qnt @@ -0,0 +1,34 @@ +// This module contains logic for PSS (Partial Set Security). +// PSS is a variant/extension of CCV that +// allows for only a subset of the validator set +// to secure a consumer chain. +// Not all logic related to PSS is inside this module, as some logic is +// too tightly coupled with the core CCV logic, +// which is instead found in ccv.qnt +module ccv_pss_test { + import ccv_types.* from "./ccv" + import extraSpells.* from "./libraries/extraSpells" + import ccv_utils.* from "./ccv_utils" + import ccv_pss.* from "./ccv_pss" + + run TopNTest = + val valSet = + Map("d" -> 25, "c1" -> 15, "c" -> 15, "b2" -> 10, "b1" -> 10, "b" -> 10, "a2" -> 5, "a1" -> 5, "a" -> 5) + // total power: 5*3 + 10*3 + 15*2 + 25 = 100 + all + { + assert(GetTopNVals(valSet, 0) == Set()), + assert(GetTopNVals(valSet, 1) == Set("d")), + assert(GetTopNVals(valSet, 10) == Set("d")), + assert(GetTopNVals(valSet, 25) == Set("d")), + // if one validator with a power is included, all validators with that power need to be included + assert(GetTopNVals(valSet, 26) == Set("d", "c1", "c")), + assert(GetTopNVals(valSet, 45) == Set("d", "c1", "c")), + assert(GetTopNVals(valSet, 55) == Set("d", "c1", "c")), + assert(GetTopNVals(valSet, 56) == Set("d", "c1", "c", "b2", "b1", "b")), + assert(GetTopNVals(valSet, 85) == Set("d", "c1", "c", "b2", "b1", "b")), + assert(GetTopNVals(valSet, 86) == valSet.keys()), + assert(GetTopNVals(valSet, 95) == valSet.keys()), + assert(GetTopNVals(valSet, 100) == valSet.keys()), + } +} \ No newline at end of file diff --git a/tests/mbt/model/ccv_sync.qnt b/tests/mbt/model/ccv_sync.qnt index d828b88a76..8af76693b8 100644 --- a/tests/mbt/model/ccv_sync.qnt +++ b/tests/mbt/model/ccv_sync.qnt @@ -25,7 +25,7 @@ module ccv_sync { action initSync = all { init.then( - EndAndBeginBlockForProvider(1 * Second, consumerChains, Set()) + EndAndBeginBlockForProvider(1 * Second, consumerChains.map(c => ccvt::NewFullConsumer(c)), Set()) ), QueuedChainsToEndBlock' = consumerChainList.foldl( List(), diff --git a/tests/mbt/model/ccv_test.qnt b/tests/mbt/model/ccv_test.qnt index 4dae28ec03..1aeb50067d 100644 --- a/tests/mbt/model/ccv_test.qnt +++ b/tests/mbt/model/ccv_test.qnt @@ -5,6 +5,7 @@ module ccv_test { import ccv_types.* from "./ccv" import Time.* from "./libraries/Time" import extraSpells.* from "./libraries/extraSpells" + import ccv_utils.* from "./ccv_utils" pure val consumerChains = Set("sender", "receiver") pure val chains = consumerChains.union(Set(PROVIDER_CHAIN)) @@ -241,15 +242,15 @@ module ccv_test { "chain3" -> STOPPED ) val res = StartStopConsumers( - currentConsumerStatusMap, - Set("chain1"), + GetEmptyProviderState.with("consumerStatus", currentConsumerStatusMap), + Set(NewOptInConsumer("chain1")), Set("chain2"), Set() ) res._2 == "" and - res._1.get("chain1") == RUNNING and - res._1.get("chain2") == STOPPED and - res._1.get("chain3") == STOPPED + res._1.consumerStatus.get("chain1") == RUNNING and + res._1.consumerStatus.get("chain2") == STOPPED and + res._1.consumerStatus.get("chain3") == STOPPED } run ConsumerStatusMapAlreadyRunningTest = @@ -260,8 +261,8 @@ module ccv_test { "chain3" -> STOPPED ) val res = StartStopConsumers( - currentConsumerStatusMap, - Set("chain2"), + GetEmptyProviderState.with("consumerStatus", currentConsumerStatusMap), + Set(NewOptInConsumer("chain2")), Set("chain3"), Set() ) @@ -276,8 +277,8 @@ module ccv_test { "chain3" -> STOPPED ) val res = StartStopConsumers( - currentConsumerStatusMap, - Set("chain1"), + GetEmptyProviderState.with("consumerStatus", currentConsumerStatusMap), + Set(NewOptInConsumer("chain1")), Set("chain3"), Set() ) @@ -292,8 +293,8 @@ module ccv_test { "chain3" -> STOPPED ) val res = StartStopConsumers( - currentConsumerStatusMap, - Set("chain1"), + GetEmptyProviderState.with("consumerStatus", currentConsumerStatusMap), + Set(NewOptInConsumer("chain1")), Set("chain1"), Set() ) diff --git a/tests/mbt/model/ccv_utils.qnt b/tests/mbt/model/ccv_utils.qnt new file mode 100644 index 0000000000..b80400c3f7 --- /dev/null +++ b/tests/mbt/model/ccv_utils.qnt @@ -0,0 +1,487 @@ +// This file contains utility functions for ccv.qnt +// that are not part of the API/core logic of ccv, but still relevant +// for the functional logic of the protocol. +module ccv_utils { + import ccv_types.* from "./ccv" + import extraSpells.* from "./libraries/extraSpells" + import Time.* from "./libraries/Time" + + + // Takes the current provider state and validator set and returns + // the validator set under the current key assignments for the given consumer, as stored in the provider state. + pure def applyKeyAssignmentToValSet( + providerState: ProviderState, + consumer: Chain, + valSet: ValidatorSet + ): ValidatorSet = { + // map each validator to a tuple of (consumer address, voting power) + valSet.keys().map( + (node) => + pure val power = valSet.get(node) + // check if the validator has a key assigned + pure val validatorToConsumerAddr = providerState.validatorToConsumerAddr.getOrElse(consumer, Map()) + if (validatorToConsumerAddr.keys().contains(node)) { + // the validator has a key assigned + pure val consAddr = validatorToConsumerAddr.get(node) + (consAddr, power) + } else { + // the validator has no key assigned + // use the default key + (node, power) + } + ).fold( // fold the (addr,pow) tuples into a map addr -> pow + Map(), + (acc, pair) => acc.put(pair._1, pair._2) + ) + } + + // Takes a validator set, to which a key assignment has been applied, and reverts it, + // i.e. returns the original validator set. + // This also filters out validators that are assigned 0 power. + pure def revertKeyAssignment( + keyAssignment: Node -> ConsumerAddr, + valSetWithAssignment: ValidatorSet + ): ValidatorSet = { + // get an assignment from consumer addr to nodes + pure val reverseAssignment = keyAssignment.keys().map( + (consAddr) => + (keyAssignment.get(consAddr), consAddr) + ).fold( + Map(), + (acc, pair) => acc.put(pair._1, pair._2) + ) + + // for each node in the valset, reverse its key assignment + valSetWithAssignment.keys().map( + (addr) => + pure val power = valSetWithAssignment.get(addr) + // if the addr has a key assigned, use that. otherwise, the addr doesn't have a key assigned, + // and therefore *is* the key that should be used. + pure val consAddr = reverseAssignment.getOrElse(addr, addr) + (consAddr, power) + ).filter( + pair => pair._2 > 0 + ).fold( + Map(), + (acc, pair) => acc.put(pair._1, pair._2) + ) + } + + // Appends the key assignment for the given oldConsAddr on the consumer by a validator + // to be pruned when a VscMaturedPacket for the current runningVscId is received from the consumer. + pure def AppendConsumerAddrToPrune(currentState: ProtocolState, oldConsAddr: ConsumerAddr, consumer: Chain): ProtocolState = { + pure val vscId = currentState.providerState.runningVscId + pure val consumerAddrsToPrune = currentState.providerState.consumerAddrsToPrune.getOrElse(consumer, Map()) + pure val prevConsAddrs = consumerAddrsToPrune.getOrElse(oldConsAddr, Map()).getOrElse(vscId, []) + + pure val newConsAddrsToPrune = consumerAddrsToPrune.put(vscId, prevConsAddrs.append(oldConsAddr)) + + currentState.with( + "providerState", + currentState.providerState.with( + "consumerAddrsToPrune", + currentState.providerState.consumerAddrsToPrune.put(consumer, newConsAddrsToPrune) + ) + ) + } + + // Returns the new ConsumerStatusMap according to the consumers to stop + // and the consumers to time out. + // If a consumer is both stopped and timed out, it will be timed out. + // The second return is an error string: If it is not equal to "", + // it contains an error message, and the first return should be ignored. + pure def stopConsumers( + currentConsumerStatusMap: Chain -> str, + consumersToStop: Set[Chain], + consumersToTimeout: Set[Chain]): (Chain -> str, str) = { + val runningConsumers = currentConsumerStatusMap.keys().filter( + chain => currentConsumerStatusMap.get(chain) == RUNNING + ) + // all consumers to stop must be running right now, else we have an error + if (consumersToStop.exclude(runningConsumers).size() > 0) { + (currentConsumerStatusMap, "Cannot stop a consumer that is not running") + } else { + val newConsumerStatusMap = currentConsumerStatusMap.keys().mapBy( + (chain) => + if (consumersToTimeout.contains(chain)) { + TIMEDOUT + } else if (consumersToStop.contains(chain)) { + STOPPED + } else { + currentConsumerStatusMap.get(chain) + } + ) + (newConsumerStatusMap, "") + } + } + + // Returns the new ConsumerStatusMap according to the consumers to start. + // The second return is an error string: If it is not equal to "", + // it contains an error message, and the first return should be ignored. + pure def startConsumers( + currentConsumerStatusMap: Chain -> str, + consumersToStart: Set[Chain]): (Chain -> str, str) = { + val nonConsumers = currentConsumerStatusMap.keys().filter( + chain => currentConsumerStatusMap.get(chain) == NOT_CONSUMER + ) + // all consumers to start must be nonConsumers right now, otherwise we have an error + if (consumersToStart.exclude(nonConsumers).size() > 0) { + (currentConsumerStatusMap, "cannot start a consumer that is stopped or already a consumer") + } else { + val newConsumerStatusMap = currentConsumerStatusMap.keys().mapBy( + (chain) => + if (consumersToStart.contains(chain)) { + RUNNING + } else { + currentConsumerStatusMap.get(chain) + } + ) + (newConsumerStatusMap, "") + } + } + + pure def StartStopConsumers( + currentProviderState: ProviderState, + consumersToStart: Set[ConsumerAdditionMsg], + consumersToStop: Set[Chain], + consumersToTimeout: Set[Chain] + ): (ProviderState, str) = { + val consumerAdditions = consumersToStart.map( + msg => msg.chain + ) + // check if any consumer is both started and stopped + if (consumerAdditions.intersect(consumersToStop).size() > 0) { + (currentProviderState, "Cannot start and stop a consumer at the same time") + } else { + val res1 = currentProviderState.consumerStatus.startConsumers(consumerAdditions) + val newConsumerStatus = res1._1 + val err1 = res1._2 + val res2 = newConsumerStatus.stopConsumers(consumersToStop, consumersToTimeout) + val err2 = res2._2 + // set the top N values in the provider correctly + + if (err1 != "") { + (currentProviderState, err1) + } else if (err2 != "") { + (currentProviderState, err2) + } else { + (currentProviderState + .with("consumerStatus", res2._1) + .SetTopNValues(consumersToStart), "") + } + } + } + + + // Takes the currentValidatorSet and puts it as the newest set of the voting history + pure def enterCurValSetIntoBlock(chainState: ChainState): ChainState = { + chainState.with( + "votingPowerHistory", chainState.votingPowerHistory.prepend( + removeZeroPowers(chainState.currentValidatorSet) + ) + ) + } + + // Advances the timestamp in the chainState by timeAdvancement + pure def advanceTime(chainState: ChainState, timeAdvancement: Time): ChainState = + { + ...chainState, + lastTimestamp: chainState.runningTimestamp, + runningTimestamp: chainState.runningTimestamp + timeAdvancement, + } + + // common logic to update the chain state, used by both provider and consumers. + pure def endAndBeginBlockShared(chainState: ChainState, timeAdvancement: Time): ChainState = { + chainState.enterCurValSetIntoBlock().advanceTime(timeAdvancement) + } + + // returns the providerState with the following modifications: + // * sends VscPackets to all running consumers, using the provided timestamp as sending time + // * increments the runningVscId + // This should only be called when the provider chain is ending a block. + // If no vsc packets need to be sent, this will be a noop. + // the ccv timeout should be the ccv timeout for the provider chain. + pure def sendVscPackets( + providerState: ProviderState, + sendingTimestamp: Time, + ccvTimeout: Time): ProviderState = { + val newSentPacketsPerConsumer = providerState.getConsumers().mapBy( // compute, for each consumer, a list of new packets to be sent + (consumer) => + // if validator set changed or the key assignments for this chain changed, and the consumer is running, send a packet + if ((providerState.providerValidatorSetChangedInThisBlock or + providerState.consumersWithAddrAssignmentChangesInThisBlock.contains(consumer)) + and + isRunningConsumer(consumer, providerState)) { + // send a packet, i.e. use a list with one element (the packet to be sent) + List({ + id: providerState.runningVscId, + // apply key assignment to the current validator set + validatorSet: providerState.applyKeyAssignmentToValSet( + consumer, + providerState.chainState.currentValidatorSet + ), + sendingTime: sendingTimestamp, + timeoutTime: sendingTimestamp + ccvTimeout + }) + } else { + // no packet to be sent, so empty list + List() + } + ) + val newOutstandingPacketsToConsumer = providerState.getConsumers().mapBy( + (consumer) => + providerState.outstandingPacketsToConsumer.getOrElse(consumer, List()).concat( + newSentPacketsPerConsumer.get(consumer) + ) + ) + val newSentVscPackets = providerState.getConsumers().mapBy( + (consumer) => + providerState.sentVscPacketsToConsumer.getOrElse(consumer, List()).concat( + newSentPacketsPerConsumer.get(consumer) + ) + ) + { + ...providerState, + outstandingPacketsToConsumer: newOutstandingPacketsToConsumer, + sentVscPacketsToConsumer: newSentVscPackets, + runningVscId: providerState.runningVscId + 1, + // we ended the block and processed that the valset or key assignments changed, + // so reset the flags + providerValidatorSetChangedInThisBlock: false, + consumersWithAddrAssignmentChangesInThisBlock: Set(), + // remember the key assignments that were applied to send the packets + keyAssignmentsForVSCPackets: providerState.keyAssignmentsForVSCPackets.put( + providerState.runningVscId, + providerState.validatorToConsumerAddr + ) + } + } + + // receives a given packet (sent by the provider) on the consumer. The arguments are the consumer chain that is receiving the packet, and the packet itself, + // as well as the unbonding period for the consumer chain. + // To receive a packet, modify the running validator set (not the one entered into the block yet, + // but the candidate that would be put into the block if it ended now) + // and store the maturation time for the packet. + pure def recvPacketOnConsumer( + currentState: ProtocolState, + receiver: Chain, + packet: VscPacket, + receiverUnbondingPeriod: Time): Result = { + if(not(isRunningConsumer(receiver, currentState.providerState))) { + Err("Receiver is not currently a consumer - must have 'running' status!") + } else { + // update the running validator set, but not the history yet, + // as that only happens when the next block is started + val currentConsumerState: ConsumerState = currentState.consumerStates.get(receiver) + val newConsumerState: ConsumerState = + { + ...currentConsumerState, + chainState: currentConsumerState.chainState.with( + "currentValidatorSet", packet.validatorSet + ), + maturationTimes: currentConsumerState.maturationTimes.append( + ( + packet, + currentConsumerState.chainState.runningTimestamp + receiverUnbondingPeriod + ) + ), + receivedVscPackets: currentConsumerState.receivedVscPackets.prepend(packet) + } + val newConsumerStates = currentState.consumerStates.set(receiver, newConsumerState) + val newState = currentState.with( + "consumerStates", newConsumerStates + ) + Ok(newState) + } + } + + // receives a given packet on the provider. The arguments are the consumer chain that sent the packet, and the packet itself. + // To receive a packet, add it to the list of received maturations. + pure def recvPacketOnProvider(currentState: ProtocolState, sender: Chain, packet: VscMaturedPacket): Result = { + if (not(isRunningConsumer(sender, currentState.providerState))) { + Err("Sender is not currently a consumer - must have 'running' status!") + } else if (currentState.providerState.sentVscPacketsToConsumer.get(sender).head().id != packet.id) { + // the packet is not the oldest sentVscPacket, something went wrong + Err("Received maturation is not for the oldest sentVscPacket") + } else { + val currentReceivedMaturations = currentState.providerState.receivedMaturations + val newReceivedMaturations = currentReceivedMaturations.union(Set(packet)) + val newProviderState = currentState.providerState.with( + "receivedMaturations", newReceivedMaturations + ) + // prune the sentVscPacket + val newSentVscPacket = currentState.providerState.sentVscPacketsToConsumer.get(sender).tail() + val newState = currentState.with( + "providerState", + {...newProviderState, + sentVscPacketsToConsumer: currentState.providerState.sentVscPacketsToConsumer.set(sender, newSentVscPacket) + } + ) + + // prune assigned keys that are no longer needed + + // get the keys that should be pruned + pure val consumerAddrsToPrune = + currentState.providerState.consumerAddrsToPrune + .getOrElse(sender, Map()) + .getOrElse(packet.id, List()).toSet() + + // actually do the pruning + pure val senderValByConsAddr = + // for the sender chain, get the consAddrToValidator mapping + newState.providerState.consumerAddrToValidator.getOrElse(sender, Map()) + pure val newSenderValByConsAddr = + senderValByConsAddr.keys().filter( + // filter out the assigned keys that should be pruned + consAddr => not(consumerAddrsToPrune.contains(consAddr)) + ).mapBy( + // rebuild the map with the pruned keys removed + consAddr => senderValByConsAddr.get(consAddr) + ) + + // update the provider state + pure val newProviderState2 = newState.providerState.with( + // update the consumerAddrToValidator map to remove the pruned keys + "consumerAddrToValidator", + newState.providerState.consumerAddrToValidator.put(sender, newSenderValByConsAddr) + ).with( + // delete the consumer addresses to prune, since they are pruned now + "consumerAddrsToPrune", + newState.providerState.consumerAddrsToPrune.put( + sender, + newState.providerState.consumerAddrsToPrune.getOrElse(sender, Map()).put( + packet.id, + List() + ) + ) + ) + + // update the state + pure val newState2 = newState.with( + "providerState", newProviderState2 + ) + + Ok(newState2) + } + } + + // removes the oldest outstanding packet from the consumer. on-chain, this would happen when the packet is acknowledged. + // only the oldest packet can be removed, since we model ordered channels. + pure def removeOutstandingPacketFromConsumer(currentState: ProtocolState, sender: Chain): ProtocolState = { + val currentOutstandingPackets = currentState.consumerStates.get(sender).outstandingPacketsToProvider + val newOutstandingPackets = currentOutstandingPackets.tail() + val newConsumerState = currentState.consumerStates.get(sender).with( + "outstandingPacketsToProvider", newOutstandingPackets + ) + val newConsumerStates = currentState.consumerStates.set(sender, newConsumerState) + val newState = currentState.with( + "consumerStates", newConsumerStates + ) + newState + } + + // removes the oldest outstanding packet (to the given consumer) from the provider. + // on-chain, this would happen when the packet is acknowledged. + // only the oldest packet can be removed, since we model ordered channels. + pure def removeOutstandingPacketFromProvider(currentState: ProtocolState, receiver: Chain): ProtocolState = { + val currentOutstandingPackets = currentState.providerState.outstandingPacketsToConsumer.get(receiver) + val newOutstandingPackets = currentOutstandingPackets.tail() + val newProviderState = currentState.providerState.with( + "outstandingPacketsToConsumer", + currentState.providerState.outstandingPacketsToConsumer.set(receiver, newOutstandingPackets) + ) + val newState = currentState.with( + "providerState", newProviderState + ) + newState + } + + // Returns a ProtocolState where the current validator set on the provider is set to + // newValidatorSet. + pure def setProviderValidatorSet(currentState: ProtocolState, newValidatorSet: ValidatorSet): ProtocolState = { + pure val newChainState = currentState.providerState.chainState.with( + "currentValidatorSet", newValidatorSet + ) + currentState.with( + "providerState", + currentState.providerState.with( + "chainState", newChainState + ) + ) + } + + // Returns true if the given chain is currently a running consumer, false otherwise. + pure def isRunningConsumer(chain: Chain, providerState: ProviderState): bool = { + val status = providerState.consumerStatus.get(chain) + status == RUNNING + } + + // Returns the set of all consumer chains. + pure def getConsumers(providerState: ProviderState): Set[Chain] = providerState.consumerStatus.keys() + + // Returns the set of all consumer chains that currently have the status RUNNING. + pure def getRunningConsumers(providerState: ProviderState): Set[Chain] = { + providerState.consumerStatus.keys().filter( + chain => providerState.consumerStatus.get(chain) == RUNNING + ) + } + + // Returns the set of all consumer chains that currently have the status NOT_CONSUMER. + pure def getNonConsumers(providerState: ProviderState): Set[Chain] = { + providerState.consumerStatus.keys().filter( + chain => providerState.consumerStatus.get(chain) == NOT_CONSUMER + ) + } + + // Returns whether the consumer has timed out due to the vscTimeout, and an error message. + // If the second return is not equal to "", the first return should be ignored. + // If it is equal to "", the first return will be true if the consumer has timed out and should be dropped, + // or false otherwise. + pure def TimeoutDueToVscTimeout(currentState: ProtocolState, consumer: Chain, vscTimeout: Time): (bool, str) = + // check for errors: the consumer is not running + if (not(isRunningConsumer(consumer, currentState.providerState))) { + (false, "Consumer is not currently a consumer - must have 'running' status!") + } else { + val providerState = currentState.providerState + val consumerState: ConsumerState = currentState.consumerStates.get(consumer) + + // has a packet been sent on the provider more than vscTimeout ago, but we have not received an answer since then? + val sentVscPacketsToConsumer = providerState.sentVscPacketsToConsumer.getOrElse(consumer, List()) + if(sentVscPacketsToConsumer.length() > 0) { + val oldestSentVscPacket = sentVscPacketsToConsumer.head() // if length is 0, this is undefined, but we check for this before we use it + if(oldestSentVscPacket.sendingTime + vscTimeout < providerState.chainState.runningTimestamp) { + (true, "") + } else { + // no timeout yet, it has not been vscTimeout since that packet was sent + (false, "") + } + } else { + // no packet has been sent yet, so no timeout + (false, "") + } + } + + // Sets the top N values on the provider chain for the given consumer chains, + // taken as consumerAdditionMsgs = chains with the top N values. + // If a chain in the set is already present, the old value will be overwritten. + pure def SetTopNValues(providerState: ProviderState, consumers: Set[ConsumerAdditionMsg]): ProviderState = + providerState.with( + "topNByConsumer", + consumers.fold( + providerState.topNByConsumer, + (acc, consumer) => acc.put(consumer.chain, consumer.topN) + ) + ) + + // From a validator set, removes all validators with zero power. + pure def removeZeroPowers(valSet: ValidatorSet): ValidatorSet = + valSet.keys().fold( + Map(), + (acc, node) => + if (valSet.get(node) == 0) { + acc + } else { + acc.put(node, valSet.get(node)) + } + ) +} \ No newline at end of file diff --git a/tests/mbt/model/libraries/extraSpells.qnt b/tests/mbt/model/libraries/extraSpells.qnt index 9167bb4bcb..6ab4063ad3 100644 --- a/tests/mbt/model/libraries/extraSpells.qnt +++ b/tests/mbt/model/libraries/extraSpells.qnt @@ -139,6 +139,72 @@ module extraSpells { __set.fold(List(), (__l, __e) => __l.append(__e)) } + /// The type of orderings between comparable things + // Follows https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Ord.html#t:Ordering + // and we think there are likely benefits to using 3 constant values rather than the more + // common integer range in Apalache. + type Ordering = + | EQ + | LT + | GT + + /// Comparison of integers + pure def intCompare(__a: int, __b:int): Ordering = { + if (__a < __b) + { LT } + else if (__a > __b) + { GT } + else + { EQ } + } + + /// Assuming `__l` is sorted according to `__cmp`, returns a list with the element `__x` + /// inserted in order. + /// + /// If `__l` is not sorted, `__x` will be inserted after the first element less than + /// or equal to it. + /// + /// - @param __l a sorted list + /// - @param __x an element to be inserted + /// - @param __cmp an operator defining an `Ordering` of the elemnts of type `a` + /// - @returns a sorted list that includes `__x` + pure def sortedListInsert(__l: List[a], __x: a, __cmp: (a, a) => Ordering): List[a] = { + // We need to track whether __x has been inserted, and the accumulator for the new list + val __init = { is_inserted: false, acc: List() } + + val __result = __l.foldl(__init, (__state, __y) => + if (__state.is_inserted) + { ...__state, acc: __state.acc.append(__y) } + else + match __cmp(__x, __y) { + | GT => { ...__state, acc: __state.acc.append(__y) } + | _ => { is_inserted: true, acc: __state.acc.append(__x).append(__y) } + }) + + if (not(__result.is_inserted)) + // If __x was not inserted, it was GT than every other element, so it goes at the end + __result.acc.append(__x) + else + __result.acc + } + + run sortedListInsertTest = all { + assert(List().sortedListInsert(3, intCompare) == List(3)), + assert(List(1,2,4).sortedListInsert(3, intCompare) == List(1,2,3,4)), + assert(List(4,1,2).sortedListInsert(3, intCompare) == List(3,4,1,2)), + assert(List(1,2,3).sortedListInsert(4, intCompare) == List(1,2,3,4)), + } + + //// Returns a list of all elements of a set. + //// The ordering will be arbitrary. + //// + //// - @param __set a set + //// - @param __cmp an operator defining an `Ordering` of the elemnts of type `a` + //// - @returns a sorted list of all elements of __set + pure def toSortedList(__set: Set[a], __cmp: (a, a) => Ordering): List[a] = { + __set.fold(List(), (__l, __e) => __l.sortedListInsert(__e, __cmp)) + } + //// Returns a set of the elements in the list. //// //// - @param __list a list @@ -207,4 +273,59 @@ module extraSpells { assert(not(listForAll(List(1, 2, 3), __x => __x > 1))), assert(listForAll(List(), __x => __x > 0)), } + + /// Compute the sum of the values over all entries in a map. + /// + /// - @param myMap a map from keys to integers + /// - @returns the sum; when the map is empty, the sum is 0. + pure def mapValuesSum(myMap: a -> int): int = { + myMap.keys().fold(0, ((sum, i) => sum + myMap.get(i))) + } + + run mapValuesSumTest = all { + assert(Map().mapValuesSum() == 0), + assert(2.to(5).mapBy(i => i * 2).mapValuesSum() == 28), + assert(Map(2 -> -4, 4 -> 2).mapValuesSum() == -2), + } + + /// Returns a map of a subset of keys and values from a map, + // where only those keys are included for which the given __f + // returns true. + pure def mapFilter(__map: a -> b, __f: a => bool): a -> b = { + __map.keys().filter(e => __f(e)).mapBy(__k => __map.get(__k)) + } + + /// Compute the maximum of two integers. + /// + /// - @param __i first integer + /// - @param __j second integer + /// - @returns the maximum of __i and __j + pure def max(__i: int, __j: int): int = { + if (__i > __j) __i else __j + } + + run maxTest = all { + assert(max(3, 4) == 4), + assert(max(6, 3) == 6), + assert(max(10, 10) == 10), + assert(max(-3, -5) == -3), + assert(max(-5, -3) == -3), + } + + /// Compute the minimum of two integers. + /// + /// - @param __i first integer + /// - @param __j second integer + /// - @returns the minimum of __i and __j + pure def min(__i: int, __j: int): int = { + if (__i < __j) __i else __j + } + + run minTest = all { + assert(min(3, 4) == 3), + assert(min(6, 3) == 3), + assert(min(10, 10) == 10), + assert(min(-3, -5) == -5), + assert(min(-5, -3) == -5), + } } \ No newline at end of file diff --git a/tests/mbt/model/run_invariants.sh b/tests/mbt/model/run_invariants.sh new file mode 100755 index 0000000000..df22b97702 --- /dev/null +++ b/tests/mbt/model/run_invariants.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +quint test ccv_model.qnt +quint test ccv_test.qnt +quint run --invariant "all{ValidatorUpdatesArePropagatedInv,ValidatorSetHasExistedInv,SameVscPacketsInv,MatureOnTimeInv,EventuallyMatureOnProviderInv}" ccv_model.qnt --max-steps 200 --max-samples 200 +quint run --invariant "all{ValidatorUpdatesArePropagatedKeyAssignmentInv,ValidatorSetHasExistedKeyAssignmentInv,SameVscPacketsKeyAssignmentInv,MatureOnTimeInv,EventuallyMatureOnProviderInv,KeyAssignmentRulesInv}" ccv_model.qnt --step stepKeyAssignment --max-steps 200 --max-samples 200 \ No newline at end of file diff --git a/tests/mbt/run_invariants.sh b/tests/mbt/run_invariants.sh deleted file mode 100755 index 613664aeae..0000000000 --- a/tests/mbt/run_invariants.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -quint test ccv_model.qnt -quint test ccv_test.qnt -quint run --invariant "all{ValidatorUpdatesArePropagatedInv,ValidatorSetHasExistedInv,SameVscPacketsInv,MatureOnTimeInv,EventuallyMatureOnProviderInv}" ccv_model.qnt --max-steps 200 --max-samples 200 \ No newline at end of file From e0491ed8f705a9eb33a6c5e7dd110cb0bcf175de Mon Sep 17 00:00:00 2001 From: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:31:44 +0100 Subject: [PATCH 12/18] test: Ports key assignment to the driver on the PSS feature branch (#1628) * Port key assignment to MBT driver * Add comment and make var names clearer --- tests/mbt/driver/core.go | 15 ++- tests/mbt/driver/generate_more_traces.sh | 3 +- tests/mbt/driver/generate_traces.sh | 3 +- tests/mbt/driver/mbt_test.go | 129 ++++++++++++++++++----- tests/mbt/driver/stats.go | 2 + 5 files changed, 122 insertions(+), 30 deletions(-) diff --git a/tests/mbt/driver/core.go b/tests/mbt/driver/core.go index f9f1e12e05..66413bafe9 100644 --- a/tests/mbt/driver/core.go +++ b/tests/mbt/driver/core.go @@ -20,6 +20,7 @@ import ( abcitypes "github.com/cometbft/cometbft/abci/types" cmttypes "github.com/cometbft/cometbft/types" + "github.com/cometbft/cometbft/proto/tendermint/crypto" appConsumer "github.com/cosmos/interchain-security/v4/app/consumer" appProvider "github.com/cosmos/interchain-security/v4/app/provider" simibc "github.com/cosmos/interchain-security/v4/testutil/simibc" @@ -123,9 +124,13 @@ func (s *Driver) consumerPower(i int64, chain ChainId) (int64, error) { return v.Power, nil } +func (s *Driver) stakingValidator(i int64) (stakingtypes.Validator, bool) { + return s.providerStakingKeeper().GetValidator(s.ctx(PROVIDER), s.validator(i)) +} + // providerPower returns the power(=number of bonded tokens) of the i-th validator on the provider. func (s *Driver) providerPower(i int64) (int64, error) { - v, found := s.providerStakingKeeper().GetValidator(s.ctx(PROVIDER), s.validator(i)) + v, found := s.stakingValidator(i) if !found { return 0, fmt.Errorf("validator with id %v not found on provider", i) } else { @@ -370,6 +375,14 @@ func (s *Driver) setTime(chain ChainId, newTime time.Time) { testChain.App.BeginBlock(abcitypes.RequestBeginBlock{Header: testChain.CurrentHeader}) } +func (s *Driver) AssignKey(chain ChainId, valIndex int64, key crypto.PublicKey) error { + stakingVal, found := s.stakingValidator(valIndex) + if !found { + return fmt.Errorf("validator with id %v not found on provider", valIndex) + } + return s.providerKeeper().AssignConsumerKey(s.providerCtx(), string(chain), stakingVal, key) +} + // DeliverPacketToConsumer delivers a packet from the provider to the given consumer recipient. // It updates the client before delivering the packet. // Since the channel is ordered, the packet that is delivered is the first packet in the outbox. diff --git a/tests/mbt/driver/generate_more_traces.sh b/tests/mbt/driver/generate_more_traces.sh index 9af4da82e9..40589bb83b 100755 --- a/tests/mbt/driver/generate_more_traces.sh +++ b/tests/mbt/driver/generate_more_traces.sh @@ -9,4 +9,5 @@ go run ./... -modelPath=../model/ccv_boundeddrift.qnt -step stepBoundedDrift -in echo "Generating synced traces with maturations" go run ./... -modelPath=../model/ccv_sync.qnt -init initSync -step stepSync -invariant CanReceiveMaturations -traceFolder traces/sync_mat -numTraces 20 -numSteps 300 -numSamples 20 echo "Generating long synced traces without invariants" -go run ./... -modelPath=../model/ccv_sync.qnt -init initSync -step stepSync -traceFolder traces/sync_noinv -numTraces 20 -numSteps 500 -numSamples 1 \ No newline at end of file +go run ./... -modelPath=../model/ccv_sync.qnt -init initSync -step stepSync -traceFolder traces/sync_noinv -numTraces 20 -numSteps 500 -numSamples 1 +go run ./... -modelPath=../model/ccv_boundeddrift.qnt --step stepBoundedDriftKeyAssignment --traceFolder traces/bound_key -numTraces 20 -numSteps 100 -numSamples 20 \ No newline at end of file diff --git a/tests/mbt/driver/generate_traces.sh b/tests/mbt/driver/generate_traces.sh index ca0a6ba973..9f1134fb26 100755 --- a/tests/mbt/driver/generate_traces.sh +++ b/tests/mbt/driver/generate_traces.sh @@ -9,4 +9,5 @@ go run ./... -modelPath=../model/ccv_boundeddrift.qnt -step stepBoundedDrift -in echo "Generating synced traces with maturations" go run ./... -modelPath=../model/ccv_sync.qnt -init initSync -step stepSync -invariant CanReceiveMaturations -traceFolder traces/sync_mat -numTraces 1 -numSteps 300 -numSamples 20 echo "Generating long synced traces without invariants" -go run ./... -modelPath=../model/ccv_sync.qnt -init initSync -step stepSync -traceFolder traces/sync_noinv -numTraces 1 -numSteps 500 -numSamples 1 \ No newline at end of file +go run ./... -modelPath=../model/ccv_sync.qnt -init initSync -step stepSync -traceFolder traces/sync_noinv -numTraces 1 -numSteps 500 -numSamples 1 +go run ./... -modelPath=../model/ccv_boundeddrift.qnt --step stepBoundedDriftKeyAssignment --traceFolder traces/bound_key -numTraces 1 -numSteps 100 -numSamples 20 \ No newline at end of file diff --git a/tests/mbt/driver/mbt_test.go b/tests/mbt/driver/mbt_test.go index 78f9e7910f..e57aa00f10 100644 --- a/tests/mbt/driver/mbt_test.go +++ b/tests/mbt/driver/mbt_test.go @@ -15,10 +15,13 @@ import ( "github.com/kylelemons/godebug/pretty" "github.com/stretchr/testify/require" - sdktypes "github.com/cosmos/cosmos-sdk/types" - cmttypes "github.com/cometbft/cometbft/types" + tmencoding "github.com/cometbft/cometbft/crypto/encoding" + "github.com/cosmos/interchain-security/v4/testutil/integration" + + sdktypes "github.com/cosmos/cosmos-sdk/types" + providertypes "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ) @@ -69,6 +72,7 @@ func TestMBT(t *testing.T) { t.Logf("Number of sent packets: %v", stats.numSentPackets) t.Logf("Number of blocks: %v", stats.numBlocks) t.Logf("Number of transactions: %v", stats.numTxs) + t.Logf("Number of key assignments: %v", stats.numKeyAssignments) t.Logf("Average summed block time delta passed per trace: %v", stats.totalBlockTimePassedPerTrace/time.Duration(numTraces)) } @@ -117,6 +121,25 @@ func RunItfTrace(t *testing.T, path string) { t.Log("Chains are: ", chains) + // generate keys that can be assigned on consumers, according to the ConsumerAddresses in the trace + consumerAddressesExpr := params["ConsumerAddresses"].Value.(itf.ListExprType) + + _, _, consumerPrivVals, err := integration.CreateValidators(len(consumerAddressesExpr)) + require.NoError(t, err, "Error creating consumer signers") + + // consumerAddrNames are the human readable names of consumer addresses in the model + // "realAddrs" are the addresses of the consumer keys on chain + // these maps relate the consumerAddrNames to the priv validators (from which one can get the real address) + // and from the real ddresses to the consumerAddrNames to allow converting between the two easily + consumerAddrNamesToPrivVals := make(map[string]cmttypes.PrivValidator, len(consumerAddressesExpr)) + realAddrsToModelConsAddrs := make(map[string]string, len(consumerAddressesExpr)) + i := 0 + for address, privVal := range consumerPrivVals { + consumerAddrNamesToPrivVals[consumerAddressesExpr[i].Value.(string)] = privVal + realAddrsToModelConsAddrs[address] = consumerAddressesExpr[i].Value.(string) + i++ + } + // create params struct vscTimeout := time.Duration(params["VscTimeout"].Value.(int64)) * time.Second @@ -145,6 +168,15 @@ func RunItfTrace(t *testing.T, path string) { valSet, addressMap, signers, err := CreateValSet(initialValSet) require.NoError(t, err, "Error creating validator set") + // get the set of signers for consumers: the validator signers, plus signers for the assignable addresses + consumerSigners := make(map[string]cmttypes.PrivValidator, 0) + for consAddr, consPrivVal := range consumerPrivVals { + consumerSigners[consAddr] = consPrivVal + } + for consAddr, signer := range signers { + consumerSigners[consAddr] = signer + } + // get a slice of validators in the right order nodes := make([]*cmttypes.Validator, len(valNames)) for i, valName := range valNames { @@ -211,6 +243,10 @@ func RunItfTrace(t *testing.T, path string) { // and then increment the rest of the time runningConsumersBefore := driver.runningConsumers() driver.endAndBeginBlock("provider", 1*time.Nanosecond) + for _, consumer := range driver.runningConsumers() { + UpdateProviderClientOnConsumer(t, driver, consumer.ChainId) + } + driver.endAndBeginBlock("provider", time.Duration(timeAdvancement)*time.Second-1*time.Nanosecond) runningConsumersAfter := driver.runningConsumers() @@ -243,7 +279,7 @@ func RunItfTrace(t *testing.T, path string) { consumer.Value.(string), modelParams, driver.providerChain().Vals, - signers, + consumerSigners, nodes, valNames, driver.providerChain(), @@ -268,11 +304,8 @@ func RunItfTrace(t *testing.T, path string) { if len(consumersToStart) > 0 && consumer.ChainId == consumersToStart[len(consumersToStart)-1].Value.(string) { continue } - consumerChainId := consumer.ChainId - driver.path(ChainId(consumerChainId)).AddClientHeader(PROVIDER, driver.providerHeader()) - err := driver.path(ChainId(consumerChainId)).UpdateClient(consumerChainId, false) - require.True(t, err == nil, "Error updating client from %v on provider: %v", consumerChainId, err) + UpdateProviderClientOnConsumer(t, driver, consumer.ChainId) } case "EndAndBeginBlockForConsumer": @@ -286,13 +319,12 @@ func RunItfTrace(t *testing.T, path string) { _ = headerBefore driver.endAndBeginBlock(ChainId(consumerChain), 1*time.Nanosecond) + UpdateConsumerClientOnProvider(t, driver, consumerChain) + driver.endAndBeginBlock(ChainId(consumerChain), time.Duration(timeAdvancement)*time.Second-1*time.Nanosecond) // update the client on the provider - consumerHeader := driver.chain(ChainId(consumerChain)).LastHeader - driver.path(ChainId(consumerChain)).AddClientHeader(consumerChain, consumerHeader) - err := driver.path(ChainId(consumerChain)).UpdateClient(PROVIDER, false) - require.True(t, err == nil, "Error updating client from %v on provider: %v", consumerChain, err) + UpdateConsumerClientOnProvider(t, driver, consumerChain) case "DeliverVscPacket": consumerChain := lastAction["consumerChain"].Value.(string) @@ -328,8 +360,26 @@ func RunItfTrace(t *testing.T, path string) { expectError = false driver.DeliverPacketFromConsumer(ChainId(consumerChain), expectError) } - default: + case "KeyAssignment": + consumerChain := lastAction["consumerChain"].Value.(string) + node := lastAction["validator"].Value.(string) + consumerAddr := lastAction["consumerAddr"].Value.(string) + t.Log("KeyAssignment", consumerChain, node, consumerAddr) + stats.numKeyAssignments++ + + valIndex := getIndexOfString(node, valNames) + assignedPrivVal := consumerAddrNamesToPrivVals[consumerAddr] + assignedKey, err := assignedPrivVal.GetPubKey() + require.NoError(t, err, "Error getting pubkey") + + protoPubKey, err := tmencoding.PubKeyToProto(assignedKey) + require.NoError(t, err, "Error converting pubkey to proto") + + error := driver.AssignKey(ChainId(consumerChain), int64(valIndex), protoPubKey) + require.NoError(t, error, "Error assigning key") + + default: log.Fatalf("Error loading trace file %s, step %v: do not know action type %s", path, index, actionKind) } @@ -364,7 +414,7 @@ func RunItfTrace(t *testing.T, path string) { require.Equal(t, modelRunningConsumers, actualRunningConsumers, "Running consumers do not match") // check validator sets - provider current validator set should be the one from the staking keeper - CompareValidatorSets(t, driver, currentModelState, actualRunningConsumers) + CompareValidatorSets(t, driver, currentModelState, actualRunningConsumers, realAddrsToModelConsAddrs) // check times - sanity check that the block times match the ones from the model CompareTimes(driver, actualRunningConsumers, currentModelState, timeOffset) @@ -383,7 +433,27 @@ func RunItfTrace(t *testing.T, path string) { t.Log("🟢 Trace is ok!") } -func CompareValidatorSets(t *testing.T, driver *Driver, currentModelState map[string]itf.Expr, consumers []string) { +func UpdateProviderClientOnConsumer(t *testing.T, driver *Driver, consumerChainId string) { + driver.path(ChainId(consumerChainId)).AddClientHeader(PROVIDER, driver.providerHeader()) + err := driver.path(ChainId(consumerChainId)).UpdateClient(consumerChainId, false) + require.True(t, err == nil, "Error updating client from %v on provider: %v", consumerChainId, err) +} + +func UpdateConsumerClientOnProvider(t *testing.T, driver *Driver, consumerChain string) { + consumerHeader := driver.chain(ChainId(consumerChain)).LastHeader + driver.path(ChainId(consumerChain)).AddClientHeader(consumerChain, consumerHeader) + err := driver.path(ChainId(consumerChain)).UpdateClient(PROVIDER, false) + require.True(t, err == nil, "Error updating client from %v on provider: %v", consumerChain, err) +} + +func CompareValidatorSets( + t *testing.T, + driver *Driver, + currentModelState map[string]itf.Expr, + consumers []string, + // a map from real addresses to the names of those consumer addresses in the model + keyAddrsToModelConsAddrName map[string]string, +) { t.Helper() modelValSet := ValidatorSet(currentModelState, "provider") @@ -407,23 +477,28 @@ func CompareValidatorSets(t *testing.T, driver *Driver, currentModelState map[st pubkey, err := val.ConsPubKey() require.NoError(t, err, "Error getting pubkey") - consAddr := providertypes.NewConsumerConsAddress(sdktypes.ConsAddress(pubkey.Address().Bytes())) + consAddrModelName, ok := keyAddrsToModelConsAddrName[pubkey.Address().String()] + if ok { // the node has a key assigned, use the name of the consumer address in the model + consumerCurValSet[consAddrModelName] = val.Power + } else { // the node doesn't have a key assigned yet, get the validator moniker + consAddr := providertypes.NewConsumerConsAddress(sdktypes.ConsAddress(pubkey.Address().Bytes())) - // the consumer vals right now are CrossChainValidators, for which we don't know their mnemonic - // so we need to find the mnemonic of the consumer val now to enter it by name in the map + // the consumer vals right now are CrossChainValidators, for which we don't know their mnemonic + // so we need to find the mnemonic of the consumer val now to enter it by name in the map - // get the address on the provider that corresponds to the consumer address - providerConsAddr, found := driver.providerKeeper().GetValidatorByConsumerAddr(driver.providerCtx(), consumer, consAddr) - if !found { - providerConsAddr = providertypes.NewProviderConsAddress(consAddr.Address) - } + // get the address on the provider that corresponds to the consumer address + providerConsAddr, found := driver.providerKeeper().GetValidatorByConsumerAddr(driver.providerCtx(), consumer, consAddr) + if !found { + providerConsAddr = providertypes.NewProviderConsAddress(consAddr.Address) + } - // get the validator for that address on the provider - providerVal, found := driver.providerStakingKeeper().GetValidatorByConsAddr(driver.providerCtx(), providerConsAddr.Address) - require.True(t, found, "Error getting provider validator") + // get the validator for that address on the provider + providerVal, found := driver.providerStakingKeeper().GetValidatorByConsAddr(driver.providerCtx(), providerConsAddr.Address) + require.True(t, found, "Error getting provider validator") - // use the moniker of that validator - consumerCurValSet[providerVal.GetMoniker()] = val.Power + // use the moniker of that validator + consumerCurValSet[providerVal.GetMoniker()] = val.Power + } } require.NoError(t, CompareValSet(modelValSet, consumerCurValSet), "Validator sets do not match for consumer %v", consumer) } diff --git a/tests/mbt/driver/stats.go b/tests/mbt/driver/stats.go index 0d397571be..8b4c95a3dd 100644 --- a/tests/mbt/driver/stats.go +++ b/tests/mbt/driver/stats.go @@ -16,4 +16,6 @@ type Stats struct { numTxs int totalBlockTimePassedPerTrace time.Duration + + numKeyAssignments int } From 72a140710a01d544f393ad7e057ed317a3cc1314 Mon Sep 17 00:00:00 2001 From: insumity Date: Thu, 8 Feb 2024 15:57:10 +0100 Subject: [PATCH 13/18] feat!: automatically opt in validators that vote Yes on consumer addition proposals (#1629) * init commit * changed providerKeeper.GetProposedConsumerChain to return a bool * add logging mesages * one more log message * fix comment * added one more test case of NO vote and made tabular test --- testutil/keeper/mocks.go | 53 ++++++++++++---- x/ccv/provider/keeper/hooks.go | 56 ++++++++++++++++- x/ccv/provider/keeper/hooks_test.go | 92 +++++++++++++++++++++++++++- x/ccv/provider/keeper/keeper.go | 5 +- x/ccv/provider/keeper/keeper_test.go | 6 +- x/ccv/types/expected_keepers.go | 2 + 6 files changed, 194 insertions(+), 20 deletions(-) diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 5f9d9b2694..b183e8b377 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -473,18 +473,6 @@ func (mr *MockSlashingKeeperMockRecorder) GetValidatorSigningInfo(ctx, address i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorSigningInfo", reflect.TypeOf((*MockSlashingKeeper)(nil).GetValidatorSigningInfo), ctx, address) } -// SetValidatorSigningInfo mocks base method. -func (m *MockSlashingKeeper) SetValidatorSigningInfo(ctx types0.Context, address types0.ConsAddress, info types3.ValidatorSigningInfo) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetValidatorSigningInfo", ctx, address, info) -} - -// SetValidatorSigningInfo indicates an expected call of SetValidatorSigningInfo. -func (mr *MockSlashingKeeperMockRecorder) SetValidatorSigningInfo(ctx, address interface{}, info interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetValidatorSigningInfo", reflect.TypeOf((*MockSlashingKeeper)(nil).SetValidatorSigningInfo), ctx, address, info) -} - // IsTombstoned mocks base method. func (m *MockSlashingKeeper) IsTombstoned(arg0 types0.Context, arg1 types0.ConsAddress) bool { m.ctrl.T.Helper() @@ -511,6 +499,18 @@ func (mr *MockSlashingKeeperMockRecorder) JailUntil(arg0, arg1, arg2 interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "JailUntil", reflect.TypeOf((*MockSlashingKeeper)(nil).JailUntil), arg0, arg1, arg2) } +// SetValidatorSigningInfo mocks base method. +func (m *MockSlashingKeeper) SetValidatorSigningInfo(ctx types0.Context, address types0.ConsAddress, info types3.ValidatorSigningInfo) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetValidatorSigningInfo", ctx, address, info) +} + +// SetValidatorSigningInfo indicates an expected call of SetValidatorSigningInfo. +func (mr *MockSlashingKeeperMockRecorder) SetValidatorSigningInfo(ctx, address, info interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetValidatorSigningInfo", reflect.TypeOf((*MockSlashingKeeper)(nil).SetValidatorSigningInfo), ctx, address, info) +} + // SlashFractionDoubleSign mocks base method. func (m *MockSlashingKeeper) SlashFractionDoubleSign(ctx types0.Context) types0.Dec { m.ctrl.T.Helper() @@ -1217,3 +1217,32 @@ func (mr *MockGovKeeperMockRecorder) GetProposal(ctx, proposalID interface{}) *g mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProposal", reflect.TypeOf((*MockGovKeeper)(nil).GetProposal), ctx, proposalID) } + +// GetProposals mocks base method. +func (m *MockGovKeeper) GetProposals(ctx types0.Context) v1.Proposals { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProposals", ctx) + ret0, _ := ret[0].(v1.Proposals) + return ret0 +} + +// GetProposals indicates an expected call of GetProposals. +func (mr *MockGovKeeperMockRecorder) GetProposals(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProposals", reflect.TypeOf((*MockGovKeeper)(nil).GetProposals), ctx) +} + +// GetVote mocks base method. +func (m *MockGovKeeper) GetVote(ctx types0.Context, proposalID uint64, voterAddr types0.AccAddress) (v1.Vote, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVote", ctx, proposalID, voterAddr) + ret0, _ := ret[0].(v1.Vote) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetVote indicates an expected call of GetVote. +func (mr *MockGovKeeperMockRecorder) GetVote(ctx, proposalID, voterAddr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVote", reflect.TypeOf((*MockGovKeeper)(nil).GetVote), ctx, proposalID, voterAddr) +} diff --git a/x/ccv/provider/keeper/hooks.go b/x/ccv/provider/keeper/hooks.go index 88590a9875..fcf8339187 100644 --- a/x/ccv/provider/keeper/hooks.go +++ b/x/ccv/provider/keeper/hooks.go @@ -1,8 +1,8 @@ package keeper import ( + "cosmossdk.io/math" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" sdkgov "github.com/cosmos/cosmos-sdk/x/gov/types" v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" @@ -175,7 +175,61 @@ func (h Hooks) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64 func (h Hooks) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) { } +// AfterProposalVote opts in validators that vote YES (with 100% weight) on a `ConsumerAdditionProposal`. If a +// validator votes multiple times, only the last vote would be considered on whether the validator is opted in or not. func (h Hooks) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { + validator, found := h.k.stakingKeeper.GetValidator(ctx, voterAddr.Bytes()) + if !found { + return + } + + consAddr, err := validator.GetConsAddr() + if err != nil { + h.k.Logger(ctx).Error("could not extract validator's consensus address", + "error", err.Error(), + "validator acc addr", voterAddr, + ) + return + } + + chainID, found := h.k.GetProposedConsumerChain(ctx, proposalID) + if !found { + return + } + + vote, found := h.k.govKeeper.GetVote(ctx, proposalID, voterAddr) + if !found { + h.k.Logger(ctx).Error("could not find vote for validator", + "validator acc addr", voterAddr, + "proposalID", proposalID, + ) + return + } + + if len(vote.Options) == 1 && vote.Options[0].Option == v1.VoteOption_VOTE_OPTION_YES { + // only consider votes that vote YES with 100% weight + weight, err := sdk.NewDecFromStr(vote.Options[0].Weight) + if err != nil { + h.k.Logger(ctx).Error("could not extract decimal value from vote's weight", + "vote", vote, + "error", err.Error(), + ) + return + } + if !weight.Equal(math.LegacyNewDec(1)) { + h.k.Logger(ctx).Error("single vote does not have a weight of 1", + "vote", vote, + ) + return + } + + // in the validator is already to-be-opted in, the `SetToBeOptedIn` is a no-op + h.k.SetToBeOptedIn(ctx, chainID, providertypes.NewProviderConsAddress(consAddr)) + } else { + // if vote is not a YES vote with 100% weight, we delete the validator from to-be-opted in + // if the validator is not to-be-opted in, the `DeleteToBeOptedIn` is a no-op + h.k.DeleteToBeOptedIn(ctx, chainID, providertypes.NewProviderConsAddress(consAddr)) + } } func (h Hooks) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) { diff --git a/x/ccv/provider/keeper/hooks_test.go b/x/ccv/provider/keeper/hooks_test.go index f4aad8d441..140bb8a4ce 100644 --- a/x/ccv/provider/keeper/hooks_test.go +++ b/x/ccv/provider/keeper/hooks_test.go @@ -1,6 +1,10 @@ package keeper_test import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" "testing" "time" @@ -123,11 +127,13 @@ func TestAfterPropSubmissionAndVotingPeriodEnded(t *testing.T) { k.Hooks().AfterProposalSubmission(ctx, prop.Id) // verify that the proposal ID is created - require.NotEmpty(t, k.GetProposedConsumerChain(ctx, prop.Id)) + _, found := k.GetProposedConsumerChain(ctx, prop.Id) + require.True(t, found) k.Hooks().AfterProposalVotingPeriodEnded(ctx, prop.Id) // verify that the proposal ID is deleted - require.Empty(t, k.GetProposedConsumerChain(ctx, prop.Id)) + _, found = k.GetProposedConsumerChain(ctx, prop.Id) + require.False(t, found) } func TestGetConsumerAdditionLegacyPropFromProp(t *testing.T) { @@ -223,3 +229,85 @@ func TestGetConsumerAdditionLegacyPropFromProp(t *testing.T) { }) } } + +func TestAfterProposalVoteWithYesVote(t *testing.T) { + k, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + providerConsPubKey := ed25519.GenPrivKeyFromSecret([]byte{1}).PubKey() + pkAny, _ := codectypes.NewAnyWithValue(providerConsPubKey) + providerAddr := types.NewProviderConsAddress(providerConsPubKey.Address().Bytes()) + + options := []*v1.WeightedVoteOption{{Option: v1.OptionYes, Weight: "1"}} + k.SetProposedConsumerChain(ctx, "chainID", 1) + + gomock.InOrder( + mocks.MockStakingKeeper.EXPECT().GetValidator(ctx, gomock.Any()).Return( + stakingtypes.Validator{ConsensusPubkey: pkAny}, true), + mocks.MockGovKeeper.EXPECT().GetVote(ctx, gomock.Any(), gomock.Any()).Return( + v1.Vote{ProposalId: 1, Voter: "voter", Options: options}, true, + ), + ) + + require.False(t, k.IsToBeOptedIn(ctx, "chainID", providerAddr)) + k.Hooks().AfterProposalVote(ctx, 1, sdk.AccAddress{}) + require.True(t, k.IsToBeOptedIn(ctx, "chainID", providerAddr)) +} + +func TestAfterProposalVoteWithNoVote(t *testing.T) { + testCases := []struct { + name string + options []*v1.WeightedVoteOption + setup func(sdk.Context, []*v1.WeightedVoteOption, testkeeper.MockedKeepers, *codectypes.Any) + }{ + { + "Weighted vote with 100% NO", + []*v1.WeightedVoteOption{{Option: v1.OptionNo, Weight: "1"}}, + func(ctx sdk.Context, options []*v1.WeightedVoteOption, + mocks testkeeper.MockedKeepers, pubKey *codectypes.Any) { + gomock.InOrder( + mocks.MockStakingKeeper.EXPECT().GetValidator(ctx, gomock.Any()).Return( + stakingtypes.Validator{ConsensusPubkey: pubKey}, true), + mocks.MockGovKeeper.EXPECT().GetVote(ctx, gomock.Any(), gomock.Any()).Return( + v1.Vote{ProposalId: 1, Voter: "voter", Options: options}, true, + ), + ) + }, + }, + { + "Weighted vote with 99.9% YES and 0.1% NO", + []*v1.WeightedVoteOption{{Option: v1.OptionYes, Weight: "0.999"}, {Option: v1.OptionNo, Weight: "0.001"}}, + func(ctx sdk.Context, options []*v1.WeightedVoteOption, + mocks testkeeper.MockedKeepers, pubKey *codectypes.Any) { + gomock.InOrder( + mocks.MockStakingKeeper.EXPECT().GetValidator(ctx, gomock.Any()).Return( + stakingtypes.Validator{ConsensusPubkey: pubKey}, true), + mocks.MockGovKeeper.EXPECT().GetVote(ctx, gomock.Any(), gomock.Any()).Return( + v1.Vote{ProposalId: 1, Voter: "voter", Options: options}, true, + ), + ) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + k, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + providerConsPubKey := ed25519.GenPrivKeyFromSecret([]byte{1}).PubKey() + pkAny, _ := codectypes.NewAnyWithValue(providerConsPubKey) + providerAddr := types.NewProviderConsAddress(providerConsPubKey.Address().Bytes()) + + k.SetProposedConsumerChain(ctx, "chainID", 1) + + tc.setup(ctx, tc.options, mocks, pkAny) + + // set the validator to-be-opted in first to assert that a NO vote removes the validator from to-be-opted in + k.SetToBeOptedIn(ctx, "chainID", providerAddr) + require.True(t, k.IsToBeOptedIn(ctx, "chainID", providerAddr)) + k.Hooks().AfterProposalVote(ctx, 1, sdk.AccAddress{}) + require.False(t, k.IsToBeOptedIn(ctx, "chainID", providerAddr)) + }) + } +} diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index ea052ec5fd..42bcb12ace 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -186,9 +186,10 @@ func (k Keeper) SetProposedConsumerChain(ctx sdk.Context, chainID string, propos } // GetProposedConsumerChain returns the proposed chainID for the given consumerAddition proposal ID. -func (k Keeper) GetProposedConsumerChain(ctx sdk.Context, proposalID uint64) string { +func (k Keeper) GetProposedConsumerChain(ctx sdk.Context, proposalID uint64) (string, bool) { store := ctx.KVStore(k.storeKey) - return string(store.Get(types.ProposedConsumerChainKey(proposalID))) + consumerChain := store.Get(types.ProposedConsumerChainKey(proposalID)) + return string(consumerChain), consumerChain != nil } // DeleteProposedConsumerChainInStore deletes the consumer chainID from store diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index 6eb28675ba..35380a67aa 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -552,7 +552,7 @@ func TestSetProposedConsumerChains(t *testing.T) { for _, test := range tests { providerKeeper.SetProposedConsumerChain(ctx, test.chainID, test.proposalID) - cID := providerKeeper.GetProposedConsumerChain(ctx, test.proposalID) + cID, _ := providerKeeper.GetProposedConsumerChain(ctx, test.proposalID) require.Equal(t, cID, test.chainID) } } @@ -574,9 +574,9 @@ func TestDeleteProposedConsumerChainInStore(t *testing.T) { for _, test := range tests { providerKeeper.SetProposedConsumerChain(ctx, test.chainID, test.proposalID) providerKeeper.DeleteProposedConsumerChainInStore(ctx, test.deleteProposalID) - cID := providerKeeper.GetProposedConsumerChain(ctx, test.proposalID) + cID, found := providerKeeper.GetProposedConsumerChain(ctx, test.proposalID) if test.ok { - require.Equal(t, cID, "") + require.False(t, found) } else { require.Equal(t, cID, test.chainID) } diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index a2ef7ab465..df531e09ca 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -151,4 +151,6 @@ type ScopedKeeper interface { type GovKeeper interface { GetProposal(ctx sdk.Context, proposalID uint64) (v1.Proposal, bool) + GetProposals(ctx sdk.Context) (proposals v1.Proposals) + GetVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) (vote v1.Vote, found bool) } From ed2b1aaf5ded50078d8aa4e70711544579efe6b8 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:43:27 +0100 Subject: [PATCH 14/18] test: Add driver for PSS (#1636) * Port key assignment to MBT driver * Add PSS trace generation * Add PSS trace gen to longer trace gen * Start handling top N parameter for new consumers * Finish merge * Add handling for optin/optout steps * Remove expected error from OptIn, which should not error * set top N parameter during path setup * Add comment to setup.go --- tests/mbt/driver/core.go | 28 ++++++++++++++++++ tests/mbt/driver/generate_more_traces.sh | 3 +- tests/mbt/driver/generate_traces.sh | 3 +- tests/mbt/driver/mbt_test.go | 37 +++++++++++++++++++++--- tests/mbt/driver/setup.go | 12 ++++++-- tests/mbt/model/ccv_pss_model.qnt | 3 +- 6 files changed, 76 insertions(+), 10 deletions(-) diff --git a/tests/mbt/driver/core.go b/tests/mbt/driver/core.go index 66413bafe9..2a01d45bfc 100644 --- a/tests/mbt/driver/core.go +++ b/tests/mbt/driver/core.go @@ -383,6 +383,34 @@ func (s *Driver) AssignKey(chain ChainId, valIndex int64, key crypto.PublicKey) return s.providerKeeper().AssignConsumerKey(s.providerCtx(), string(chain), stakingVal, key) } +// Opts the given validator into the given consumer chain on the provider. +func (s *Driver) OptIn(chain ChainId, valIndex int64) error { + stakingVal, found := s.stakingValidator(valIndex) + if !found { + return fmt.Errorf("validator with id %v not found on provider", valIndex) + } + consPubKey, err := stakingVal.ConsPubKey() + if err != nil { + return err + } + consAddr := sdk.GetConsAddress(consPubKey) + return s.providerKeeper().HandleOptIn(s.providerCtx(), string(chain), providertypes.NewProviderConsAddress(consAddr), nil) +} + +// Opts the given validator out of the given consumer chain on the provider. +func (s *Driver) OptOut(chain ChainId, valIndex int64) error { + stakingVal, found := s.stakingValidator(valIndex) + if !found { + return fmt.Errorf("validator with id %v not found on provider", valIndex) + } + consPubKey, err := stakingVal.ConsPubKey() + if err != nil { + return err + } + consAddr := sdk.GetConsAddress(consPubKey) + return s.providerKeeper().HandleOptOut(s.providerCtx(), string(chain), providertypes.NewProviderConsAddress(consAddr)) +} + // DeliverPacketToConsumer delivers a packet from the provider to the given consumer recipient. // It updates the client before delivering the packet. // Since the channel is ordered, the packet that is delivered is the first packet in the outbox. diff --git a/tests/mbt/driver/generate_more_traces.sh b/tests/mbt/driver/generate_more_traces.sh index 40589bb83b..905507a108 100755 --- a/tests/mbt/driver/generate_more_traces.sh +++ b/tests/mbt/driver/generate_more_traces.sh @@ -10,4 +10,5 @@ echo "Generating synced traces with maturations" go run ./... -modelPath=../model/ccv_sync.qnt -init initSync -step stepSync -invariant CanReceiveMaturations -traceFolder traces/sync_mat -numTraces 20 -numSteps 300 -numSamples 20 echo "Generating long synced traces without invariants" go run ./... -modelPath=../model/ccv_sync.qnt -init initSync -step stepSync -traceFolder traces/sync_noinv -numTraces 20 -numSteps 500 -numSamples 1 -go run ./... -modelPath=../model/ccv_boundeddrift.qnt --step stepBoundedDriftKeyAssignment --traceFolder traces/bound_key -numTraces 20 -numSteps 100 -numSamples 20 \ No newline at end of file +go run ./... -modelPath=../model/ccv_boundeddrift.qnt --step stepBoundedDriftKeyAssignment --traceFolder traces/bound_key -numTraces 20 -numSteps 100 -numSamples 20 +go run ./... -modelPath=../model/ccv_boundeddrift.qnt --step stepBoundedDriftKeyAndPSS --traceFolder traces/bound_pss -numTraces 20 -numSteps 100 -numSamples 20 diff --git a/tests/mbt/driver/generate_traces.sh b/tests/mbt/driver/generate_traces.sh index 9f1134fb26..f898b27e2e 100755 --- a/tests/mbt/driver/generate_traces.sh +++ b/tests/mbt/driver/generate_traces.sh @@ -10,4 +10,5 @@ echo "Generating synced traces with maturations" go run ./... -modelPath=../model/ccv_sync.qnt -init initSync -step stepSync -invariant CanReceiveMaturations -traceFolder traces/sync_mat -numTraces 1 -numSteps 300 -numSamples 20 echo "Generating long synced traces without invariants" go run ./... -modelPath=../model/ccv_sync.qnt -init initSync -step stepSync -traceFolder traces/sync_noinv -numTraces 1 -numSteps 500 -numSamples 1 -go run ./... -modelPath=../model/ccv_boundeddrift.qnt --step stepBoundedDriftKeyAssignment --traceFolder traces/bound_key -numTraces 1 -numSteps 100 -numSamples 20 \ No newline at end of file +go run ./... -modelPath=../model/ccv_boundeddrift.qnt --step stepBoundedDriftKeyAssignment --traceFolder traces/bound_key -numTraces 1 -numSteps 100 -numSamples 20 +go run ./... -modelPath=../model/ccv_boundeddrift.qnt --step stepBoundedDriftKeyAndPSS --traceFolder traces/bound_pss -numTraces 1 -numSteps 100 -numSamples 20 diff --git a/tests/mbt/driver/mbt_test.go b/tests/mbt/driver/mbt_test.go index e57aa00f10..54135c575d 100644 --- a/tests/mbt/driver/mbt_test.go +++ b/tests/mbt/driver/mbt_test.go @@ -275,14 +275,17 @@ func RunItfTrace(t *testing.T, path string) { driver.coordinator.CurrentTime = driver.runningTime("provider") // start consumers for _, consumer := range consumersToStart { + chainId := consumer.Value.(itf.MapExprType)["chain"].Value.(string) + topN := consumer.Value.(itf.MapExprType)["topN"].Value.(int64) driver.setupConsumer( - consumer.Value.(string), + chainId, modelParams, driver.providerChain().Vals, consumerSigners, nodes, valNames, driver.providerChain(), + topN, ) } @@ -301,7 +304,8 @@ func RunItfTrace(t *testing.T, path string) { // unless it was the last consumer to be started, in which case it already has the header // as we called driver.setupConsumer for _, consumer := range driver.runningConsumers() { - if len(consumersToStart) > 0 && consumer.ChainId == consumersToStart[len(consumersToStart)-1].Value.(string) { + if len(consumersToStart) > 0 && + consumer.ChainId == consumersToStart[len(consumersToStart)-1].Value.(itf.MapExprType)["chain"].Value.(string) { continue } @@ -376,8 +380,33 @@ func RunItfTrace(t *testing.T, path string) { protoPubKey, err := tmencoding.PubKeyToProto(assignedKey) require.NoError(t, err, "Error converting pubkey to proto") - error := driver.AssignKey(ChainId(consumerChain), int64(valIndex), protoPubKey) - require.NoError(t, error, "Error assigning key") + err = driver.AssignKey(ChainId(consumerChain), int64(valIndex), protoPubKey) + require.NoError(t, err, "Error assigning key") + case "OptIn": + consumerChain := lastAction["consumerChain"].Value.(string) + validator := lastAction["validator"].Value.(string) + t.Log("OptIn", consumerChain, validator) + + valIndex := getIndexOfString(validator, valNames) + + err := driver.OptIn(ChainId(consumerChain), int64(valIndex)) + require.NoError(t, err, "Error opting in") + + case "OptOut": + consumerChain := lastAction["consumerChain"].Value.(string) + validator := lastAction["validator"].Value.(string) + expectedError := lastAction["expectedError"].Value.(string) + t.Log("OptOut", consumerChain, validator, expectedError) + + valIndex := getIndexOfString(validator, valNames) + + err := driver.OptOut(ChainId(consumerChain), int64(valIndex)) + + if expectedError != "" { + require.Error(t, err, "Expected an error: %v", expectedError) + } else { + require.NoError(t, err, "Error opting out, but expected no error") + } default: log.Fatalf("Error loading trace file %s, step %v: do not know action type %s", diff --git a/tests/mbt/driver/setup.go b/tests/mbt/driver/setup.go index 83fa6e0669..ada9bddca3 100644 --- a/tests/mbt/driver/setup.go +++ b/tests/mbt/driver/setup.go @@ -310,7 +310,7 @@ func newChain( // Creates a path for cross-chain validation from the consumer to the provider and configures the channel config of the endpoints // as well as the clients. // this function stops when there is an initialized, ready-to-relay channel between the provider and consumer. -func (s *Driver) ConfigureNewPath(consumerChain, providerChain *ibctesting.TestChain, params ModelParams) *ibctesting.Path { +func (s *Driver) ConfigureNewPath(consumerChain, providerChain *ibctesting.TestChain, params ModelParams, topN uint32) *ibctesting.Path { consumerChainId := ChainId(consumerChain.ChainID) path := ibctesting.NewPath(consumerChain, providerChain) @@ -362,6 +362,11 @@ func (s *Driver) ConfigureNewPath(consumerChain, providerChain *ibctesting.TestC consumerGenesisForProvider) require.NoError(s.t, err, "Error setting consumer genesis on provider for chain %v", consumerChain.ChainID) + // set the top N percentage + // needs to be done before the provider queues the first vsc packet to the consumer + // TODO: might be able to move this into setupConsumer, need to test once more logic is here + s.providerKeeper().SetTopN(providerChain.GetContext(), consumerChain.ChainID, topN) + // Client ID is set in InitGenesis and we treat it as a black box. So // must query it to use it with the endpoint. clientID, _ := s.consumerKeeper(consumerChainId).GetProviderClientID(s.ctx(consumerChainId)) @@ -433,9 +438,12 @@ func (s *Driver) setupConsumer( nodes []*cmttypes.Validator, // the list of nodes, even ones that have no voting power initially valNames []string, providerChain *ibctesting.TestChain, + topN int64, ) { s.t.Logf("Starting consumer %v", chain) + // TODO: reuse the partial set computation logic to compute the initial validator set + // for top N chains initValUpdates := cmttypes.TM2PB.ValidatorUpdates(valSet) // start consumer chains @@ -443,7 +451,7 @@ func (s *Driver) setupConsumer( consumerChain := newChain(s.t, params, s.coordinator, icstestingutils.ConsumerAppIniter(initValUpdates), chain, valSet, signers, nodes, valNames) s.coordinator.Chains[chain] = consumerChain - path := s.ConfigureNewPath(consumerChain, providerChain, params) + path := s.ConfigureNewPath(consumerChain, providerChain, params, uint32(topN)) s.simibcs[ChainId(chain)] = simibc.MakeRelayedPath(s.t, path) } diff --git a/tests/mbt/model/ccv_pss_model.qnt b/tests/mbt/model/ccv_pss_model.qnt index 76c4873b43..d2911a0989 100644 --- a/tests/mbt/model/ccv_pss_model.qnt +++ b/tests/mbt/model/ccv_pss_model.qnt @@ -22,8 +22,7 @@ module ccv_pss_model { ...emptyAction, kind: "OptIn", consumerChain: consumer, - validator: validator, - expectedError: res.error + validator: validator } ), params' = params, From 3359ad27da4da309093628ed777c8db9bffe1822 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 21 Feb 2024 17:16:15 +0100 Subject: [PATCH 15/18] feat!: add PSS reward distribution spike (#1632) * PSS reward distribution * "add optin mapping to test" * Update app/provider/app.go Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * docs * add TODO * fix Dos vector in IBCMiddlewarea * add reformat * fix DOS issue and make integration tests pass * doc * add integration test * doc * Compute total vp per consumer * add comments * remove opt-in comments and add TODOs * format * Update x/ccv/provider/keeper/distribution.go Co-authored-by: insumity * add UT + doc * Update tests/integration/distribution.go Co-authored-by: insumity * Update tests/integration/distribution.go Co-authored-by: insumity * nits * Update x/ccv/provider/ibc_middleware.go Co-authored-by: Marius Poke * add panics in IBC Middleware ICS4wrapper funcs * address comments --------- Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> Co-authored-by: insumity Co-authored-by: Marius Poke --- app/provider/app.go | 14 +- .../ccv/provider/v1/provider.proto | 13 + tests/integration/distribution.go | 506 +++++++++++++++++- testutil/integration/debug_test.go | 16 + testutil/keeper/mocks.go | 261 +++++++-- x/ccv/provider/ibc_middleware.go | 240 +++++++++ x/ccv/provider/ibc_middleware_test.go | 76 +++ x/ccv/provider/keeper/distribution.go | 239 ++++++++- x/ccv/provider/keeper/distribution_test.go | 238 ++++++++ x/ccv/provider/module.go | 4 +- x/ccv/provider/types/consumer.go | 8 + x/ccv/provider/types/keys.go | 9 + x/ccv/provider/types/keys_test.go | 1 + x/ccv/provider/types/provider.pb.go | 405 ++++++++++---- x/ccv/types/expected_keepers.go | 5 + 15 files changed, 1845 insertions(+), 190 deletions(-) create mode 100644 x/ccv/provider/ibc_middleware.go create mode 100644 x/ccv/provider/ibc_middleware_test.go create mode 100644 x/ccv/provider/keeper/distribution_test.go diff --git a/app/provider/app.go b/app/provider/app.go index 7527756d45..9cf45fe7e3 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -102,6 +102,7 @@ import ( appencoding "github.com/cosmos/interchain-security/v4/app/encoding" testutil "github.com/cosmos/interchain-security/v4/testutil/integration" + "github.com/cosmos/interchain-security/v4/x/ccv/provider" ibcprovider "github.com/cosmos/interchain-security/v4/x/ccv/provider" ibcproviderclient "github.com/cosmos/interchain-security/v4/x/ccv/provider/client" ibcproviderkeeper "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" @@ -470,12 +471,15 @@ func New( app.BankKeeper, scopedTransferKeeper, ) - transferModule := transfer.NewAppModule(app.TransferKeeper) - ibcmodule := transfer.NewIBCModule(app.TransferKeeper) + + // Add an IBC middleware callback to track the consumer rewards + var transferStack porttypes.IBCModule + transferStack = transfer.NewIBCModule(app.TransferKeeper) + transferStack = provider.NewIBCMiddleware(transferStack, app.ProviderKeeper) // create static IBC router, add transfer route, then set and seal it ibcRouter := porttypes.NewRouter() - ibcRouter.AddRoute(ibctransfertypes.ModuleName, ibcmodule) + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) ibcRouter.AddRoute(providertypes.ModuleName, providerModule) app.IBCKeeper.SetRouter(ibcRouter) @@ -514,7 +518,7 @@ func New( evidence.NewAppModule(app.EvidenceKeeper), ibc.NewAppModule(app.IBCKeeper), params.NewAppModule(app.ParamsKeeper), - transferModule, + transfer.NewAppModule(app.TransferKeeper), providerModule, ) @@ -610,7 +614,7 @@ func New( params.NewAppModule(app.ParamsKeeper), evidence.NewAppModule(app.EvidenceKeeper), ibc.NewAppModule(app.IBCKeeper), - transferModule, + transfer.NewAppModule(app.TransferKeeper), ) app.sm.RegisterStoreDecoders() diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index 38be02864f..b4a512e36d 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -13,6 +13,8 @@ import "ibc/lightclients/tendermint/v1/tendermint.proto"; import "tendermint/crypto/keys.proto"; import "cosmos/evidence/v1beta1/evidence.proto"; import "cosmos/base/v1beta1/coin.proto"; +import "amino/amino.proto"; + // // Note any type defined in this file is ONLY used internally to the provider CCV module. @@ -300,3 +302,14 @@ message ConsumerAddrsToPrune { uint64 vsc_id = 2; AddressList consumer_addrs = 3; } + +// ConsumerRewardsAllocation stores the rewards allocated by a consumer chain +// to the consumer rewards pool. It is used to allocate the tokens to the consumer +// opted-in validators and the community pool during BeginBlock. +message ConsumerRewardsAllocation { + repeated cosmos.base.v1beta1.DecCoin rewards = 1 [ + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins" + ]; +} diff --git a/tests/integration/distribution.go b/tests/integration/distribution.go index 25cbcb3132..f06c6f9482 100644 --- a/tests/integration/distribution.go +++ b/tests/integration/distribution.go @@ -3,14 +3,22 @@ package integration import ( "strings" + "cosmossdk.io/math" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/libs/bytes" + "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" icstestingutils "github.com/cosmos/interchain-security/v4/testutil/integration" consumerkeeper "github.com/cosmos/interchain-security/v4/x/ccv/consumer/keeper" consumertypes "github.com/cosmos/interchain-security/v4/x/ccv/consumer/types" + providerkeeper "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" providertypes "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ccv "github.com/cosmos/interchain-security/v4/x/ccv/types" ) @@ -93,25 +101,41 @@ func (s *CCVTestSuite) TestRewardsDistribution() { s.Require().Greater(ibcCoinIndex, -1) // Check that the coins got into the ConsumerRewardsPool - s.Require().True(rewardCoins[ibcCoinIndex].Amount.Equal(providerExpectedRewards[0].Amount)) + s.Require().Equal(rewardCoins[ibcCoinIndex].Amount, (providerExpectedRewards[0].Amount)) // 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)) + s.Require().Equal(rewardCoins[ibcCoinIndex].Amount, (providerExpectedRewards[0].Amount)) // 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() + // Refill the consumer fee pool + err = consumerBankKeeper.SendCoinsFromAccountToModule(s.consumerCtx(), s.consumerChain.SenderAccount.GetAddress(), authtypes.FeeCollectorName, fees) + s.Require().NoError(err) + + // pass two blocks + s.consumerChain.NextBlock() + s.consumerChain.NextBlock() + + // transfer rewards from consumer to provider + relayAllCommittedPackets(s, s.consumerChain, s.transferPath, transfertypes.PortID, s.transferPath.EndpointA.ChannelID, 1) + + // check that the consumer rewards allocation are empty since relayAllCommittedPackets call BeginBlock + rewardsAlloc := s.providerApp.GetProviderKeeper().GetConsumerRewardsAllocation(s.providerCtx(), s.consumerChain.ChainID) + s.Require().Empty(rewardsAlloc.Rewards) - // Check that the reward pool has no more coins because they were transferred to the fee pool + // Check that the reward pool still has the first coins transferred that were never allocated rewardCoins = providerBankKeeper.GetAllBalances(s.providerCtx(), rewardPool) - s.Require().Equal(0, len(rewardCoins)) + s.Require().Equal(rewardCoins[ibcCoinIndex].Amount, (providerExpectedRewards[0].Amount)) // check that the fee pool has the expected amount of coins + // Note that all rewards are allocated to the community pool since + // BeginBlock is called without the validators' votes in ibctesting. + // See NextBlock() in https://github.com/cosmos/ibc-go/blob/release/v7.3.x/testing/chain.go#L281 communityCoins := s.providerApp.GetTestDistributionKeeper().GetFeePoolCommunityCoins(s.providerCtx()) - s.Require().True(communityCoins[ibcCoinIndex].Amount.Equal(sdk.NewDecCoinFromCoin(providerExpectedRewards[0]).Amount)) + s.Require().Equal(communityCoins[ibcCoinIndex].Amount, (sdk.NewDecCoinFromCoin(providerExpectedRewards[0]).Amount)) } // TestSendRewardsRetries tests that failed reward transmissions are retried every BlocksPerDistributionTransmission blocks @@ -454,6 +478,397 @@ func (s *CCVTestSuite) TestSendRewardsToProvider() { } } +// TestIBCTransferMiddleware tests the logic of the IBC transfer OnRecvPacket callback +func (s *CCVTestSuite) TestIBCTransferMiddleware() { + + var ( + data ibctransfertypes.FungibleTokenPacketData + packet channeltypes.Packet + getIBCDenom func(string, string) string + ) + + testCases := []struct { + name string + setup func(sdk.Context, *providerkeeper.Keeper, icstestingutils.TestBankKeeper) + rewardsAllocated bool + expErr bool + }{ + { + "invalid IBC packet", + func(sdk.Context, *providerkeeper.Keeper, icstestingutils.TestBankKeeper) { + packet = channeltypes.Packet{} + }, + false, + true, + }, + { + "IBC packet sender isn't a consumer chain", + func(ctx sdk.Context, keeper *providerkeeper.Keeper, bankKeeper icstestingutils.TestBankKeeper) { + // make the sender consumer chain impossible to identify + packet.DestinationChannel = "CorruptedChannelId" + }, + false, + false, + }, + { + "IBC Transfer recipient is not the consumer rewards pool address", + func(ctx sdk.Context, keeper *providerkeeper.Keeper, bankKeeper icstestingutils.TestBankKeeper) { + data.Receiver = "cosmos149lw9fktlqfed3zt8ah48r5czmsug5s7kw77u9" // random acct address + packet.Data = data.GetBytes() + }, + false, + false, + }, + { + "IBC Transfer coin denom isn't registered", + func(ctx sdk.Context, keeper *providerkeeper.Keeper, bankKeeper icstestingutils.TestBankKeeper) {}, + false, + false, + }, + { + "successful token transfer to empty pool", + func(ctx sdk.Context, keeper *providerkeeper.Keeper, bankKeeper icstestingutils.TestBankKeeper) { + keeper.SetConsumerRewardDenom( + s.providerCtx(), + getIBCDenom(packet.DestinationPort, packet.DestinationChannel), + ) + }, + true, + false, + }, + { + "successful token transfer to filled pool", + func(ctx sdk.Context, keeper *providerkeeper.Keeper, bankKeeper icstestingutils.TestBankKeeper) { + keeper.SetConsumerRewardDenom( + ctx, + getIBCDenom(packet.DestinationPort, packet.DestinationChannel), + ) + + // fill consumer reward pool + bankKeeper.SendCoinsFromAccountToModule( + ctx, + s.providerChain.SenderAccount.GetAddress(), + providertypes.ConsumerRewardsPool, + sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(100_000))), + ) + // update consumer allocation + keeper.SetConsumerRewardsAllocation( + ctx, + s.consumerChain.ChainID, + providertypes.ConsumerRewardsAllocation{ + Rewards: sdk.NewDecCoins(sdk.NewDecCoin(sdk.DefaultBondDenom, math.NewInt(100_000))), + }, + ) + }, + true, + false, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + s.SetupCCVChannel(s.path) + s.SetupTransferChannel() + + providerKeeper := s.providerApp.GetProviderKeeper() + bankKeeper := s.providerApp.GetTestBankKeeper() + amount := sdk.NewInt(100) + + data = types.NewFungibleTokenPacketData( // can be explicitly changed in setup + sdk.DefaultBondDenom, + amount.String(), + authtypes.NewModuleAddress(consumertypes.ConsumerToSendToProviderName).String(), + providerKeeper.GetConsumerRewardsPoolAddressStr(s.providerCtx()), + "", + ) + + packet = channeltypes.NewPacket( // can be explicitly changed in setup + data.GetBytes(), + uint64(1), + s.transferPath.EndpointA.ChannelConfig.PortID, + s.transferPath.EndpointA.ChannelID, + s.transferPath.EndpointB.ChannelConfig.PortID, + s.transferPath.EndpointB.ChannelID, + clienttypes.NewHeight(1, 100), + 0, + ) + + providerKeeper.SetConsumerRewardDenom(s.providerCtx(), + transfertypes.GetPrefixedDenom( + packet.DestinationPort, + packet.DestinationChannel, + sdk.DefaultBondDenom, + ), + ) + + getIBCDenom = func(dstPort, dstChannel string) string { + return transfertypes.ParseDenomTrace( + transfertypes.GetPrefixedDenom( + packet.DestinationPort, + packet.DestinationChannel, + sdk.DefaultBondDenom, + ), + ).IBCDenom() + } + + tc.setup(s.providerCtx(), &providerKeeper, bankKeeper) + + cbs, ok := s.providerChain.App.GetIBCKeeper().Router.GetRoute(ibctransfertypes.ModuleName) + s.Require().True(ok) + + // save the IBC transfer rewards transferred + rewardsPoolBalance := bankKeeper.GetAllBalances(s.providerCtx(), sdk.MustAccAddressFromBech32(data.Receiver)) + + // save the consumer's rewards allocated + consumerRewardsAllocations := providerKeeper.GetConsumerRewardsAllocation(s.providerCtx(), s.consumerChain.ChainID) + + // execute middleware OnRecvPacket logic + ack := cbs.OnRecvPacket(s.providerCtx(), packet, sdk.AccAddress{}) + + // compute expected rewards with provider denom + expRewards := sdk.Coin{ + Amount: amount, + Denom: getIBCDenom(packet.DestinationPort, packet.DestinationChannel), + } + + // compute the balance and allocation difference + rewardsTransferred := bankKeeper.GetAllBalances(s.providerCtx(), sdk.MustAccAddressFromBech32(data.Receiver)). + Sub(rewardsPoolBalance...) + rewardsAllocated := providerKeeper.GetConsumerRewardsAllocation(s.providerCtx(), s.consumerChain.ChainID). + Rewards.Sub(consumerRewardsAllocations.Rewards) + + if !tc.expErr { + s.Require().True(ack.Success()) + // verify that the consumer rewards pool received the IBC coins + s.Require().Equal(rewardsTransferred, sdk.Coins{expRewards}) + + if tc.rewardsAllocated { + // check the data receiver address is set to the consumer rewards pool address + s.Require().Equal(data.GetReceiver(), providerKeeper.GetConsumerRewardsPoolAddressStr(s.providerCtx())) + + // verify that consumer rewards allocation is updated + s.Require().Equal(rewardsAllocated, sdk.NewDecCoinsFromCoins(expRewards)) + } else { + // verify that consumer rewards aren't allocated + s.Require().Empty(rewardsAllocated) + } + } else { + s.Require().False(ack.Success()) + } + }) + } +} + +// TestAllocateTokens is a happy-path test of the consumer rewards pool allocation +// to opted-in validators and the community pool +func (s *CCVTestSuite) TestAllocateTokens() { + // set up channel and delegate some tokens in order for validator set update to be sent to the consumer chain + s.SetupAllCCVChannels() + providerKeeper := s.providerApp.GetProviderKeeper() + bankKeeper := s.providerApp.GetTestBankKeeper() + distributionKeeper := s.providerApp.GetTestDistributionKeeper() + + totalRewards := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))} + + // fund consumer rewards pool + bankKeeper.SendCoinsFromAccountToModule( + s.providerCtx(), + s.providerChain.SenderAccount.GetAddress(), + providertypes.ConsumerRewardsPool, + totalRewards, + ) + + // Allocate rewards evenly between consumers + rewardsPerConsumer := totalRewards.QuoInt(math.NewInt(int64(len(s.consumerBundles)))) + for chainID := range s.consumerBundles { + // update consumer allocation + providerKeeper.SetConsumerRewardsAllocation( + s.providerCtx(), + chainID, + providertypes.ConsumerRewardsAllocation{ + Rewards: sdk.NewDecCoinsFromCoins(rewardsPerConsumer...), + }, + ) + } + + // Iterate over the validators and + // store their current voting power and outstanding rewards + lastValOutRewards := map[string]sdk.DecCoins{} + votes := []abci.VoteInfo{} + for _, val := range s.providerChain.Vals.Validators { + votes = append(votes, + abci.VoteInfo{ + Validator: abci.Validator{Address: val.Address, Power: val.VotingPower}, + SignedLastBlock: true, + }, + ) + + valRewards := distributionKeeper.GetValidatorOutstandingRewards(s.providerCtx(), sdk.ValAddress(val.Address)) + lastValOutRewards[sdk.ValAddress(val.Address).String()] = valRewards.Rewards + } + + // store community pool balance + lastCommPool := distributionKeeper.GetFeePoolCommunityCoins(s.providerCtx()) + + // execute BeginBlock to trigger the token allocation + providerKeeper.BeginBlockRD( + s.providerCtx(), + abci.RequestBeginBlock{ + LastCommitInfo: abci.CommitInfo{ + Votes: votes, + }, + }, + ) + + valNum := len(s.providerChain.Vals.Validators) + consuNum := len(s.consumerBundles) + + // compute the expected validators token allocation by subtracting the community tax + rewardsPerConsumerDec := sdk.NewDecCoinsFromCoins(rewardsPerConsumer...) + communityTax := distributionKeeper.GetCommunityTax(s.providerCtx()) + validatorsExpRewards := rewardsPerConsumerDec. + MulDecTruncate(math.LegacyOneDec().Sub(communityTax)). + // multiply by the number of consumers since all the validators opted in + MulDec(sdk.NewDec(int64(consuNum))) + perValExpReward := validatorsExpRewards.QuoDec(sdk.NewDec(int64(valNum))) + + // verify the validator tokens allocation + // note all validators have the same voting power to keep things simple + for _, val := range s.providerChain.Vals.Validators { + valReward := distributionKeeper.GetValidatorOutstandingRewards(s.providerCtx(), sdk.ValAddress(val.Address)) + s.Require().Equal( + valReward.Rewards, + lastValOutRewards[sdk.ValAddress(val.Address).String()].Add(perValExpReward...), + ) + } + + commPoolExpRewards := sdk.NewDecCoinsFromCoins(totalRewards...).Sub(validatorsExpRewards) + currCommPool := distributionKeeper.GetFeePoolCommunityCoins(s.providerCtx()) + + s.Require().Equal(currCommPool, (lastCommPool.Add(commPoolExpRewards...))) +} + +// TestAllocateTokens is a unit-test for TransferConsumerRewardsToDistributionModule() +// but is written as an integration test to avoid excessive mocking. +func (s *CCVTestSuite) TransferConsumerRewardsToDistributionModule() { + testCases := []struct { + name string + rewardsPool sdk.Coins + rewardsAlloc sdk.DecCoins + expErr bool + }{ + { + "empty consumer rewards pool", + sdk.Coins{}, + sdk.DecCoins{}, + false, + }, + { + "empty consumer allocation", + sdk.Coins{ + sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)), + }, + sdk.DecCoins{}, + false, + }, + { + "equal consumer rewards pool and allocation", + sdk.Coins{ + sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)), + }, + sdk.DecCoins{ + sdk.NewDecCoin(sdk.DefaultBondDenom, sdk.NewInt(100)), + }, + false, + }, + { + "less consumer rewards than allocation", + sdk.Coins{ + sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(90)), + }, + sdk.DecCoins{ + sdk.NewDecCoin(sdk.DefaultBondDenom, sdk.NewInt(100)), + }, + true, + }, + { + "remaining consumer rewards allocation", + sdk.Coins{ + sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)), + }, + sdk.DecCoins{ + sdk.DecCoin{ + Denom: sdk.DefaultBondDenom, + Amount: sdk.NewDecWithPrec(995, 1), + }, + }, + false, + }, + } + + providerKeeper := s.providerApp.GetProviderKeeper() + bankKeeper := s.providerApp.GetTestBankKeeper() + distributionKeeper := s.providerApp.GetTestDistributionKeeper() + + chainID := s.consumerChain.ChainID + + for _, tc := range testCases { + s.Run(tc.name, func() { + ctx, _ := s.providerCtx().CacheContext() + // fund consumer rewards pool + bankKeeper.SendCoinsFromAccountToModule( + ctx, + s.providerChain.SenderAccount.GetAddress(), + providertypes.ConsumerRewardsPool, + tc.rewardsPool, + ) + + // update consumer rewars allocation + providerKeeper.SetConsumerRewardsAllocation( + ctx, + chainID, + providertypes.ConsumerRewardsAllocation{ + Rewards: tc.rewardsAlloc, + }, + ) + + // store pool balance + oldPool := bankKeeper.GetAllBalances( + ctx, + distributionKeeper.GetDistributionAccount(ctx).GetAddress(), + ) + + // transfer consumer rewars to distribution module + coinsTransferred, err := providerKeeper.TransferConsumerRewardsToDistributionModule( + ctx, + chainID, + ) + if tc.expErr { + s.Require().Error(err) + return + } + + // check remaining consumer rewards allocation + expCoinTransferred, expRemaining := tc.rewardsAlloc.TruncateDecimal() + s.Require().Equal(expCoinTransferred, coinsTransferred) + + s.Require().Equal( + expRemaining, + providerKeeper.GetConsumerRewardsAllocation(ctx, chainID).Rewards, + ) + + // check updated consuemer rewards pool balance + newPool := bankKeeper.GetAllBalances( + ctx, + distributionKeeper.GetDistributionAccount(ctx).GetAddress(), + ) + + s.Require().Equal(newPool.Sub(oldPool...), coinsTransferred) + }) + } +} + // getEscrowBalance gets the current balances in the escrow account holding the transferred tokens to the provider func (s *CCVTestSuite) getEscrowBalance() sdk.Coins { consumerBankKeeper := s.consumerApp.GetTestBankKeeper() @@ -485,3 +900,82 @@ func (s *CCVTestSuite) prepareRewardDist() { blocksToGo := bpdt - blocksSinceLastTrans s.coordinator.CommitNBlocks(s.consumerChain, uint64(blocksToGo)) } + +func (s *CCVTestSuite) TestAllocateTokensToValidator() { + + providerkeepr := s.providerApp.GetProviderKeeper() + + chainID := "consumer" + + validators := []bytes.HexBytes{ + s.providerChain.Vals.Validators[0].Address, + s.providerChain.Vals.Validators[1].Address, + } + + votes := []abci.VoteInfo{ + {Validator: abci.Validator{Address: validators[0], Power: 1}}, + {Validator: abci.Validator{Address: validators[1], Power: 1}}, + } + + testCases := []struct { + name string + votes []abci.VoteInfo + tokens sdk.DecCoins + expCoinTransferred sdk.DecCoins + }{ + { + name: "reward tokens are empty", + }, + { + name: "total voting power is zero", + tokens: sdk.DecCoins{sdk.NewDecCoin("uatom", math.NewInt(100_000))}, + }, + { + name: "expect all tokens to be allocated to a single validator", + votes: []abci.VoteInfo{votes[0]}, + tokens: sdk.DecCoins{sdk.NewDecCoin("uatom", math.NewInt(100_000))}, + expCoinTransferred: sdk.DecCoins{sdk.NewDecCoin("uatom", math.NewInt(100_000))}, + }, + { + name: "expect tokens to be allocated evenly between validators", + votes: []abci.VoteInfo{votes[0], votes[1]}, + tokens: sdk.DecCoins{sdk.NewDecCoin("uatom", math.NewInt(555_555))}, + expCoinTransferred: sdk.DecCoins{sdk.NewDecCoin("uatom", math.NewInt(555_555))}, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + // TODO: opt validators in and verify + // that rewards are solely allocated to them + + ctx, _ := s.providerCtx().CacheContext() + + // allocate tokens + res := providerkeepr.AllocateTokensToConsumerValidators( + ctx, + chainID, + tc.votes, + tc.tokens, + ) + + // check that the expect result is returned + s.Require().Equal(tc.expCoinTransferred, res) + + if !tc.expCoinTransferred.Empty() { + // rewards are expected to be allocated evenly between validators + rewardsPerVal := tc.expCoinTransferred.QuoDec(sdk.NewDec(int64(len(tc.votes)))) + + // check that the rewards are allocated to validators as expected + for _, v := range tc.votes { + rewards := s.providerApp.GetTestDistributionKeeper().GetValidatorOutstandingRewards( + ctx, + sdk.ValAddress(v.Validator.Address), + ) + s.Require().Equal(rewardsPerVal, rewards.Rewards) + } + } + + }) + } +} diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index 77d460f05f..c370e8e701 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -283,3 +283,19 @@ func TestHandleConsumerDoubleVotingSlashesUndelegationsAndRelegations(t *testing func TestSlashRetries(t *testing.T) { runCCVTestByName(t, "TestSlashRetries") } + +func TestIBCTransferMiddleware(t *testing.T) { + runCCVTestByName(t, "TestIBCTransferMiddleware") +} + +func TestAllocateTokens(t *testing.T) { + runCCVTestByName(t, "TestAllocateTokens") +} + +func TestTransferConsumerRewardsToDistributionModule(t *testing.T) { + runCCVTestByName(t, "TransferConsumerRewardsToDistributionModule") +} + +func TestAllocateTokensToValidator(t *testing.T) { + runCCVTestByName(t, "TestAllocateTokensToValidator") +} diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index b183e8b377..a192765ec9 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -14,13 +14,14 @@ 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/distribution/types" v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - 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" + 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" exported "github.com/cosmos/ibc-go/v7/modules/core/exported" gomock "github.com/golang/mock/gomock" ) @@ -63,10 +64,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) types4.DelegationI { +func (m *MockStakingKeeper) Delegation(ctx types0.Context, addr types0.AccAddress, valAddr types0.ValAddress) types5.DelegationI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Delegation", ctx, addr, valAddr) - ret0, _ := ret[0].(types4.DelegationI) + ret0, _ := ret[0].(types5.DelegationI) return ret0 } @@ -105,10 +106,10 @@ func (mr *MockStakingKeeperMockRecorder) GetLastValidatorPower(ctx, operator int } // GetLastValidators mocks base method. -func (m *MockStakingKeeper) GetLastValidators(ctx types0.Context) []types4.Validator { +func (m *MockStakingKeeper) GetLastValidators(ctx types0.Context) []types5.Validator { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetLastValidators", ctx) - ret0, _ := ret[0].([]types4.Validator) + ret0, _ := ret[0].([]types5.Validator) return ret0 } @@ -119,10 +120,10 @@ func (mr *MockStakingKeeperMockRecorder) GetLastValidators(ctx interface{}) *gom } // GetRedelegationsFromSrcValidator mocks base method. -func (m *MockStakingKeeper) GetRedelegationsFromSrcValidator(ctx types0.Context, valAddr types0.ValAddress) []types4.Redelegation { +func (m *MockStakingKeeper) GetRedelegationsFromSrcValidator(ctx types0.Context, valAddr types0.ValAddress) []types5.Redelegation { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRedelegationsFromSrcValidator", ctx, valAddr) - ret0, _ := ret[0].([]types4.Redelegation) + ret0, _ := ret[0].([]types5.Redelegation) return ret0 } @@ -133,10 +134,10 @@ func (mr *MockStakingKeeperMockRecorder) GetRedelegationsFromSrcValidator(ctx, v } // GetUnbondingDelegationsFromValidator mocks base method. -func (m *MockStakingKeeper) GetUnbondingDelegationsFromValidator(ctx types0.Context, valAddr types0.ValAddress) []types4.UnbondingDelegation { +func (m *MockStakingKeeper) GetUnbondingDelegationsFromValidator(ctx types0.Context, valAddr types0.ValAddress) []types5.UnbondingDelegation { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetUnbondingDelegationsFromValidator", ctx, valAddr) - ret0, _ := ret[0].([]types4.UnbondingDelegation) + ret0, _ := ret[0].([]types5.UnbondingDelegation) return ret0 } @@ -147,10 +148,10 @@ func (mr *MockStakingKeeperMockRecorder) GetUnbondingDelegationsFromValidator(ct } // GetUnbondingType mocks base method. -func (m *MockStakingKeeper) GetUnbondingType(ctx types0.Context, id uint64) (types4.UnbondingType, bool) { +func (m *MockStakingKeeper) GetUnbondingType(ctx types0.Context, id uint64) (types5.UnbondingType, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetUnbondingType", ctx, id) - ret0, _ := ret[0].(types4.UnbondingType) + ret0, _ := ret[0].(types5.UnbondingType) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -162,10 +163,10 @@ func (mr *MockStakingKeeperMockRecorder) GetUnbondingType(ctx, id interface{}) * } // GetValidator mocks base method. -func (m *MockStakingKeeper) GetValidator(ctx types0.Context, addr types0.ValAddress) (types4.Validator, bool) { +func (m *MockStakingKeeper) GetValidator(ctx types0.Context, addr types0.ValAddress) (types5.Validator, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidator", ctx, addr) - ret0, _ := ret[0].(types4.Validator) + ret0, _ := ret[0].(types5.Validator) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -177,10 +178,10 @@ func (mr *MockStakingKeeperMockRecorder) GetValidator(ctx, addr interface{}) *go } // GetValidatorByConsAddr mocks base method. -func (m *MockStakingKeeper) GetValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) (types4.Validator, bool) { +func (m *MockStakingKeeper) GetValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) (types5.Validator, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidatorByConsAddr", ctx, consAddr) - ret0, _ := ret[0].(types4.Validator) + ret0, _ := ret[0].(types5.Validator) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -232,7 +233,7 @@ func (mr *MockStakingKeeperMockRecorder) IterateLastValidatorPowers(ctx, cb inte } // IterateValidators mocks base method. -func (m *MockStakingKeeper) IterateValidators(ctx types0.Context, f func(int64, types4.ValidatorI) bool) { +func (m *MockStakingKeeper) IterateValidators(ctx types0.Context, f func(int64, types5.ValidatorI) bool) { m.ctrl.T.Helper() m.ctrl.Call(m, "IterateValidators", ctx, f) } @@ -312,7 +313,7 @@ func (mr *MockStakingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4 inte } // SlashRedelegation mocks base method. -func (m *MockStakingKeeper) SlashRedelegation(arg0 types0.Context, arg1 types4.Validator, arg2 types4.Redelegation, arg3 int64, arg4 types0.Dec) math.Int { +func (m *MockStakingKeeper) SlashRedelegation(arg0 types0.Context, arg1 types5.Validator, arg2 types5.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) @@ -326,7 +327,7 @@ func (mr *MockStakingKeeperMockRecorder) SlashRedelegation(arg0, arg1, arg2, arg } // SlashUnbondingDelegation mocks base method. -func (m *MockStakingKeeper) SlashUnbondingDelegation(arg0 types0.Context, arg1 types4.UnbondingDelegation, arg2 int64, arg3 types0.Dec) math.Int { +func (m *MockStakingKeeper) SlashUnbondingDelegation(arg0 types0.Context, arg1 types5.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) @@ -340,7 +341,7 @@ func (mr *MockStakingKeeperMockRecorder) SlashUnbondingDelegation(arg0, arg1, ar } // 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 { +func (m *MockStakingKeeper) SlashWithInfractionReason(arg0 types0.Context, arg1 types0.ConsAddress, arg2, arg3 int64, arg4 types0.Dec, arg5 types5.Infraction) math.Int { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SlashWithInfractionReason", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(math.Int) @@ -394,10 +395,10 @@ func (mr *MockStakingKeeperMockRecorder) Unjail(ctx, addr interface{}) *gomock.C } // Validator mocks base method. -func (m *MockStakingKeeper) Validator(ctx types0.Context, addr types0.ValAddress) types4.ValidatorI { +func (m *MockStakingKeeper) Validator(ctx types0.Context, addr types0.ValAddress) types5.ValidatorI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Validator", ctx, addr) - ret0, _ := ret[0].(types4.ValidatorI) + ret0, _ := ret[0].(types5.ValidatorI) return ret0 } @@ -408,10 +409,10 @@ func (mr *MockStakingKeeperMockRecorder) Validator(ctx, addr interface{}) *gomoc } // ValidatorByConsAddr mocks base method. -func (m *MockStakingKeeper) ValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) types4.ValidatorI { +func (m *MockStakingKeeper) ValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) types5.ValidatorI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ValidatorByConsAddr", ctx, consAddr) - ret0, _ := ret[0].(types4.ValidatorI) + ret0, _ := ret[0].(types5.ValidatorI) return ret0 } @@ -459,10 +460,10 @@ func (mr *MockSlashingKeeperMockRecorder) DowntimeJailDuration(arg0 interface{}) } // GetValidatorSigningInfo mocks base method. -func (m *MockSlashingKeeper) GetValidatorSigningInfo(ctx types0.Context, address types0.ConsAddress) (types3.ValidatorSigningInfo, bool) { +func (m *MockSlashingKeeper) GetValidatorSigningInfo(ctx types0.Context, address types0.ConsAddress) (types4.ValidatorSigningInfo, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidatorSigningInfo", ctx, address) - ret0, _ := ret[0].(types3.ValidatorSigningInfo) + ret0, _ := ret[0].(types4.ValidatorSigningInfo) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -500,7 +501,7 @@ func (mr *MockSlashingKeeperMockRecorder) JailUntil(arg0, arg1, arg2 interface{} } // SetValidatorSigningInfo mocks base method. -func (m *MockSlashingKeeper) SetValidatorSigningInfo(ctx types0.Context, address types0.ConsAddress, info types3.ValidatorSigningInfo) { +func (m *MockSlashingKeeper) SetValidatorSigningInfo(ctx types0.Context, address types0.ConsAddress, info types4.ValidatorSigningInfo) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetValidatorSigningInfo", ctx, address, info) } @@ -589,10 +590,10 @@ func (mr *MockChannelKeeperMockRecorder) ChanCloseInit(ctx, portID, channelID, c } // GetChannel mocks base method. -func (m *MockChannelKeeper) GetChannel(ctx types0.Context, srcPort, srcChan string) (types8.Channel, bool) { +func (m *MockChannelKeeper) GetChannel(ctx types0.Context, srcPort, srcChan string) (types9.Channel, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetChannel", ctx, srcPort, srcChan) - ret0, _ := ret[0].(types8.Channel) + ret0, _ := ret[0].(types9.Channel) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -635,7 +636,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 types6.Height, timeoutTimestamp uint64, data []byte) (uint64, error) { +func (m *MockChannelKeeper) SendPacket(ctx types0.Context, chanCap *types2.Capability, sourcePort, sourceChannel string, timeoutHeight types7.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) @@ -724,10 +725,10 @@ func (m *MockConnectionKeeper) EXPECT() *MockConnectionKeeperMockRecorder { } // GetConnection mocks base method. -func (m *MockConnectionKeeper) GetConnection(ctx types0.Context, connectionID string) (types7.ConnectionEnd, bool) { +func (m *MockConnectionKeeper) GetConnection(ctx types0.Context, connectionID string) (types8.ConnectionEnd, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetConnection", ctx, connectionID) - ret0, _ := ret[0].(types7.ConnectionEnd) + ret0, _ := ret[0].(types8.ConnectionEnd) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -885,6 +886,18 @@ func (m *MockDistributionKeeper) EXPECT() *MockDistributionKeeperMockRecorder { return m.recorder } +// AllocateTokensToValidator mocks base method. +func (m *MockDistributionKeeper) AllocateTokensToValidator(ctx types0.Context, validator types5.ValidatorI, reward types0.DecCoins) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AllocateTokensToValidator", ctx, validator, reward) +} + +// AllocateTokensToValidator indicates an expected call of AllocateTokensToValidator. +func (mr *MockDistributionKeeperMockRecorder) AllocateTokensToValidator(ctx, validator, reward interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllocateTokensToValidator", reflect.TypeOf((*MockDistributionKeeper)(nil).AllocateTokensToValidator), ctx, validator, reward) +} + // FundCommunityPool mocks base method. func (m *MockDistributionKeeper) FundCommunityPool(ctx types0.Context, amount types0.Coins, sender types0.AccAddress) error { m.ctrl.T.Helper() @@ -899,6 +912,46 @@ func (mr *MockDistributionKeeperMockRecorder) FundCommunityPool(ctx, amount, sen return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FundCommunityPool", reflect.TypeOf((*MockDistributionKeeper)(nil).FundCommunityPool), ctx, amount, sender) } +// GetCommunityTax mocks base method. +func (m *MockDistributionKeeper) GetCommunityTax(ctx types0.Context) math.LegacyDec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCommunityTax", ctx) + ret0, _ := ret[0].(math.LegacyDec) + return ret0 +} + +// GetCommunityTax indicates an expected call of GetCommunityTax. +func (mr *MockDistributionKeeperMockRecorder) GetCommunityTax(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCommunityTax", reflect.TypeOf((*MockDistributionKeeper)(nil).GetCommunityTax), ctx) +} + +// GetFeePool mocks base method. +func (m *MockDistributionKeeper) GetFeePool(ctx types0.Context) types3.FeePool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFeePool", ctx) + ret0, _ := ret[0].(types3.FeePool) + return ret0 +} + +// GetFeePool indicates an expected call of GetFeePool. +func (mr *MockDistributionKeeperMockRecorder) GetFeePool(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFeePool", reflect.TypeOf((*MockDistributionKeeper)(nil).GetFeePool), ctx) +} + +// SetFeePool mocks base method. +func (m *MockDistributionKeeper) SetFeePool(ctx types0.Context, feePool types3.FeePool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetFeePool", ctx, feePool) +} + +// SetFeePool indicates an expected call of SetFeePool. +func (mr *MockDistributionKeeperMockRecorder) SetFeePool(ctx, feePool interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFeePool", reflect.TypeOf((*MockDistributionKeeper)(nil).SetFeePool), ctx, feePool) +} + // MockConsumerHooks is a mock of ConsumerHooks interface. type MockConsumerHooks struct { ctrl *gomock.Controller @@ -1061,11 +1114,139 @@ func (m *MockIBCTransferKeeper) EXPECT() *MockIBCTransferKeeperMockRecorder { return m.recorder } +// OnAcknowledgementPacket mocks base method. +func (m *MockIBCTransferKeeper) OnAcknowledgementPacket(ctx types0.Context, packet types9.Packet, acknowledgement []byte, relayer types0.AccAddress) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnAcknowledgementPacket", ctx, packet, acknowledgement, relayer) + ret0, _ := ret[0].(error) + return ret0 +} + +// OnAcknowledgementPacket indicates an expected call of OnAcknowledgementPacket. +func (mr *MockIBCTransferKeeperMockRecorder) OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAcknowledgementPacket", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnAcknowledgementPacket), ctx, packet, acknowledgement, relayer) +} + +// OnChanCloseConfirm mocks base method. +func (m *MockIBCTransferKeeper) OnChanCloseConfirm(ctx types0.Context, portID, channelID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnChanCloseConfirm", ctx, portID, channelID) + ret0, _ := ret[0].(error) + return ret0 +} + +// OnChanCloseConfirm indicates an expected call of OnChanCloseConfirm. +func (mr *MockIBCTransferKeeperMockRecorder) OnChanCloseConfirm(ctx, portID, channelID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanCloseConfirm", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanCloseConfirm), ctx, portID, channelID) +} + +// OnChanCloseInit mocks base method. +func (m *MockIBCTransferKeeper) OnChanCloseInit(ctx types0.Context, portID, channelID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnChanCloseInit", ctx, portID, channelID) + ret0, _ := ret[0].(error) + return ret0 +} + +// OnChanCloseInit indicates an expected call of OnChanCloseInit. +func (mr *MockIBCTransferKeeperMockRecorder) OnChanCloseInit(ctx, portID, channelID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanCloseInit", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanCloseInit), ctx, portID, channelID) +} + +// OnChanOpenAck mocks base method. +func (m *MockIBCTransferKeeper) OnChanOpenAck(ctx types0.Context, portID, channelID, counterpartyChannelID, counterpartyVersion string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnChanOpenAck", ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + ret0, _ := ret[0].(error) + return ret0 +} + +// OnChanOpenAck indicates an expected call of OnChanOpenAck. +func (mr *MockIBCTransferKeeperMockRecorder) OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanOpenAck", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanOpenAck), ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) +} + +// OnChanOpenConfirm mocks base method. +func (m *MockIBCTransferKeeper) OnChanOpenConfirm(ctx types0.Context, portID, channelID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnChanOpenConfirm", ctx, portID, channelID) + ret0, _ := ret[0].(error) + return ret0 +} + +// OnChanOpenConfirm indicates an expected call of OnChanOpenConfirm. +func (mr *MockIBCTransferKeeperMockRecorder) OnChanOpenConfirm(ctx, portID, channelID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanOpenConfirm", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanOpenConfirm), ctx, portID, channelID) +} + +// OnChanOpenInit mocks base method. +func (m *MockIBCTransferKeeper) OnChanOpenInit(ctx types0.Context, order types9.Order, connectionHops []string, portID, channelID string, channelCap *types2.Capability, counterparty types9.Counterparty, version string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnChanOpenInit", ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OnChanOpenInit indicates an expected call of OnChanOpenInit. +func (mr *MockIBCTransferKeeperMockRecorder) OnChanOpenInit(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanOpenInit", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanOpenInit), ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version) +} + +// OnChanOpenTry mocks base method. +func (m *MockIBCTransferKeeper) OnChanOpenTry(ctx types0.Context, order types9.Order, connectionHops []string, portID, channelID string, channelCap *types2.Capability, counterparty types9.Counterparty, counterpartyVersion string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnChanOpenTry", ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OnChanOpenTry indicates an expected call of OnChanOpenTry. +func (mr *MockIBCTransferKeeperMockRecorder) OnChanOpenTry(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanOpenTry", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanOpenTry), ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion) +} + +// OnRecvPacket mocks base method. +func (m *MockIBCTransferKeeper) OnRecvPacket(ctx types0.Context, packet types9.Packet, relayer types0.AccAddress) exported.Acknowledgement { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnRecvPacket", ctx, packet, relayer) + ret0, _ := ret[0].(exported.Acknowledgement) + return ret0 +} + +// OnRecvPacket indicates an expected call of OnRecvPacket. +func (mr *MockIBCTransferKeeperMockRecorder) OnRecvPacket(ctx, packet, relayer interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnRecvPacket", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnRecvPacket), ctx, packet, relayer) +} + +// OnTimeoutPacket mocks base method. +func (m *MockIBCTransferKeeper) OnTimeoutPacket(ctx types0.Context, packet types9.Packet, relayer types0.AccAddress) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnTimeoutPacket", ctx, packet, relayer) + ret0, _ := ret[0].(error) + return ret0 +} + +// OnTimeoutPacket indicates an expected call of OnTimeoutPacket. +func (mr *MockIBCTransferKeeperMockRecorder) OnTimeoutPacket(ctx, packet, relayer interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnTimeoutPacket", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnTimeoutPacket), ctx, packet, relayer) +} + // Transfer mocks base method. -func (m *MockIBCTransferKeeper) Transfer(arg0 context.Context, arg1 *types5.MsgTransfer) (*types5.MsgTransferResponse, error) { +func (m *MockIBCTransferKeeper) Transfer(arg0 context.Context, arg1 *types6.MsgTransfer) (*types6.MsgTransferResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Transfer", arg0, arg1) - ret0, _ := ret[0].(*types5.MsgTransferResponse) + ret0, _ := ret[0].(*types6.MsgTransferResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1100,10 +1281,10 @@ func (m *MockIBCCoreKeeper) EXPECT() *MockIBCCoreKeeperMockRecorder { } // ChannelOpenInit mocks base method. -func (m *MockIBCCoreKeeper) ChannelOpenInit(goCtx context.Context, msg *types8.MsgChannelOpenInit) (*types8.MsgChannelOpenInitResponse, error) { +func (m *MockIBCCoreKeeper) ChannelOpenInit(goCtx context.Context, msg *types9.MsgChannelOpenInit) (*types9.MsgChannelOpenInitResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ChannelOpenInit", goCtx, msg) - ret0, _ := ret[0].(*types8.MsgChannelOpenInitResponse) + ret0, _ := ret[0].(*types9.MsgChannelOpenInitResponse) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/x/ccv/provider/ibc_middleware.go b/x/ccv/provider/ibc_middleware.go new file mode 100644 index 0000000000..1c1b1ce824 --- /dev/null +++ b/x/ccv/provider/ibc_middleware.go @@ -0,0 +1,240 @@ +package provider + +import ( + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" + "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" + + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v7/modules/core/exported" +) + +var _ porttypes.Middleware = &IBCMiddleware{} + +// IBCMiddleware implements the callbacks for the IBC transfer middleware given the +// provider keeper and the underlying application. +type IBCMiddleware struct { + app porttypes.IBCModule + keeper keeper.Keeper +} + +// NewIBCMiddleware creates a new IBCMiddlware given the keeper and underlying application +func NewIBCMiddleware(app porttypes.IBCModule, k keeper.Keeper) IBCMiddleware { + return IBCMiddleware{ + app: app, + keeper: k, + } +} + +// OnChanOpenInit implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // call underlying app's OnChanOpenInit callback with the appVersion + return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version) +} + +// OnChanOpenTry implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // call underlying app's OnChanOpenTry callback with the appVersion + return im.app.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, counterpartyVersion) +} + +// OnChanOpenAck implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + // call underlying app's OnChanOpenAck callback with the counterparty app version. + return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) +} + +// OnChanOpenConfirm implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // call underlying app's OnChanOpenConfirm callback. + return im.app.OnChanOpenConfirm(ctx, portID, channelID) +} + +// OnChanCloseInit implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // call underlying app's OnChanCloseInit callback. + return im.app.OnChanCloseInit(ctx, portID, channelID) +} + +// OnChanCloseConfirm implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return im.app.OnChanCloseConfirm(ctx, portID, channelID) +} + +// OnRecvPacket executes the IBC transfer. In case of success, +// it verifies if the packet sender is a consumer chain +// and if the received IBC coin is whitelisted. In such instances, +// it appends the coin to the consumer's chain allocation record +func (im IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) exported.Acknowledgement { + // executes the IBC transfer OnRecv logic + ack := im.app.OnRecvPacket(ctx, packet, relayer) + + // execute the middleware logic only if the sender is a consumer chain + consumerID, err := im.keeper.IdentifyConsumerChainIDFromIBCPacket(ctx, packet) + if err != nil { + return ack + } + + // Note that inside the below if condition statement, + // we know that the IBC transfer succeeded. That entails + // that the packet data is valid and can be safely + // deserialized without checking errors. + if ack.Success() { + // extract the coin info received from the packet data + var data ibctransfertypes.FungibleTokenPacketData + _ = types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data) + + // check if the recipient is the consumer reward's pool address + receiver, _ := sdk.AccAddressFromBech32(data.Receiver) + if receiver.String() != im.keeper.GetConsumerRewardsPoolAddressStr(ctx) { + return ack + } + + coinAmt, _ := math.NewIntFromString(data.Amount) + coinDenom := GetProviderDenom(data.Denom, packet) + + // verify that the coin's denom is a whitelisted consumer denom, + // and if so, adds it to the consumer chain rewards allocation, + // otherwise the prohibited coin just stays in the pool forever. + if im.keeper.ConsumerRewardDenomExists(ctx, coinDenom) { + alloc := im.keeper.GetConsumerRewardsAllocation(ctx, consumerID) + alloc.Rewards = alloc.Rewards.Add( + sdk.NewDecCoinsFromCoins(sdk.Coin{ + Denom: coinDenom, + Amount: coinAmt, + })...) + im.keeper.SetConsumerRewardsAllocation(ctx, consumerID, alloc) + } + } + + return ack +} + +// OnAcknowledgementPacket implements the IBCMiddleware interface +// If fees are not enabled, this callback will default to the ibc-core packet callback +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // call underlying app's OnAcknowledgementPacket callback. + return im.app.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) +} + +// OnTimeoutPacket implements the IBCMiddleware interface +// If fees are not enabled, this callback will default to the ibc-core packet callback +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + // call underlying app's OnTimeoutPacket callback. + return im.app.OnTimeoutPacket(ctx, packet, relayer) +} + +// SendPacket implements the ICS4 Wrapper interface +func (im IBCMiddleware) SendPacket( + sdk.Context, + *capabilitytypes.Capability, + string, + string, + clienttypes.Height, + uint64, + []byte, +) (uint64, error) { + panic("should never be called since the IBC middleware doesn't have an ICS4wrapper") +} + +// WriteAcknowledgement implements the ICS4 Wrapper interface +func (im IBCMiddleware) WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, +) error { + panic("should never be called since the IBC middleware doesn't have an ICS4wrapper") +} + +// GetAppVersion returns the application version of the underlying application +func (im IBCMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + panic("should never be called since the IBC middleware doesn't have an ICS4wrapper") +} + +// GetProviderDenom returns the updated given denom according to the given IBC packet +// It follows the same logic than the OnRecvPacket method of the IBC transfer module +// see https://github.com/cosmos/ibc-go/blob/v7.3.2/modules/apps/transfer/keeper/relay.go#L162 +func GetProviderDenom(denom string, packet channeltypes.Packet) (providerDenom string) { + // If the the prefix denom corresponds to the packet's source port and channel, + // returns the base denom + if ibctransfertypes.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), denom) { + voucherPrefix := ibctransfertypes.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel()) + unprefixedDenom := denom[len(voucherPrefix):] + + // coin denomination used in sending from the escrow address + providerDenom = unprefixedDenom + + // The denomination used to send the coins is either the native denom or the hash of the path + // if the denomination is not native. + denomTrace := ibctransfertypes.ParseDenomTrace(unprefixedDenom) + if denomTrace.Path != "" { + providerDenom = denomTrace.IBCDenom() + } + // update the prefix denom according to the packet info + } else { + prefixedDenom := ibctransfertypes.GetPrefixedDenom( + packet.GetDestPort(), + packet.GetDestChannel(), + denom, + ) + + providerDenom = ibctransfertypes.ParseDenomTrace(prefixedDenom).IBCDenom() + } + + return providerDenom +} diff --git a/x/ccv/provider/ibc_middleware_test.go b/x/ccv/provider/ibc_middleware_test.go new file mode 100644 index 0000000000..3701a65402 --- /dev/null +++ b/x/ccv/provider/ibc_middleware_test.go @@ -0,0 +1,76 @@ +package provider_test + +import ( + "testing" + + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/cosmos/interchain-security/v4/x/ccv/provider" + "github.com/stretchr/testify/require" +) + +func TestGetProviderDenom(t *testing.T) { + testCases := []struct { + name string + denom string + packet channeltypes.Packet + expProviderDenom string + }{ + { + name: "returns base denom with destination port and channel as prefix", + denom: "stake", + packet: channeltypes.NewPacket( + []byte{}, + 0, + "srcPort", + "srcChannel", + "dstPort", + "dstChannel", + clienttypes.NewHeight(1, 1), + 0, + ), + expProviderDenom: "dstPort/dstChannel/stake", + }, + { + name: "returns base denom if the prefix denom corresponds to the packet's port and channel source", + denom: "srcPort/srcChannel/stake", + packet: channeltypes.NewPacket( + []byte{}, + 0, + "srcPort", + "srcChannel", + "dstPort", + "dstChannel", + clienttypes.NewHeight(1, 1), + 0, + ), + expProviderDenom: "stake", + }, + { + name: "returns prefixed denom updated with packet's port and channel destination as prefix", + denom: "dstPort/dstChannel/stake", + packet: channeltypes.NewPacket( + []byte{}, + 0, + "srcPort", + "srcChannel", + "dstPort", + "dstChannel", + clienttypes.NewHeight(1, 1), + 0, + ), + expProviderDenom: "dstPort/dstChannel/dstPort/dstChannel/stake", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res := provider.GetProviderDenom( + tc.denom, + tc.packet, + ) + + require.Equal(t, tc.expProviderDenom, res) + }) + } +} diff --git a/x/ccv/provider/keeper/distribution.go b/x/ccv/provider/keeper/distribution.go index 5b2e6025ef..45af3eb262 100644 --- a/x/ccv/provider/keeper/distribution.go +++ b/x/ccv/provider/keeper/distribution.go @@ -1,17 +1,30 @@ package keeper import ( + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" + abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ) -// EndBlockRD executes EndBlock logic for the Reward Distribution sub-protocol. -// Reward Distribution follows a simple model: send tokens to the ConsumerRewardsPool, -// from where they sent to the fee collector address -func (k Keeper) EndBlockRD(ctx sdk.Context) { - // transfers all whitelisted consumer rewards to the fee collector address - k.TransferRewardsToFeeCollector(ctx) +// BeginBlockRD executes BeginBlock logic for the Reward Distribution sub-protocol. +func (k Keeper) BeginBlockRD(ctx sdk.Context, req abci.RequestBeginBlock) { + + // determine the total power signing the block + var previousTotalPower int64 + for _, voteInfo := range req.LastCommitInfo.GetVotes() { + previousTotalPower += voteInfo.Validator.Power + } + + // TODO this is Tendermint-dependent + // ref https://github.com/cosmos/cosmos-sdk/issues/3095 + if ctx.BlockHeight() > 1 { + k.AllocateTokens(ctx, previousTotalPower, req.LastCommitInfo.GetVotes()) + } } func (k Keeper) GetConsumerRewardsPoolAddressStr(ctx sdk.Context) string { @@ -57,32 +70,200 @@ func (k Keeper) GetAllConsumerRewardDenoms(ctx sdk.Context) (consumerRewardDenom return consumerRewardDenoms } -// TransferRewardsToFeeCollector transfers all consumer rewards to the fee collector address -func (k Keeper) TransferRewardsToFeeCollector(ctx sdk.Context) { - // 1. Get the denom whitelist from the store - denoms := k.GetAllConsumerRewardDenoms(ctx) +// AllocateTokens performs rewards distribution to the community pool and validators +// based on the Partial Set Security distribution specification. +func (k Keeper) AllocateTokens(ctx sdk.Context, totalPreviousPower int64, bondedVotes []abci.VoteInfo) { + // return if there is no coins in the consumer rewards pool + if k.GetConsumerRewardsPool(ctx).IsZero() { + return + } + + // Iterate over all registered consumer chains + for _, consumer := range k.GetAllConsumerChains(ctx) { + // transfer the consumer rewards to the distribution module account + // note that the rewards transferred are only consumer whitelisted denoms + rewardsCollected, err := k.TransferConsumerRewardsToDistributionModule(ctx, consumer.ChainId) + if err != nil { + k.Logger(ctx).Error( + "fail to transfer rewards to distribution module for chain %s: %s", + consumer.ChainId, + err, + ) + continue + } + + if rewardsCollected.IsZero() { + continue + } + + rewardsCollectedDec := sdk.NewDecCoinsFromCoins(rewardsCollected...) + + // temporary workaround to keep CanWithdrawInvariant happy + // general discussions here: https://github.com/cosmos/cosmos-sdk/issues/2906#issuecomment-441867634 + feePool := k.distributionKeeper.GetFeePool(ctx) + if k.ComputeConsumerTotalVotingPower(ctx, consumer.ChainId, bondedVotes) == 0 { + feePool.CommunityPool = feePool.CommunityPool.Add(rewardsCollectedDec...) + k.distributionKeeper.SetFeePool(ctx, feePool) + return + } + + // Calculate the reward allocations + remaining := rewardsCollectedDec + communityTax := k.distributionKeeper.GetCommunityTax(ctx) + voteMultiplier := math.LegacyOneDec().Sub(communityTax) + feeMultiplier := rewardsCollectedDec.MulDecTruncate(voteMultiplier) - // 2. Iterate over the whitelist - for _, denom := range denoms { - // 3. For each denom, retrieve the balance from the consumer rewards pool - balance := k.bankKeeper.GetBalance( + // allocate tokens to consumer validators + feeAllocated := k.AllocateTokensToConsumerValidators( ctx, - k.accountKeeper.GetModuleAccount(ctx, types.ConsumerRewardsPool).GetAddress(), - denom, + consumer.ChainId, + bondedVotes, + feeMultiplier, ) + remaining = remaining.Sub(feeAllocated) - // if the balance is not zero, - if !balance.IsZero() { - // 4. Transfer the balance to the fee collector address - err := k.bankKeeper.SendCoinsFromModuleToModule( - ctx, - types.ConsumerRewardsPool, - k.feeCollectorName, - sdk.NewCoins(balance), - ) - if err != nil { - k.Logger(ctx).Error("cannot sent consumer rewards to fee collector:", "reward", balance.String()) - } - } + // allocate community funding + feePool.CommunityPool = feePool.CommunityPool.Add(remaining...) + k.distributionKeeper.SetFeePool(ctx, feePool) } } + +// TODO: allocate tokens to validators that opted-in and for long enough e.g. 1000 blocks +// once the opt-in logic is integrated QueueVSCPackets() +// +// AllocateTokensToConsumerValidators allocates the given tokens from the +// from consumer rewards pool to validator according to their voting power +func (k Keeper) AllocateTokensToConsumerValidators( + ctx sdk.Context, + chainID string, + bondedVotes []abci.VoteInfo, + tokens sdk.DecCoins, +) (totalReward sdk.DecCoins) { + // return early if the tokens are empty + if tokens.Empty() { + return totalReward + } + + // get the consumer total voting power from the votes + totalPower := k.ComputeConsumerTotalVotingPower(ctx, chainID, bondedVotes) + if totalPower == 0 { + return totalReward + } + + for _, vote := range bondedVotes { + // TODO: should check if validator IsOptIn or continue here + consAddr := sdk.ConsAddress(vote.Validator.Address) + + powerFraction := math.LegacyNewDec(vote.Validator.Power).QuoTruncate(math.LegacyNewDec(totalPower)) + tokensFraction := tokens.MulDecTruncate(powerFraction) + + k.distributionKeeper.AllocateTokensToValidator( + ctx, + k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr), + tokensFraction, + ) + totalReward = totalReward.Add(tokensFraction...) + } + + return totalReward +} + +// TransferConsumerRewardsToDistributionModule transfers the collected rewards of the given consumer chain +// from the consumer rewards pool module account to a the distribution module +func (k Keeper) TransferConsumerRewardsToDistributionModule( + ctx sdk.Context, + chainID string, +) (sdk.Coins, error) { + // Get coins of the consumer rewards allocation + allocation := k.GetConsumerRewardsAllocation(ctx, chainID) + + if allocation.Rewards.IsZero() { + return sdk.Coins{}, nil + } + + // Truncate coin rewards + rewardsToSend, _ := allocation.Rewards.TruncateDecimal() + + // NOTE the consumer rewards allocation isn't a module account, however its coins + // are held in the consumer reward pool module account. Thus the consumer + // rewards allocation must be reduced separately from the SendCoinsFromModuleToAccount call. + + // Update consumer rewards allocation with the remaining decimal coins + allocation.Rewards = allocation.Rewards.Sub(sdk.NewDecCoinsFromCoins(rewardsToSend...)) + + // Send coins to distribution module account + err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ConsumerRewardsPool, distrtypes.ModuleName, rewardsToSend) + if err != nil { + return sdk.Coins{}, err + } + + k.SetConsumerRewardsAllocation(ctx, chainID, allocation) + return rewardsToSend, nil +} + +// consumer reward pools getter and setter + +// GetConsumerRewardsAllocation returns the consumer rewards allocation for the given chain ID +func (k Keeper) GetConsumerRewardsAllocation(ctx sdk.Context, chainID string) (pool types.ConsumerRewardsAllocation) { + store := ctx.KVStore(k.storeKey) + b := store.Get(types.ConsumerRewardsAllocationKey(chainID)) + k.cdc.MustUnmarshal(b, &pool) + return +} + +// SetConsumerRewardsAllocation sets the consumer rewards allocation for the given chain ID +func (k Keeper) SetConsumerRewardsAllocation(ctx sdk.Context, chainID string, pool types.ConsumerRewardsAllocation) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshal(&pool) + store.Set(types.ConsumerRewardsAllocationKey(chainID), b) +} + +// GetConsumerRewardsPool returns the balance +// of the consumer rewards pool module account +func (k Keeper) GetConsumerRewardsPool(ctx sdk.Context) sdk.Coins { + return k.bankKeeper.GetAllBalances( + ctx, + k.accountKeeper.GetModuleAccount(ctx, types.ConsumerRewardsPool).GetAddress(), + ) +} + +// ComputeConsumerTotalVotingPower returns the total voting power for a given consumer chain +// by summing its opted-in validators votes +func (k Keeper) ComputeConsumerTotalVotingPower(ctx sdk.Context, chainID string, votes []abci.VoteInfo) int64 { + // TODO: create a optedIn set from the OptedIn validators + // and sum their validator power + var totalPower int64 + + // sum the opted-in validators set voting powers + for _, vote := range votes { + // TODO: check that val is in the optedIn set + + totalPower += vote.Validator.Power + } + + return totalPower +} + +// IdentifyConsumerChainIDFromIBCPacket checks if the packet destination matches a registered consumer chain. +// If so, it returns the consumer chain ID, otherwise an error. +func (k Keeper) IdentifyConsumerChainIDFromIBCPacket(ctx sdk.Context, packet channeltypes.Packet) (string, error) { + channel, ok := k.channelKeeper.GetChannel(ctx, packet.DestinationPort, packet.DestinationChannel) + if !ok { + return "", errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "channel not found for channel ID: %s", packet.DestinationChannel) + } + if len(channel.ConnectionHops) != 1 { + return "", errorsmod.Wrap(channeltypes.ErrTooManyConnectionHops, "must have direct connection to consumer chain") + } + connectionID := channel.ConnectionHops[0] + _, tmClient, err := k.getUnderlyingClient(ctx, connectionID) + if err != nil { + return "", err + } + + chainID := tmClient.ChainId + if _, ok := k.GetChainToChannel(ctx, chainID); !ok { + return "", errorsmod.Wrapf(types.ErrUnknownConsumerChannelId, "no CCV channel found for chain with ID: %s", chainID) + } + + return chainID, nil +} diff --git a/x/ccv/provider/keeper/distribution_test.go b/x/ccv/provider/keeper/distribution_test.go new file mode 100644 index 0000000000..b8883177b1 --- /dev/null +++ b/x/ccv/provider/keeper/distribution_test.go @@ -0,0 +1,238 @@ +package keeper_test + +import ( + "testing" + + abci "github.com/cometbft/cometbft/abci/types" + tmtypes "github.com/cometbft/cometbft/types" + sdk "github.com/cosmos/cosmos-sdk/types" + 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" + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + testkeeper "github.com/cosmos/interchain-security/v4/testutil/keeper" + "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func TestComputeConsumerTotalVotingPower(t *testing.T) { + keeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + createVal := func(power int64) tmtypes.Validator { + signer := tmtypes.NewMockPV() + val := tmtypes.NewValidator(signer.PrivKey.PubKey(), power) + return *val + } + + chainID := "consumer" + validatorsVotes := make([]abci.VoteInfo, 5) + + expTotalPower := int64(0) + + // create validators, opt them in and use them + // to create block votes + for i := 0; i < 5; i++ { + val := createVal(int64(i)) + keeper.SetOptedIn( + ctx, + chainID, + types.NewProviderConsAddress(sdk.ConsAddress(val.Address)), + 0, + ) + + validatorsVotes = append( + validatorsVotes, + abci.VoteInfo{ + Validator: abci.Validator{ + Address: val.Address, + Power: val.VotingPower, + }, + }, + ) + + expTotalPower += val.VotingPower + } + + res := keeper.ComputeConsumerTotalVotingPower( + ctx, + chainID, + validatorsVotes, + ) + + require.Equal(t, expTotalPower, res) +} + +func TestIdentifyConsumerChainIDFromIBCPacket(t *testing.T) { + + var ( + chainID = "consumer" + ccvChannel = "channel-0" + ) + + testCases := []struct { + name string + packet channeltypes.Packet + expectedCalls func(sdk.Context, testkeeper.MockedKeepers, channeltypes.Packet) []*gomock.Call + expCCVChannel bool + expErr bool + }{ + { + "channel not found", + channeltypes.NewPacket( + []byte{}, + 0, + "srcPort", + "srcChannel", + "dstPort", + "dstChannel", + clienttypes.NewHeight(1, 1), + 0, + ), + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, packet channeltypes.Packet) []*gomock.Call { + return []*gomock.Call{ + mocks.MockChannelKeeper.EXPECT().GetChannel( + ctx, + packet.DestinationPort, + packet.DestinationChannel, + ).Return(channeltypes.Channel{}, false).Times(1), + } + }, + false, + true, + }, + { + "connection hops can't be greater than 1", + channeltypes.NewPacket( + []byte{}, + 0, + "srcPort", + "srcChannel", + "dstPort", + "dstChannel", + clienttypes.NewHeight(1, 1), + 0, + ), + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, packet channeltypes.Packet) []*gomock.Call { + return []*gomock.Call{ + mocks.MockChannelKeeper.EXPECT().GetChannel( + ctx, + packet.DestinationPort, + packet.DestinationChannel, + ).Return(channeltypes.Channel{ConnectionHops: []string{"conn1", "conn2"}}, true).Times(1), + } + }, + false, + true, + }, + { + "underlying client isn't found", + channeltypes.NewPacket( + []byte{}, + 0, + "srcPort", + "srcChannel", + "dstPort", + "dstChannel", + clienttypes.NewHeight(1, 1), + 0, + ), + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, packet channeltypes.Packet) []*gomock.Call { + return []*gomock.Call{ + mocks.MockChannelKeeper.EXPECT().GetChannel( + ctx, + packet.DestinationPort, + packet.DestinationChannel, + ).Return(channeltypes.Channel{ConnectionHops: []string{"connectionID"}}, true).Times(1), + mocks.MockConnectionKeeper.EXPECT().GetConnection(ctx, "connectionID").Return( + conntypes.ConnectionEnd{ClientId: "clientID"}, true, + ).Times(1), + mocks.MockClientKeeper.EXPECT().GetClientState(ctx, "clientID").Return( + &ibctmtypes.ClientState{ChainId: ""}, false, + ).Times(1), + } + }, + false, + true, + }, + { + "no CCV channel registered", + channeltypes.NewPacket( + []byte{}, + 0, + "srcPort", + "srcChannel", + "dstPort", + "dstChannel", + clienttypes.NewHeight(1, 1), + 0, + ), + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, packet channeltypes.Packet) []*gomock.Call { + return []*gomock.Call{ + mocks.MockChannelKeeper.EXPECT().GetChannel( + ctx, + packet.DestinationPort, + packet.DestinationChannel, + ).Return(channeltypes.Channel{ConnectionHops: []string{"connectionID"}}, true).Times(1), + mocks.MockConnectionKeeper.EXPECT().GetConnection(ctx, "connectionID").Return( + conntypes.ConnectionEnd{ClientId: "clientID"}, true, + ).Times(1), + mocks.MockClientKeeper.EXPECT().GetClientState(ctx, "clientID").Return( + &ibctmtypes.ClientState{ChainId: chainID}, true, + ).Times(1), + } + }, + false, + true, + }, + { + "consumer chain identified", + channeltypes.NewPacket( + []byte{}, + 0, + "srcPort", + "srcChannel", + "dstPort", + "dstChannel", + clienttypes.NewHeight(1, 1), + 0, + ), + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, packet channeltypes.Packet) []*gomock.Call { + return []*gomock.Call{ + mocks.MockChannelKeeper.EXPECT().GetChannel( + ctx, + packet.DestinationPort, + packet.DestinationChannel, + ), + } + }, + false, + true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + keeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + tc.expectedCalls(ctx, mocks, tc.packet) + _, err := keeper.IdentifyConsumerChainIDFromIBCPacket( + ctx, + tc.packet, + ) + + if tc.expCCVChannel { + keeper.SetChainToChannel(ctx, chainID, ccvChannel) + } + + if !tc.expErr { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} diff --git a/x/ccv/provider/module.go b/x/ccv/provider/module.go index f34b92bb07..1fe4ae08bb 100644 --- a/x/ccv/provider/module.go +++ b/x/ccv/provider/module.go @@ -144,6 +144,8 @@ func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { am.keeper.BeginBlockCCR(ctx) // Check for replenishing slash meter before any slash packets are processed for this block am.keeper.BeginBlockCIS(ctx) + // BeginBlock logic need for the Reward Distribution sub-protocol + am.keeper.BeginBlockRD(ctx, req) } // EndBlock implements the AppModule interface @@ -155,8 +157,6 @@ func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.V am.keeper.EndBlockCCR(ctx) // EndBlock logic needed for the Validator Set Update sub-protocol am.keeper.EndBlockVSU(ctx) - // EndBlock logic need for the Reward Distribution sub-protocol - am.keeper.EndBlockRD(ctx) return []abci.ValidatorUpdate{} } diff --git a/x/ccv/provider/types/consumer.go b/x/ccv/provider/types/consumer.go index 4c43bd58e7..2a89859a36 100644 --- a/x/ccv/provider/types/consumer.go +++ b/x/ccv/provider/types/consumer.go @@ -1,6 +1,7 @@ package types import ( + sdk "github.com/cosmos/cosmos-sdk/types" ccv "github.com/cosmos/interchain-security/v4/x/ccv/types" ) @@ -25,3 +26,10 @@ func NewConsumerStates( SlashDowntimeAck: slashDowntimeAck, } } + +// zero consumer rewards allocation +func InitialConsumerRewardsAllocation() ConsumerRewardsAllocation { + return ConsumerRewardsAllocation{ + Rewards: sdk.DecCoins{}, + } +} diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 20824454cb..ed86ea0804 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -160,6 +160,10 @@ const ( // are about to be opted out ToBeOptedOutBytePrefix + // ConsumerRewardsAllocationBytePrefix is the byte prefix used when storing for each consumer the rewards + // it allocated to the consumer rewards pool + ConsumerRewardsAllocationBytePrefix + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -555,6 +559,11 @@ func ToBeOptedOutKey(chainID string, providerAddr ProviderConsAddress) []byte { return append(prefix, providerAddr.ToSdkConsAddr().Bytes()...) } +// ConsumerModuleAccount returns the module account byte prefix for a consumer chain +func ConsumerRewardsAllocationKey(chainID string) []byte { + return append([]byte{ConsumerRewardsAllocationBytePrefix}, []byte(chainID)...) +} + // // End of generic helpers section // diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index 62ca2b9fef..9d797011b6 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -60,6 +60,7 @@ func getAllKeyPrefixes() []byte { providertypes.OptedInBytePrefix, providertypes.ToBeOptedInBytePrefix, providertypes.ToBeOptedOutBytePrefix, + providertypes.ConsumerRewardsAllocationBytePrefix, } } diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index fd9d63bc7e..20733b4fa0 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -6,7 +6,9 @@ package types import ( fmt "fmt" crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" types2 "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" types1 "github.com/cosmos/cosmos-sdk/x/evidence/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" @@ -1390,6 +1392,51 @@ func (m *ConsumerAddrsToPrune) GetConsumerAddrs() *AddressList { return nil } +// ConsumerRewardsAllocation is used to serialize the allocation of consumer chain rewards +type ConsumerRewardsAllocation struct { + Rewards github_com_cosmos_cosmos_sdk_types.DecCoins `protobuf:"bytes,1,rep,name=rewards,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.DecCoins" json:"rewards"` +} + +func (m *ConsumerRewardsAllocation) Reset() { *m = ConsumerRewardsAllocation{} } +func (m *ConsumerRewardsAllocation) String() string { return proto.CompactTextString(m) } +func (*ConsumerRewardsAllocation) ProtoMessage() {} +func (*ConsumerRewardsAllocation) Descriptor() ([]byte, []int) { + return fileDescriptor_f22ec409a72b7b72, []int{22} +} +func (m *ConsumerRewardsAllocation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConsumerRewardsAllocation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConsumerRewardsAllocation.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 *ConsumerRewardsAllocation) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConsumerRewardsAllocation.Merge(m, src) +} +func (m *ConsumerRewardsAllocation) XXX_Size() int { + return m.Size() +} +func (m *ConsumerRewardsAllocation) XXX_DiscardUnknown() { + xxx_messageInfo_ConsumerRewardsAllocation.DiscardUnknown(m) +} + +var xxx_messageInfo_ConsumerRewardsAllocation proto.InternalMessageInfo + +func (m *ConsumerRewardsAllocation) GetRewards() github_com_cosmos_cosmos_sdk_types.DecCoins { + if m != nil { + return m.Rewards + } + return nil +} + func init() { proto.RegisterType((*ConsumerAdditionProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerAdditionProposal") proto.RegisterType((*ConsumerRemovalProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerRemovalProposal") @@ -1413,6 +1460,7 @@ func init() { proto.RegisterType((*ValidatorConsumerPubKey)(nil), "interchain_security.ccv.provider.v1.ValidatorConsumerPubKey") proto.RegisterType((*ValidatorByConsumerAddr)(nil), "interchain_security.ccv.provider.v1.ValidatorByConsumerAddr") proto.RegisterType((*ConsumerAddrsToPrune)(nil), "interchain_security.ccv.provider.v1.ConsumerAddrsToPrune") + proto.RegisterType((*ConsumerRewardsAllocation)(nil), "interchain_security.ccv.provider.v1.ConsumerRewardsAllocation") } func init() { @@ -1420,114 +1468,119 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1712 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4f, 0x73, 0xdb, 0xc6, - 0x15, 0x17, 0x44, 0x4a, 0x16, 0x1f, 0xf5, 0xcf, 0x90, 0x12, 0x43, 0xae, 0x4a, 0xd1, 0x48, 0x93, - 0xaa, 0x93, 0x09, 0x58, 0x29, 0xed, 0x4c, 0xc6, 0xd3, 0x4c, 0x46, 0xa2, 0x9c, 0x58, 0x56, 0x63, - 0x2b, 0x90, 0x2a, 0x4f, 0xdb, 0x03, 0x66, 0xb9, 0x58, 0x93, 0x3b, 0x02, 0xb1, 0xf0, 0xee, 0x02, - 0x0e, 0x2f, 0x3d, 0xf7, 0x98, 0xde, 0x32, 0xbd, 0x34, 0xed, 0x17, 0xe8, 0xb9, 0xdf, 0x20, 0xc7, - 0x1c, 0x7b, 0x4a, 0x3b, 0xf2, 0xb1, 0xd7, 0x7e, 0x80, 0xce, 0x2e, 0xfe, 0x92, 0x12, 0x5d, 0x7a, - 0xdc, 0xde, 0x80, 0xb7, 0xef, 0xfd, 0xde, 0xdb, 0xf7, 0xe7, 0xf7, 0x40, 0xc2, 0x3e, 0x0d, 0x25, - 0xe1, 0x78, 0x80, 0x68, 0xe8, 0x09, 0x82, 0x63, 0x4e, 0xe5, 0xa8, 0x83, 0x71, 0xd2, 0x89, 0x38, - 0x4b, 0xa8, 0x4f, 0x78, 0x27, 0xd9, 0x2b, 0x9e, 0x9d, 0x88, 0x33, 0xc9, 0xcc, 0x77, 0x6e, 0xb0, - 0x71, 0x30, 0x4e, 0x9c, 0x42, 0x2f, 0xd9, 0xbb, 0xfb, 0xee, 0x34, 0xe0, 0x64, 0xaf, 0xf3, 0x82, - 0x72, 0x92, 0x62, 0xdd, 0xdd, 0xec, 0xb3, 0x3e, 0xd3, 0x8f, 0x1d, 0xf5, 0x94, 0x49, 0x77, 0xfa, - 0x8c, 0xf5, 0x03, 0xd2, 0xd1, 0x6f, 0xbd, 0xf8, 0x59, 0x47, 0xd2, 0x21, 0x11, 0x12, 0x0d, 0xa3, - 0x4c, 0xa1, 0x35, 0xa9, 0xe0, 0xc7, 0x1c, 0x49, 0xca, 0xc2, 0x1c, 0x80, 0xf6, 0x70, 0x07, 0x33, - 0x4e, 0x3a, 0x38, 0xa0, 0x24, 0x94, 0xca, 0x6b, 0xfa, 0x94, 0x29, 0x74, 0x94, 0x42, 0x40, 0xfb, - 0x03, 0x99, 0x8a, 0x45, 0x47, 0x92, 0xd0, 0x27, 0x7c, 0x48, 0x53, 0xe5, 0xf2, 0x2d, 0x33, 0xd8, - 0xae, 0x9c, 0x63, 0x3e, 0x8a, 0x24, 0xeb, 0x5c, 0x92, 0x91, 0xc8, 0x4e, 0xdf, 0xc3, 0x4c, 0x0c, - 0x99, 0xe8, 0x10, 0x75, 0xff, 0x10, 0x93, 0x4e, 0xb2, 0xd7, 0x23, 0x12, 0xed, 0x15, 0x82, 0x3c, - 0xee, 0x4c, 0xaf, 0x87, 0x44, 0xa9, 0x83, 0x19, 0xcd, 0xe2, 0xb6, 0xff, 0xbd, 0x08, 0x56, 0x97, - 0x85, 0x22, 0x1e, 0x12, 0x7e, 0xe0, 0xfb, 0x54, 0x5d, 0xe9, 0x94, 0xb3, 0x88, 0x09, 0x14, 0x98, - 0x9b, 0xb0, 0x20, 0xa9, 0x0c, 0x88, 0x65, 0xb4, 0x8d, 0xdd, 0x86, 0x9b, 0xbe, 0x98, 0x6d, 0x68, - 0xfa, 0x44, 0x60, 0x4e, 0x23, 0xa5, 0x6c, 0xcd, 0xeb, 0xb3, 0xaa, 0xc8, 0xdc, 0x82, 0xa5, 0xb4, - 0x0e, 0xd4, 0xb7, 0x6a, 0xfa, 0xf8, 0x96, 0x7e, 0x3f, 0xf6, 0xcd, 0xcf, 0x60, 0x95, 0x86, 0x54, - 0x52, 0x14, 0x78, 0x03, 0xa2, 0xb2, 0x61, 0xd5, 0xdb, 0xc6, 0x6e, 0x73, 0xff, 0xae, 0x43, 0x7b, - 0xd8, 0x51, 0x09, 0x74, 0xb2, 0xb4, 0x25, 0x7b, 0xce, 0x43, 0xad, 0x71, 0x58, 0xff, 0xf6, 0xfb, - 0x9d, 0x39, 0x77, 0x25, 0xb3, 0x4b, 0x85, 0xe6, 0x3d, 0x58, 0xee, 0x93, 0x90, 0x08, 0x2a, 0xbc, - 0x01, 0x12, 0x03, 0x6b, 0xa1, 0x6d, 0xec, 0x2e, 0xbb, 0xcd, 0x4c, 0xf6, 0x10, 0x89, 0x81, 0xb9, - 0x03, 0xcd, 0x1e, 0x0d, 0x11, 0x1f, 0xa5, 0x1a, 0x8b, 0x5a, 0x03, 0x52, 0x91, 0x56, 0xe8, 0x02, - 0x88, 0x08, 0xbd, 0x08, 0x3d, 0x55, 0x6d, 0xeb, 0x56, 0x16, 0x48, 0x5a, 0x69, 0x27, 0xaf, 0xb4, - 0x73, 0x9e, 0xb7, 0xc2, 0xe1, 0x92, 0x0a, 0xe4, 0xab, 0x7f, 0xec, 0x18, 0x6e, 0x43, 0xdb, 0xa9, - 0x13, 0xf3, 0x31, 0xac, 0xc7, 0x61, 0x8f, 0x85, 0x3e, 0x0d, 0xfb, 0x5e, 0x44, 0x38, 0x65, 0xbe, - 0xb5, 0xa4, 0xa1, 0xb6, 0xae, 0x41, 0x1d, 0x65, 0x4d, 0x93, 0x22, 0x7d, 0xad, 0x90, 0xd6, 0x0a, - 0xe3, 0x53, 0x6d, 0x6b, 0x7e, 0x01, 0x26, 0xc6, 0x89, 0x0e, 0x89, 0xc5, 0x32, 0x47, 0x6c, 0xcc, - 0x8e, 0xb8, 0x8e, 0x71, 0x72, 0x9e, 0x5a, 0x67, 0x90, 0xbf, 0x85, 0x3b, 0x92, 0xa3, 0x50, 0x3c, - 0x23, 0x7c, 0x12, 0x17, 0x66, 0xc7, 0x7d, 0x2b, 0xc7, 0x18, 0x07, 0x7f, 0x08, 0x6d, 0x9c, 0x35, - 0x90, 0xc7, 0x89, 0x4f, 0x85, 0xe4, 0xb4, 0x17, 0x2b, 0x5b, 0xef, 0x19, 0x47, 0x58, 0xf7, 0x48, - 0x53, 0x37, 0x41, 0x2b, 0xd7, 0x73, 0xc7, 0xd4, 0x3e, 0xcd, 0xb4, 0xcc, 0x27, 0xf0, 0xa3, 0x5e, - 0xc0, 0xf0, 0xa5, 0x50, 0xc1, 0x79, 0x63, 0x48, 0xda, 0xf5, 0x90, 0x0a, 0xa1, 0xd0, 0x96, 0xdb, - 0xc6, 0x6e, 0xcd, 0xbd, 0x97, 0xea, 0x9e, 0x12, 0x7e, 0x54, 0xd1, 0x3c, 0xaf, 0x28, 0x9a, 0x1f, - 0x80, 0x39, 0xa0, 0x42, 0x32, 0x4e, 0x31, 0x0a, 0x3c, 0x12, 0x4a, 0x4e, 0x89, 0xb0, 0x56, 0xb4, - 0xf9, 0xed, 0xf2, 0xe4, 0x41, 0x7a, 0x60, 0x3e, 0x82, 0x7b, 0x53, 0x9d, 0x7a, 0x78, 0x80, 0xc2, - 0x90, 0x04, 0xd6, 0xaa, 0xbe, 0xca, 0x8e, 0x3f, 0xc5, 0x67, 0x37, 0x55, 0x33, 0x37, 0x60, 0x41, - 0xb2, 0xc8, 0x7b, 0x6c, 0xad, 0xb5, 0x8d, 0xdd, 0x15, 0xb7, 0x2e, 0x59, 0xf4, 0xf8, 0xfe, 0xd2, - 0xef, 0xbf, 0xd9, 0x99, 0xfb, 0xfa, 0x9b, 0x9d, 0x39, 0xfb, 0xaf, 0x06, 0xdc, 0xe9, 0x16, 0xd9, - 0x18, 0xb2, 0x04, 0x05, 0xff, 0xcf, 0xa9, 0x3b, 0x80, 0x86, 0x50, 0xe1, 0xe8, 0x3e, 0xaf, 0xbf, - 0x46, 0x9f, 0x2f, 0x29, 0x33, 0x75, 0x60, 0xff, 0xc9, 0x80, 0xcd, 0x07, 0xcf, 0x63, 0x9a, 0x30, - 0x8c, 0xfe, 0x27, 0x24, 0x71, 0x02, 0x2b, 0xa4, 0x82, 0x27, 0xac, 0x5a, 0xbb, 0xb6, 0xdb, 0xdc, - 0x7f, 0xd7, 0x49, 0x19, 0xcb, 0x29, 0x88, 0x2c, 0x63, 0x2d, 0xa7, 0xea, 0xdd, 0x1d, 0xb7, 0xbd, - 0x3f, 0x6f, 0x19, 0xf6, 0x5f, 0x0c, 0xb8, 0xab, 0xd2, 0xdf, 0x27, 0x2e, 0x79, 0x81, 0xb8, 0x7f, - 0x44, 0x42, 0x36, 0x14, 0x6f, 0x1c, 0xa7, 0x0d, 0x2b, 0xbe, 0x46, 0xf2, 0x24, 0xf3, 0x90, 0xef, - 0xeb, 0x38, 0xb5, 0x8e, 0x12, 0x9e, 0xb3, 0x03, 0xdf, 0x37, 0x77, 0x61, 0xbd, 0xd4, 0xe1, 0xaa, - 0x9e, 0x2a, 0xcd, 0x4a, 0x6d, 0x35, 0x57, 0xd3, 0x55, 0x26, 0xf6, 0xbf, 0x0c, 0x58, 0xff, 0x2c, - 0x60, 0x3d, 0x14, 0x9c, 0x05, 0x48, 0x0c, 0x54, 0xeb, 0x8d, 0x54, 0x79, 0x38, 0xc9, 0x66, 0x5e, - 0x87, 0x37, 0x73, 0x79, 0x94, 0x99, 0x66, 0xa1, 0x4f, 0xe0, 0x76, 0x31, 0x85, 0x45, 0x17, 0xe8, - 0xdb, 0x1c, 0x6e, 0x5c, 0x7d, 0xbf, 0xb3, 0x96, 0x37, 0x5b, 0x57, 0x77, 0xc4, 0x91, 0xbb, 0x86, - 0xc7, 0x04, 0xbe, 0xd9, 0x82, 0x26, 0xed, 0x61, 0x4f, 0x90, 0xe7, 0x5e, 0x18, 0x0f, 0x75, 0x03, - 0xd5, 0xdd, 0x06, 0xed, 0xe1, 0x33, 0xf2, 0xfc, 0x71, 0x3c, 0x34, 0x3f, 0x84, 0xb7, 0xf3, 0x6d, - 0xeb, 0x25, 0x28, 0xf0, 0x94, 0xbd, 0x4a, 0x07, 0xd7, 0xfd, 0xb4, 0xec, 0x6e, 0xe4, 0xa7, 0x17, - 0x28, 0x50, 0xce, 0x0e, 0x7c, 0x9f, 0xdb, 0x7f, 0x5b, 0x80, 0xc5, 0x53, 0xc4, 0xd1, 0x50, 0x98, - 0xe7, 0xb0, 0x26, 0xc9, 0x30, 0x0a, 0x90, 0x24, 0x5e, 0xca, 0xf0, 0xd9, 0x4d, 0xdf, 0xd7, 0xcc, - 0x5f, 0xdd, 0x8c, 0x4e, 0x65, 0x17, 0x26, 0x7b, 0x4e, 0x57, 0x4b, 0xcf, 0x24, 0x92, 0xc4, 0x5d, - 0xcd, 0x31, 0x52, 0xa1, 0xf9, 0x11, 0x58, 0x92, 0xc7, 0x42, 0x96, 0xdc, 0x5b, 0x92, 0x4e, 0x5a, - 0xcb, 0xb7, 0xf3, 0xf3, 0x94, 0xae, 0x0a, 0xb2, 0xb9, 0x99, 0x66, 0x6b, 0x6f, 0x42, 0xb3, 0x67, - 0xb0, 0xa1, 0x76, 0xd4, 0x24, 0x66, 0x7d, 0x76, 0xcc, 0xdb, 0xca, 0x7e, 0x1c, 0xf4, 0x0b, 0x30, - 0x13, 0x81, 0x27, 0x31, 0x17, 0x5e, 0x23, 0xce, 0x44, 0xe0, 0x71, 0x48, 0x1f, 0xb6, 0x85, 0x6a, - 0x3e, 0x6f, 0x48, 0xa4, 0x26, 0xed, 0x28, 0x20, 0x21, 0x15, 0x83, 0x1c, 0x7c, 0x71, 0x76, 0xf0, - 0x2d, 0x0d, 0xf4, 0xb9, 0xc2, 0x71, 0x73, 0x98, 0xcc, 0x4b, 0x17, 0x5a, 0x37, 0x7b, 0x29, 0x0a, - 0x74, 0x4b, 0x17, 0xe8, 0x07, 0x37, 0x40, 0x14, 0x55, 0x12, 0xf0, 0x5e, 0x65, 0xb9, 0xa8, 0xa9, - 0xf6, 0xf4, 0x40, 0x79, 0x9c, 0xf4, 0x15, 0x03, 0xa3, 0x74, 0xcf, 0x10, 0x52, 0x2c, 0xc8, 0x8c, - 0x3d, 0xd4, 0xf7, 0x4e, 0xc1, 0x1c, 0x5d, 0x46, 0xc3, 0xec, 0x2b, 0xc2, 0x2e, 0x77, 0x50, 0xc1, - 0x11, 0x6e, 0x05, 0xeb, 0x53, 0x42, 0x1e, 0xd5, 0x97, 0x96, 0xd6, 0x1b, 0xf6, 0x4f, 0xa0, 0xa1, - 0x47, 0xf4, 0x00, 0x5f, 0x0a, 0x73, 0x1b, 0x1a, 0xaa, 0xd7, 0x89, 0x10, 0x44, 0x58, 0x86, 0x9e, - 0xec, 0x52, 0x60, 0x4b, 0xd8, 0x9a, 0xf6, 0x0d, 0x25, 0xcc, 0xa7, 0x70, 0x2b, 0x22, 0x7a, 0xc1, - 0x6b, 0xc3, 0xe6, 0xfe, 0xc7, 0xce, 0x0c, 0x9f, 0xb3, 0xce, 0x34, 0x40, 0x37, 0x47, 0xb3, 0x79, - 0xf9, 0xe5, 0x36, 0xb1, 0x42, 0x84, 0x79, 0x31, 0xe9, 0xf4, 0x17, 0xaf, 0xe5, 0x74, 0x02, 0xaf, - 0xf4, 0xf9, 0x3e, 0x34, 0x0f, 0xd2, 0x6b, 0xff, 0x92, 0x0a, 0x79, 0x3d, 0x2d, 0xcb, 0xd5, 0xb4, - 0x3c, 0x82, 0xd5, 0x6c, 0x1d, 0x9e, 0x33, 0x4d, 0x33, 0xe6, 0x0f, 0x01, 0xb2, 0x3d, 0xaa, 0xe8, - 0x29, 0x25, 0xe2, 0x46, 0x26, 0x39, 0xf6, 0xc7, 0x36, 0xd8, 0xfc, 0xd8, 0x06, 0xb3, 0x5d, 0x58, - 0xbb, 0x10, 0xf8, 0x57, 0xf9, 0xb7, 0xd2, 0x93, 0x48, 0x98, 0x6f, 0xc1, 0xa2, 0x9a, 0x8c, 0x0c, - 0xa8, 0xee, 0x2e, 0x24, 0x02, 0x1f, 0x6b, 0x2e, 0x2e, 0xbf, 0xc7, 0x58, 0xe4, 0x51, 0x5f, 0x58, - 0xf3, 0xed, 0xda, 0x6e, 0xdd, 0x5d, 0x8d, 0x4b, 0xf3, 0x63, 0x5f, 0xd8, 0xbf, 0x86, 0x66, 0x05, - 0xd0, 0x5c, 0x85, 0xf9, 0x02, 0x6b, 0x9e, 0xfa, 0xe6, 0x7d, 0xd8, 0x2a, 0x81, 0xc6, 0xc9, 0x35, - 0x45, 0x6c, 0xb8, 0x77, 0x0a, 0x85, 0x31, 0x7e, 0x15, 0xf6, 0x13, 0xd8, 0x3c, 0x2e, 0x47, 0xb9, - 0xa0, 0xee, 0xb1, 0x1b, 0x1a, 0xe3, 0x3b, 0x7a, 0x1b, 0x1a, 0xc5, 0x8f, 0x0e, 0x7d, 0xfb, 0xba, - 0x5b, 0x0a, 0xec, 0x21, 0xac, 0x5f, 0x08, 0x7c, 0x46, 0x42, 0xbf, 0x04, 0x9b, 0x92, 0x80, 0xc3, - 0x49, 0xa0, 0x99, 0x3f, 0x6a, 0x4b, 0x77, 0x0c, 0xb6, 0x2e, 0x50, 0x40, 0x7d, 0x24, 0x19, 0x3f, - 0x23, 0x32, 0x5d, 0xab, 0xa7, 0x08, 0x5f, 0x12, 0x29, 0x4c, 0x17, 0xea, 0x01, 0x15, 0x32, 0xeb, - 0xac, 0x8f, 0xa6, 0x76, 0x56, 0xb2, 0xe7, 0x4c, 0x03, 0x39, 0x42, 0x12, 0x65, 0x13, 0xa9, 0xb1, - 0xec, 0x1f, 0xc3, 0xc6, 0xe7, 0x48, 0xc6, 0x9c, 0xf8, 0x63, 0x35, 0x5e, 0x87, 0x9a, 0xaa, 0x9f, - 0xa1, 0xeb, 0xa7, 0x1e, 0xd5, 0x96, 0xb7, 0x1e, 0x7c, 0x19, 0x31, 0x2e, 0x89, 0x7f, 0x2d, 0x23, - 0xaf, 0x48, 0xef, 0x25, 0x6c, 0xa8, 0x64, 0x09, 0x12, 0xfa, 0x5e, 0x71, 0xcf, 0xb4, 0x8e, 0xcd, - 0xfd, 0x9f, 0xcf, 0x34, 0x1d, 0x93, 0xee, 0xb2, 0x0b, 0xdc, 0x4e, 0x26, 0xe4, 0xc2, 0xfe, 0x83, - 0x01, 0xd6, 0x09, 0x19, 0x1d, 0x08, 0x41, 0xfb, 0xe1, 0x90, 0x84, 0x52, 0x31, 0x1b, 0xc2, 0x44, - 0x3d, 0x9a, 0xef, 0xc0, 0x4a, 0xb1, 0x49, 0xf5, 0x02, 0x35, 0xf4, 0x02, 0x5d, 0xce, 0x85, 0x6a, - 0xc0, 0xcc, 0xfb, 0x00, 0x11, 0x27, 0x89, 0x87, 0xbd, 0x4b, 0x32, 0xca, 0xaa, 0xb8, 0x5d, 0x5d, - 0x8c, 0xe9, 0x4f, 0x42, 0xe7, 0x34, 0xee, 0x05, 0x14, 0x9f, 0x90, 0x91, 0xbb, 0xa4, 0xf4, 0xbb, - 0x27, 0x64, 0xa4, 0xbe, 0x74, 0x22, 0xf6, 0x82, 0x70, 0xbd, 0xcd, 0x6a, 0x6e, 0xfa, 0x62, 0xff, - 0xd1, 0x80, 0x3b, 0x45, 0x39, 0xf2, 0x76, 0x3d, 0x8d, 0x7b, 0xca, 0xe2, 0x15, 0x79, 0xbb, 0x16, - 0xed, 0xfc, 0x0d, 0xd1, 0x7e, 0x02, 0xcb, 0xc5, 0x80, 0xa8, 0x78, 0x6b, 0x33, 0xc4, 0xdb, 0xcc, - 0x2d, 0x4e, 0xc8, 0xc8, 0xfe, 0x5d, 0x25, 0xb6, 0xc3, 0x51, 0x85, 0xfb, 0xf8, 0x7f, 0x89, 0xad, - 0x70, 0x5b, 0x8d, 0x0d, 0x57, 0xed, 0xaf, 0x5d, 0xa0, 0x76, 0xfd, 0x02, 0xf6, 0x9f, 0x0d, 0xd8, - 0xac, 0x7a, 0x15, 0xe7, 0xec, 0x94, 0xc7, 0x21, 0x79, 0x95, 0xf7, 0x72, 0xfc, 0xe6, 0xab, 0xe3, - 0xf7, 0x14, 0x56, 0xc7, 0x82, 0x12, 0x59, 0x36, 0x7e, 0x3a, 0x53, 0x8f, 0x55, 0xd8, 0xd5, 0x5d, - 0xa9, 0xde, 0x43, 0x1c, 0x3e, 0xfd, 0xf6, 0xaa, 0x65, 0x7c, 0x77, 0xd5, 0x32, 0xfe, 0x79, 0xd5, - 0x32, 0xbe, 0x7a, 0xd9, 0x9a, 0xfb, 0xee, 0x65, 0x6b, 0xee, 0xef, 0x2f, 0x5b, 0x73, 0xbf, 0xf9, - 0xb8, 0x4f, 0xe5, 0x20, 0xee, 0x39, 0x98, 0x0d, 0x3b, 0xd9, 0xef, 0xfd, 0xd2, 0xd7, 0x07, 0xc5, - 0x9f, 0x21, 0xc9, 0xcf, 0x3a, 0x5f, 0x8e, 0xff, 0xd5, 0x22, 0x47, 0x11, 0x11, 0xbd, 0x45, 0xcd, - 0x0a, 0x1f, 0xfe, 0x27, 0x00, 0x00, 0xff, 0xff, 0xa8, 0x99, 0xdf, 0x57, 0x9b, 0x11, 0x00, 0x00, + // 1787 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x73, 0x1c, 0x47, + 0x15, 0xd7, 0x68, 0x57, 0x1f, 0xfb, 0x56, 0x9f, 0x23, 0x25, 0x1e, 0x19, 0xb1, 0x92, 0x27, 0x24, + 0x08, 0x5c, 0x9e, 0x41, 0x0a, 0x54, 0xb9, 0x5c, 0xa4, 0x52, 0xd2, 0xca, 0x89, 0x65, 0x11, 0x5b, + 0x19, 0x09, 0xb9, 0x80, 0xc3, 0x54, 0x6f, 0x4f, 0x7b, 0xb7, 0x4b, 0xb3, 0xd3, 0xe3, 0xee, 0xde, + 0x71, 0xf6, 0xc2, 0x99, 0x0b, 0x55, 0xe1, 0x96, 0xe2, 0x42, 0xe0, 0x44, 0x71, 0x81, 0x2b, 0xff, + 0x41, 0x8e, 0x39, 0x72, 0x4a, 0x28, 0xfb, 0xc0, 0x81, 0x2b, 0x7f, 0x00, 0xd5, 0x3d, 0x9f, 0xbb, + 0x92, 0xcc, 0xba, 0x42, 0x2e, 0xd2, 0xcc, 0xeb, 0xf7, 0x7e, 0xef, 0x75, 0xbf, 0xf7, 0x7e, 0xaf, + 0x77, 0x60, 0x8f, 0x46, 0x92, 0x70, 0xdc, 0x43, 0x34, 0xf2, 0x05, 0xc1, 0x03, 0x4e, 0xe5, 0xd0, + 0xc5, 0x38, 0x71, 0x63, 0xce, 0x12, 0x1a, 0x10, 0xee, 0x26, 0xbb, 0xc5, 0xb3, 0x13, 0x73, 0x26, + 0x99, 0xf9, 0xd6, 0x15, 0x36, 0x0e, 0xc6, 0x89, 0x53, 0xe8, 0x25, 0xbb, 0x37, 0xdf, 0xbe, 0x0e, + 0x38, 0xd9, 0x75, 0x9f, 0x53, 0x4e, 0x52, 0xac, 0x9b, 0xeb, 0x5d, 0xd6, 0x65, 0xfa, 0xd1, 0x55, + 0x4f, 0x99, 0x74, 0xab, 0xcb, 0x58, 0x37, 0x24, 0xae, 0x7e, 0xeb, 0x0c, 0x9e, 0xba, 0x92, 0xf6, + 0x89, 0x90, 0xa8, 0x1f, 0x67, 0x0a, 0xad, 0x71, 0x85, 0x60, 0xc0, 0x91, 0xa4, 0x2c, 0xca, 0x01, + 0x68, 0x07, 0xbb, 0x98, 0x71, 0xe2, 0xe2, 0x90, 0x92, 0x48, 0x2a, 0xaf, 0xe9, 0x53, 0xa6, 0xe0, + 0x2a, 0x85, 0x90, 0x76, 0x7b, 0x32, 0x15, 0x0b, 0x57, 0x92, 0x28, 0x20, 0xbc, 0x4f, 0x53, 0xe5, + 0xf2, 0x2d, 0x33, 0xd8, 0xac, 0xac, 0x63, 0x3e, 0x8c, 0x25, 0x73, 0x2f, 0xc8, 0x50, 0x64, 0xab, + 0xef, 0x60, 0x26, 0xfa, 0x4c, 0xb8, 0x44, 0xed, 0x3f, 0xc2, 0xc4, 0x4d, 0x76, 0x3b, 0x44, 0xa2, + 0xdd, 0x42, 0x90, 0xc7, 0x9d, 0xe9, 0x75, 0x90, 0x28, 0x75, 0x30, 0xa3, 0x79, 0xdc, 0xab, 0xa8, + 0x4f, 0x23, 0xe6, 0xea, 0xbf, 0xa9, 0xc8, 0xfe, 0xcf, 0x2c, 0x58, 0x6d, 0x16, 0x89, 0x41, 0x9f, + 0xf0, 0xfd, 0x20, 0xa0, 0x6a, 0x97, 0x27, 0x9c, 0xc5, 0x4c, 0xa0, 0xd0, 0x5c, 0x87, 0x19, 0x49, + 0x65, 0x48, 0x2c, 0x63, 0xdb, 0xd8, 0x69, 0x78, 0xe9, 0x8b, 0xb9, 0x0d, 0xcd, 0x80, 0x08, 0xcc, + 0x69, 0xac, 0x94, 0xad, 0x69, 0xbd, 0x56, 0x15, 0x99, 0x1b, 0x30, 0x9f, 0xa6, 0x86, 0x06, 0x56, + 0x4d, 0x2f, 0xcf, 0xe9, 0xf7, 0xa3, 0xc0, 0xfc, 0x10, 0x96, 0x68, 0x44, 0x25, 0x45, 0xa1, 0xdf, + 0x23, 0xea, 0x80, 0xac, 0xfa, 0xb6, 0xb1, 0xd3, 0xdc, 0xbb, 0xe9, 0xd0, 0x0e, 0x76, 0xd4, 0x99, + 0x3a, 0xd9, 0x49, 0x26, 0xbb, 0xce, 0x03, 0xad, 0x71, 0x50, 0xff, 0xe2, 0xab, 0xad, 0x29, 0x6f, + 0x31, 0xb3, 0x4b, 0x85, 0xe6, 0x2d, 0x58, 0xe8, 0x92, 0x88, 0x08, 0x2a, 0xfc, 0x1e, 0x12, 0x3d, + 0x6b, 0x66, 0xdb, 0xd8, 0x59, 0xf0, 0x9a, 0x99, 0xec, 0x01, 0x12, 0x3d, 0x73, 0x0b, 0x9a, 0x1d, + 0x1a, 0x21, 0x3e, 0x4c, 0x35, 0x66, 0xb5, 0x06, 0xa4, 0x22, 0xad, 0xd0, 0x06, 0x10, 0x31, 0x7a, + 0x1e, 0xf9, 0xaa, 0x00, 0xac, 0xb9, 0x2c, 0x90, 0x34, 0xf9, 0x4e, 0x9e, 0x7c, 0xe7, 0x2c, 0xaf, + 0x8e, 0x83, 0x79, 0x15, 0xc8, 0xa7, 0x5f, 0x6f, 0x19, 0x5e, 0x43, 0xdb, 0xa9, 0x15, 0xf3, 0x11, + 0xac, 0x0c, 0xa2, 0x0e, 0x8b, 0x02, 0x1a, 0x75, 0xfd, 0x98, 0x70, 0xca, 0x02, 0x6b, 0x5e, 0x43, + 0x6d, 0x5c, 0x82, 0x3a, 0xcc, 0xea, 0x28, 0x45, 0xfa, 0x4c, 0x21, 0x2d, 0x17, 0xc6, 0x27, 0xda, + 0xd6, 0xfc, 0x18, 0x4c, 0x8c, 0x13, 0x1d, 0x12, 0x1b, 0xc8, 0x1c, 0xb1, 0x31, 0x39, 0xe2, 0x0a, + 0xc6, 0xc9, 0x59, 0x6a, 0x9d, 0x41, 0xfe, 0x0a, 0x6e, 0x48, 0x8e, 0x22, 0xf1, 0x94, 0xf0, 0x71, + 0x5c, 0x98, 0x1c, 0xf7, 0x8d, 0x1c, 0x63, 0x14, 0xfc, 0x01, 0x6c, 0xe3, 0xac, 0x80, 0x7c, 0x4e, + 0x02, 0x2a, 0x24, 0xa7, 0x9d, 0x81, 0xb2, 0xf5, 0x9f, 0x72, 0x84, 0x75, 0x8d, 0x34, 0x75, 0x11, + 0xb4, 0x72, 0x3d, 0x6f, 0x44, 0xed, 0x83, 0x4c, 0xcb, 0x7c, 0x0c, 0xdf, 0xeb, 0x84, 0x0c, 0x5f, + 0x08, 0x15, 0x9c, 0x3f, 0x82, 0xa4, 0x5d, 0xf7, 0xa9, 0x10, 0x0a, 0x6d, 0x61, 0xdb, 0xd8, 0xa9, + 0x79, 0xb7, 0x52, 0xdd, 0x13, 0xc2, 0x0f, 0x2b, 0x9a, 0x67, 0x15, 0x45, 0xf3, 0x0e, 0x98, 0x3d, + 0x2a, 0x24, 0xe3, 0x14, 0xa3, 0xd0, 0x27, 0x91, 0xe4, 0x94, 0x08, 0x6b, 0x51, 0x9b, 0xaf, 0x96, + 0x2b, 0xf7, 0xd3, 0x05, 0xf3, 0x21, 0xdc, 0xba, 0xd6, 0xa9, 0x8f, 0x7b, 0x28, 0x8a, 0x48, 0x68, + 0x2d, 0xe9, 0xad, 0x6c, 0x05, 0xd7, 0xf8, 0x6c, 0xa7, 0x6a, 0xe6, 0x1a, 0xcc, 0x48, 0x16, 0xfb, + 0x8f, 0xac, 0xe5, 0x6d, 0x63, 0x67, 0xd1, 0xab, 0x4b, 0x16, 0x3f, 0xba, 0x37, 0xff, 0x9b, 0xcf, + 0xb7, 0xa6, 0x3e, 0xfb, 0x7c, 0x6b, 0xca, 0xfe, 0xab, 0x01, 0x37, 0xda, 0xc5, 0x69, 0xf4, 0x59, + 0x82, 0xc2, 0x6f, 0xb3, 0xeb, 0xf6, 0xa1, 0x21, 0x54, 0x38, 0xba, 0xce, 0xeb, 0xaf, 0x51, 0xe7, + 0xf3, 0xca, 0x4c, 0x2d, 0xd8, 0x7f, 0x30, 0x60, 0xfd, 0xfe, 0xb3, 0x01, 0x4d, 0x18, 0x46, 0xff, + 0x17, 0x92, 0x38, 0x86, 0x45, 0x52, 0xc1, 0x13, 0x56, 0x6d, 0xbb, 0xb6, 0xd3, 0xdc, 0x7b, 0xdb, + 0x49, 0x49, 0xcc, 0x29, 0xb8, 0x2d, 0x23, 0x32, 0xa7, 0xea, 0xdd, 0x1b, 0xb5, 0xbd, 0x37, 0x6d, + 0x19, 0xf6, 0x9f, 0x0c, 0xb8, 0xa9, 0x8e, 0xbf, 0x4b, 0x3c, 0xf2, 0x1c, 0xf1, 0xe0, 0x90, 0x44, + 0xac, 0x2f, 0xbe, 0x71, 0x9c, 0x36, 0x2c, 0x06, 0x1a, 0xc9, 0x97, 0xcc, 0x47, 0x41, 0xa0, 0xe3, + 0xd4, 0x3a, 0x4a, 0x78, 0xc6, 0xf6, 0x83, 0xc0, 0xdc, 0x81, 0x95, 0x52, 0x87, 0xab, 0x7c, 0xaa, + 0x63, 0x56, 0x6a, 0x4b, 0xb9, 0x9a, 0xce, 0x32, 0xb1, 0xff, 0x6d, 0xc0, 0xca, 0x87, 0x21, 0xeb, + 0xa0, 0xf0, 0x34, 0x44, 0xa2, 0xa7, 0x4a, 0x6f, 0xa8, 0xd2, 0xc3, 0x49, 0xd6, 0xf3, 0x3a, 0xbc, + 0x89, 0xd3, 0xa3, 0xcc, 0x34, 0x0b, 0xbd, 0x0f, 0xab, 0x45, 0x17, 0x16, 0x55, 0xa0, 0x77, 0x73, + 0xb0, 0xf6, 0xe2, 0xab, 0xad, 0xe5, 0xbc, 0xd8, 0xda, 0xba, 0x22, 0x0e, 0xbd, 0x65, 0x3c, 0x22, + 0x08, 0xcc, 0x16, 0x34, 0x69, 0x07, 0xfb, 0x82, 0x3c, 0xf3, 0xa3, 0x41, 0x5f, 0x17, 0x50, 0xdd, + 0x6b, 0xd0, 0x0e, 0x3e, 0x25, 0xcf, 0x1e, 0x0d, 0xfa, 0xe6, 0xbb, 0xf0, 0x66, 0x3e, 0x80, 0xfd, + 0x04, 0x85, 0xbe, 0xb2, 0x57, 0xc7, 0xc1, 0x75, 0x3d, 0x2d, 0x78, 0x6b, 0xf9, 0xea, 0x39, 0x0a, + 0x95, 0xb3, 0xfd, 0x20, 0xe0, 0xf6, 0xdf, 0x67, 0x60, 0xf6, 0x04, 0x71, 0xd4, 0x17, 0xe6, 0x19, + 0x2c, 0x4b, 0xd2, 0x8f, 0x43, 0x24, 0x89, 0x9f, 0x32, 0x7c, 0xb6, 0xd3, 0xdb, 0x9a, 0xf9, 0xab, + 0xc3, 0xd2, 0xa9, 0x8c, 0xc7, 0x64, 0xd7, 0x69, 0x6b, 0xe9, 0xa9, 0x44, 0x92, 0x78, 0x4b, 0x39, + 0x46, 0x2a, 0x34, 0xef, 0x82, 0x25, 0xf9, 0x40, 0xc8, 0x92, 0x7b, 0x4b, 0xd2, 0x49, 0x73, 0xf9, + 0x66, 0xbe, 0x9e, 0xd2, 0x55, 0x41, 0x36, 0x57, 0xd3, 0x6c, 0xed, 0x9b, 0xd0, 0xec, 0x29, 0xac, + 0xa9, 0x19, 0x35, 0x8e, 0x59, 0x9f, 0x1c, 0x73, 0x55, 0xd9, 0x8f, 0x82, 0x7e, 0x0c, 0x66, 0x22, + 0xf0, 0x38, 0xe6, 0xcc, 0x6b, 0xc4, 0x99, 0x08, 0x3c, 0x0a, 0x19, 0xc0, 0xa6, 0x50, 0xc5, 0xe7, + 0xf7, 0x89, 0xd4, 0xa4, 0x1d, 0x87, 0x24, 0xa2, 0xa2, 0x97, 0x83, 0xcf, 0x4e, 0x0e, 0xbe, 0xa1, + 0x81, 0x3e, 0x52, 0x38, 0x5e, 0x0e, 0x93, 0x79, 0x69, 0x43, 0xeb, 0x6a, 0x2f, 0x45, 0x82, 0xe6, + 0x74, 0x82, 0xbe, 0x73, 0x05, 0x44, 0x91, 0x25, 0x01, 0xef, 0x54, 0x86, 0x8b, 0xea, 0x6a, 0x5f, + 0x37, 0x94, 0xcf, 0x49, 0x57, 0x31, 0x30, 0x4a, 0xe7, 0x0c, 0x21, 0xc5, 0x80, 0xcc, 0xd8, 0x43, + 0x5d, 0x81, 0x0a, 0xe6, 0x68, 0x33, 0x1a, 0x65, 0xb7, 0x08, 0xbb, 0x9c, 0x41, 0x05, 0x47, 0x78, + 0x15, 0xac, 0x0f, 0x08, 0x79, 0x58, 0x9f, 0x9f, 0x5f, 0x69, 0xd8, 0x3f, 0x80, 0x86, 0x6e, 0xd1, + 0x7d, 0x7c, 0x21, 0xcc, 0x4d, 0x68, 0xa8, 0x5a, 0x27, 0x42, 0x10, 0x61, 0x19, 0xba, 0xb3, 0x4b, + 0x81, 0x2d, 0x61, 0xe3, 0xba, 0x3b, 0x94, 0x30, 0x9f, 0xc0, 0x5c, 0x4c, 0xf4, 0x80, 0xd7, 0x86, + 0xcd, 0xbd, 0xf7, 0x9c, 0x09, 0x6e, 0xb8, 0xce, 0x75, 0x80, 0x5e, 0x8e, 0x66, 0xf3, 0xf2, 0xe6, + 0x36, 0x36, 0x42, 0x84, 0x79, 0x3e, 0xee, 0xf4, 0xa7, 0xaf, 0xe5, 0x74, 0x0c, 0xaf, 0xf4, 0x79, + 0x1b, 0x9a, 0xfb, 0xe9, 0xb6, 0x7f, 0x46, 0x85, 0xbc, 0x7c, 0x2c, 0x0b, 0xd5, 0x63, 0x79, 0x08, + 0x4b, 0xd9, 0x38, 0x3c, 0x63, 0x9a, 0x66, 0xcc, 0xef, 0x02, 0x64, 0x73, 0x54, 0xd1, 0x53, 0x4a, + 0xc4, 0x8d, 0x4c, 0x72, 0x14, 0x8c, 0x4c, 0xb0, 0xe9, 0x91, 0x09, 0x66, 0x7b, 0xb0, 0x7c, 0x2e, + 0xf0, 0xcf, 0xf3, 0xbb, 0xd2, 0xe3, 0x58, 0x98, 0x6f, 0xc0, 0xac, 0xea, 0x8c, 0x0c, 0xa8, 0xee, + 0xcd, 0x24, 0x02, 0x1f, 0x69, 0x2e, 0x2e, 0xef, 0x63, 0x2c, 0xf6, 0x69, 0x20, 0xac, 0xe9, 0xed, + 0xda, 0x4e, 0xdd, 0x5b, 0x1a, 0x94, 0xe6, 0x47, 0x81, 0xb0, 0x7f, 0x01, 0xcd, 0x0a, 0xa0, 0xb9, + 0x04, 0xd3, 0x05, 0xd6, 0x34, 0x0d, 0xcc, 0x7b, 0xb0, 0x51, 0x02, 0x8d, 0x92, 0x6b, 0x8a, 0xd8, + 0xf0, 0x6e, 0x14, 0x0a, 0x23, 0xfc, 0x2a, 0xec, 0xc7, 0xb0, 0x7e, 0x54, 0xb6, 0x72, 0x41, 0xdd, + 0x23, 0x3b, 0x34, 0x46, 0x67, 0xf4, 0x26, 0x34, 0x8a, 0xdf, 0x21, 0x7a, 0xf7, 0x75, 0xaf, 0x14, + 0xd8, 0x7d, 0x58, 0x39, 0x17, 0xf8, 0x94, 0x44, 0x41, 0x09, 0x76, 0xcd, 0x01, 0x1c, 0x8c, 0x03, + 0x4d, 0x7c, 0xa9, 0x2d, 0xdd, 0x31, 0xd8, 0x38, 0x47, 0x21, 0x0d, 0x90, 0x64, 0xfc, 0x94, 0xc8, + 0x74, 0xac, 0x9e, 0x20, 0x7c, 0x41, 0xa4, 0x30, 0x3d, 0xa8, 0x87, 0x54, 0xc8, 0xac, 0xb2, 0xee, + 0x5e, 0x5b, 0x59, 0xc9, 0xae, 0x73, 0x1d, 0xc8, 0x21, 0x92, 0x28, 0xeb, 0x48, 0x8d, 0x65, 0x7f, + 0x1f, 0xd6, 0x3e, 0x42, 0x72, 0xc0, 0x49, 0x30, 0x92, 0xe3, 0x15, 0xa8, 0xa9, 0xfc, 0x19, 0x3a, + 0x7f, 0xea, 0x51, 0x4d, 0x79, 0xeb, 0xfe, 0x27, 0x31, 0xe3, 0x92, 0x04, 0x97, 0x4e, 0xe4, 0x15, + 0xc7, 0x7b, 0x01, 0x6b, 0xea, 0xb0, 0x04, 0x89, 0x02, 0xbf, 0xd8, 0x67, 0x9a, 0xc7, 0xe6, 0xde, + 0x4f, 0x26, 0xea, 0x8e, 0x71, 0x77, 0xd9, 0x06, 0x56, 0x93, 0x31, 0xb9, 0xb0, 0x7f, 0x67, 0x80, + 0x75, 0x4c, 0x86, 0xfb, 0x42, 0xd0, 0x6e, 0xd4, 0x27, 0x91, 0x54, 0xcc, 0x86, 0x30, 0x51, 0x8f, + 0xe6, 0x5b, 0xb0, 0x58, 0x4c, 0x52, 0x3d, 0x40, 0x0d, 0x3d, 0x40, 0x17, 0x72, 0xa1, 0x6a, 0x30, + 0xf3, 0x1e, 0x40, 0xcc, 0x49, 0xe2, 0x63, 0xff, 0x82, 0x0c, 0xb3, 0x2c, 0x6e, 0x56, 0x07, 0x63, + 0xfa, 0x2b, 0xd1, 0x39, 0x19, 0x74, 0x42, 0x8a, 0x8f, 0xc9, 0xd0, 0x9b, 0x57, 0xfa, 0xed, 0x63, + 0x32, 0x54, 0x37, 0x9d, 0x98, 0x3d, 0x27, 0x5c, 0x4f, 0xb3, 0x9a, 0x97, 0xbe, 0xd8, 0xbf, 0x37, + 0xe0, 0x46, 0x91, 0x8e, 0xbc, 0x5c, 0x4f, 0x06, 0x1d, 0x65, 0xf1, 0x8a, 0x73, 0xbb, 0x14, 0xed, + 0xf4, 0x15, 0xd1, 0xbe, 0x0f, 0x0b, 0x45, 0x83, 0xa8, 0x78, 0x6b, 0x13, 0xc4, 0xdb, 0xcc, 0x2d, + 0x8e, 0xc9, 0xd0, 0xfe, 0x75, 0x25, 0xb6, 0x83, 0x61, 0x85, 0xfb, 0xf8, 0xff, 0x88, 0xad, 0x70, + 0x5b, 0x8d, 0x0d, 0x57, 0xed, 0x2f, 0x6d, 0xa0, 0x76, 0x79, 0x03, 0xf6, 0x1f, 0x0d, 0x58, 0xaf, + 0x7a, 0x15, 0x67, 0xec, 0x84, 0x0f, 0x22, 0xf2, 0x2a, 0xef, 0x65, 0xfb, 0x4d, 0x57, 0xdb, 0xef, + 0x09, 0x2c, 0x8d, 0x04, 0x25, 0xb2, 0xd3, 0xf8, 0xd1, 0x44, 0x35, 0x56, 0x61, 0x57, 0x6f, 0xb1, + 0xba, 0x0f, 0x61, 0xff, 0xd6, 0x28, 0xc7, 0x4c, 0x3a, 0xbd, 0xc4, 0x7e, 0x18, 0x66, 0x57, 0x60, + 0x33, 0x86, 0xb9, 0x74, 0x40, 0x8a, 0xac, 0x2f, 0x37, 0xaf, 0x1c, 0x85, 0x87, 0x04, 0xeb, 0x69, + 0x78, 0x57, 0x95, 0xee, 0x5f, 0xbe, 0xde, 0xba, 0xdd, 0xa5, 0xb2, 0x37, 0xe8, 0x38, 0x98, 0xf5, + 0xdd, 0xec, 0xeb, 0x41, 0xfa, 0xef, 0x8e, 0x08, 0x2e, 0x5c, 0x39, 0x8c, 0x89, 0xc8, 0x6d, 0xc4, + 0x9f, 0xff, 0xf5, 0xb7, 0x1f, 0x1a, 0x5e, 0xee, 0xe6, 0xe0, 0xc9, 0x17, 0x2f, 0x5a, 0xc6, 0x97, + 0x2f, 0x5a, 0xc6, 0x3f, 0x5f, 0xb4, 0x8c, 0x4f, 0x5f, 0xb6, 0xa6, 0xbe, 0x7c, 0xd9, 0x9a, 0xfa, + 0xc7, 0xcb, 0xd6, 0xd4, 0x2f, 0xdf, 0xbb, 0x0c, 0x5a, 0xee, 0xfd, 0x4e, 0xf1, 0xbd, 0x26, 0xf9, + 0xb1, 0xfb, 0xc9, 0xe8, 0xd7, 0x20, 0xed, 0xaf, 0x33, 0xab, 0x59, 0xea, 0xdd, 0xff, 0x06, 0x00, + 0x00, 0xff, 0xff, 0x2a, 0x2c, 0xd4, 0x2e, 0x3e, 0x12, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -2596,6 +2649,43 @@ func (m *ConsumerAddrsToPrune) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ConsumerRewardsAllocation) 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 *ConsumerRewardsAllocation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConsumerRewardsAllocation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Rewards) > 0 { + for iNdEx := len(m.Rewards) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Rewards[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintProvider(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func encodeVarintProvider(dAtA []byte, offset int, v uint64) int { offset -= sovProvider(v) base := offset @@ -3067,6 +3157,21 @@ func (m *ConsumerAddrsToPrune) Size() (n int) { return n } +func (m *ConsumerRewardsAllocation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Rewards) > 0 { + for _, e := range m.Rewards { + l = e.Size() + n += 1 + l + sovProvider(uint64(l)) + } + } + return n +} + func sovProvider(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -6360,6 +6465,90 @@ func (m *ConsumerAddrsToPrune) Unmarshal(dAtA []byte) error { } return nil } +func (m *ConsumerRewardsAllocation) 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: ConsumerRewardsAllocation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConsumerRewardsAllocation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Rewards", 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.Rewards = append(m.Rewards, types2.DecCoin{}) + if err := m.Rewards[len(m.Rewards)-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 skipProvider(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index df531e09ca..761eca90d7 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -11,6 +11,7 @@ import ( ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" "cosmossdk.io/math" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" sdk "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -109,6 +110,10 @@ type ClientKeeper interface { // DistributionKeeper defines the expected interface of the distribution keeper type DistributionKeeper interface { FundCommunityPool(ctx sdk.Context, amount sdk.Coins, sender sdk.AccAddress) error + GetFeePool(ctx sdk.Context) distrtypes.FeePool + SetFeePool(ctx sdk.Context, feePool distrtypes.FeePool) + GetCommunityTax(ctx sdk.Context) math.LegacyDec + AllocateTokensToValidator(ctx sdk.Context, validator stakingtypes.ValidatorI, reward sdk.DecCoins) } // ConsumerHooks event hooks for newly bonded cross-chain validators From ad7a8525e71ac8ca22782c88ead2f02afbacf4dc Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 23 Feb 2024 15:15:37 +0100 Subject: [PATCH 16/18] feat! use protos to serialize opted-in validators (#1659) move OptedInValidators to proto Co-authored-by: insumity --- .../ccv/provider/v1/provider.proto | 13 + x/ccv/provider/keeper/distribution_test.go | 9 +- x/ccv/provider/keeper/keeper.go | 38 +- x/ccv/provider/keeper/keeper_test.go | 56 +- x/ccv/provider/keeper/key_assignment.go | 4 +- x/ccv/provider/keeper/partial_set_security.go | 6 - .../keeper/partial_set_security_test.go | 7 +- x/ccv/provider/types/keys.go | 4 +- x/ccv/provider/types/provider.pb.go | 526 ++++++++++++++---- 9 files changed, 499 insertions(+), 164 deletions(-) diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index b4a512e36d..39e71df522 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -313,3 +313,16 @@ message ConsumerRewardsAllocation { (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins" ]; } + +// OptedInValidator is used to store a opted-in validator +// to a consumer chain with the following mapping: (chainID, providerAddr) -> optedInValidator +message OptedInValidator { + // validator address + bytes provider_addr = 1; + // block height at which the validator opted-in + int64 block_height = 2; + // validator voting power at the block it opted-in + int64 power = 3; + // public key used by the validator on the consumer + bytes public_key = 4; +} diff --git a/x/ccv/provider/keeper/distribution_test.go b/x/ccv/provider/keeper/distribution_test.go index b8883177b1..52ac0e0a82 100644 --- a/x/ccv/provider/keeper/distribution_test.go +++ b/x/ccv/provider/keeper/distribution_test.go @@ -38,8 +38,12 @@ func TestComputeConsumerTotalVotingPower(t *testing.T) { keeper.SetOptedIn( ctx, chainID, - types.NewProviderConsAddress(sdk.ConsAddress(val.Address)), - 0, + types.OptedInValidator{ + ProviderAddr: val.Address, + BlockHeight: ctx.BlockHeight(), + Power: val.VotingPower, + PublicKey: val.PubKey.Bytes(), + }, ) validatorsVotes = append( @@ -65,7 +69,6 @@ func TestComputeConsumerTotalVotingPower(t *testing.T) { } func TestIdentifyConsumerChainIDFromIBCPacket(t *testing.T) { - var ( chainID = "consumer" ccvChannel = "channel-0" diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 42bcb12ace..e6b1c589aa 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -1189,16 +1189,15 @@ func (k Keeper) IsOptIn(ctx sdk.Context, chainID string) bool { func (k Keeper) SetOptedIn( ctx sdk.Context, chainID string, - providerAddr types.ProviderConsAddress, - blockHeight uint64, + validator types.OptedInValidator, ) { store := ctx.KVStore(k.storeKey) + bz, err := validator.Marshal() + if err != nil { + panic(fmt.Errorf("failed to marshal OptedInValidator: %w", err)) + } - // validator is considered opted in - blockHeightBytes := make([]byte, 8) - binary.BigEndian.PutUint64(blockHeightBytes, blockHeight) - - store.Set(types.OptedInKey(chainID, providerAddr), blockHeightBytes) + store.Set(types.OptedInKey(chainID, validator.ProviderAddr), bz) } func (k Keeper) DeleteOptedIn( @@ -1207,7 +1206,7 @@ func (k Keeper) DeleteOptedIn( providerAddr types.ProviderConsAddress, ) { store := ctx.KVStore(k.storeKey) - store.Delete(types.OptedInKey(chainID, providerAddr)) + store.Delete(types.OptedInKey(chainID, providerAddr.ToSdkConsAddr())) } func (k Keeper) IsOptedIn( @@ -1216,22 +1215,25 @@ func (k Keeper) IsOptedIn( providerAddr types.ProviderConsAddress, ) bool { store := ctx.KVStore(k.storeKey) - return store.Get(types.OptedInKey(chainID, providerAddr)) != nil + return store.Get(types.OptedInKey(chainID, providerAddr.ToSdkConsAddr())) != nil } -func (k Keeper) GetOptedIn( +// GetAllOptedIn returns all the opted-in validators on chain `chainID` +func (k Keeper) GetAllOptedIn( ctx sdk.Context, - chainID string) (optedInValidators []OptedInValidator) { + chainID string) (optedInValidators []types.OptedInValidator) { store := ctx.KVStore(k.storeKey) key := types.ChainIdWithLenKey(types.OptedInBytePrefix, chainID) iterator := sdk.KVStorePrefixIterator(store, key) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { - optedInValidators = append(optedInValidators, OptedInValidator{ - ProviderAddr: types.NewProviderConsAddress(iterator.Key()[len(key):]), - BlockHeight: binary.BigEndian.Uint64(iterator.Value()), - }) + iterator.Value() + var optedInValidator types.OptedInValidator + if err := optedInValidator.Unmarshal(iterator.Value()); err != nil { + panic(fmt.Errorf("failed to unmarshal OptedInValidator: %w", err)) + } + optedInValidators = append(optedInValidators, optedInValidator) } return optedInValidators @@ -1264,7 +1266,8 @@ func (k Keeper) IsToBeOptedIn( return store.Get(types.ToBeOptedInKey(chainID, providerAddr)) != nil } -func (k Keeper) GetToBeOptedIn( +// GetAllToBeOptedIn returns all the to-be-opted-in validators on chain `chainID` +func (k Keeper) GetAllToBeOptedIn( ctx sdk.Context, chainID string) (addresses []types.ProviderConsAddress) { @@ -1308,7 +1311,8 @@ func (k Keeper) IsToBeOptedOut( return store.Get(types.ToBeOptedOutKey(chainID, providerAddr)) != nil } -func (k Keeper) GetToBeOptedOut( +// GetAllToBeOptedOut returns all the to-be-opted-out validators on chain `chainID` +func (k Keeper) GetAllToBeOptedOut( ctx sdk.Context, chainID string) (addresses []types.ProviderConsAddress) { diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index 35380a67aa..a231eb4c0c 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -3,7 +3,6 @@ package keeper_test import ( "bytes" "fmt" - "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" "sort" "testing" "time" @@ -662,39 +661,49 @@ func TestTopN(t *testing.T) { } } -func TestGetOptedIn(t *testing.T) { +func TestGetAllOptedIn(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - expectedOptedInValidators := []keeper.OptedInValidator{ + expectedOptedInValidators := []types.OptedInValidator{ { - ProviderAddr: types.NewProviderConsAddress([]byte("providerAddr1")), + ProviderAddr: []byte("providerAddr1"), BlockHeight: 1, + Power: 2, + PublicKey: []byte{3}, }, { - ProviderAddr: types.NewProviderConsAddress([]byte("providerAddr2")), + ProviderAddr: []byte("providerAddr2"), BlockHeight: 2, + Power: 3, + PublicKey: []byte{4}, }, { - ProviderAddr: types.NewProviderConsAddress([]byte("providerAddr3")), + ProviderAddr: []byte("providerAddr3"), BlockHeight: 3, + Power: 4, + PublicKey: []byte{5}, }, } for _, expectedOptedInValidator := range expectedOptedInValidators { providerKeeper.SetOptedIn(ctx, "chainID", - expectedOptedInValidator.ProviderAddr, expectedOptedInValidator.BlockHeight) + types.OptedInValidator{ + ProviderAddr: expectedOptedInValidator.ProviderAddr, + BlockHeight: expectedOptedInValidator.BlockHeight, + Power: expectedOptedInValidator.Power, + PublicKey: expectedOptedInValidator.PublicKey}) } - actualOptedInValidators := providerKeeper.GetOptedIn(ctx, "chainID") + actualOptedInValidators := providerKeeper.GetAllOptedIn(ctx, "chainID") // sort validators first to be able to compare - sortOptedInValidators := func(optedInValidators []keeper.OptedInValidator) { + sortOptedInValidators := func(optedInValidators []types.OptedInValidator) { sort.Slice(optedInValidators, func(i int, j int) bool { a := optedInValidators[i] b := optedInValidators[j] return a.BlockHeight < b.BlockHeight || - (a.BlockHeight == b.BlockHeight && bytes.Compare(a.ProviderAddr.ToSdkConsAddr(), b.ProviderAddr.ToSdkConsAddr()) < 0) + (a.BlockHeight == b.BlockHeight && bytes.Compare(a.ProviderAddr, b.ProviderAddr) < 0) }) } sortOptedInValidators(expectedOptedInValidators) @@ -707,19 +716,20 @@ func TestOptedIn(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - optedInValidator := keeper.OptedInValidator{ - ProviderAddr: types.NewProviderConsAddress([]byte("providerAddr")), - BlockHeight: 1, + optedInValidator := types.OptedInValidator{ProviderAddr: []byte("providerAddr"), + BlockHeight: 1, + Power: 2, + PublicKey: []byte{3}, } - require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", optedInValidator.ProviderAddr)) - providerKeeper.SetOptedIn(ctx, "chainID", optedInValidator.ProviderAddr, optedInValidator.BlockHeight) - require.True(t, providerKeeper.IsOptedIn(ctx, "chainID", optedInValidator.ProviderAddr)) - providerKeeper.DeleteOptedIn(ctx, "chainID", optedInValidator.ProviderAddr) - require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", optedInValidator.ProviderAddr)) + require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderAddr))) + providerKeeper.SetOptedIn(ctx, "chainID", optedInValidator) + require.True(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderAddr))) + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderAddr)) + require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderAddr))) } -func TestGetToBeOptedIn(t *testing.T) { +func TestGetAllToBeOptedIn(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() @@ -732,7 +742,7 @@ func TestGetToBeOptedIn(t *testing.T) { providerKeeper.SetToBeOptedIn(ctx, "chainID", addr) } - actualAddresses := providerKeeper.GetToBeOptedIn(ctx, "chainID") + actualAddresses := providerKeeper.GetAllToBeOptedIn(ctx, "chainID") // sort addresses first to be able to compare sortAddresses := func(addresses []types.ProviderConsAddress) { @@ -766,7 +776,7 @@ func TestBeOptedIn(t *testing.T) { providerKeeper.SetToBeOptedIn(ctx, "chainID", addr) } - actualAddresses := providerKeeper.GetToBeOptedIn(ctx, "chainID") + actualAddresses := providerKeeper.GetAllToBeOptedIn(ctx, "chainID") // sort addresses first to be able to compare sortAddresses := func(addresses []types.ProviderConsAddress) { @@ -801,7 +811,7 @@ func TestToBeOptedIn(t *testing.T) { require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) } -func TestGetToBeOptedOut(t *testing.T) { +func TestGetAllToBeOptedOut(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() @@ -814,7 +824,7 @@ func TestGetToBeOptedOut(t *testing.T) { providerKeeper.SetToBeOptedOut(ctx, "chainID", addr) } - actualAddresses := providerKeeper.GetToBeOptedOut(ctx, "chainID") + actualAddresses := providerKeeper.GetAllToBeOptedOut(ctx, "chainID") // sort addresses first to be able to compare sortAddresses := func(addresses []types.ProviderConsAddress) { diff --git a/x/ccv/provider/keeper/key_assignment.go b/x/ccv/provider/keeper/key_assignment.go index fc864c6417..6fbe48b41e 100644 --- a/x/ccv/provider/keeper/key_assignment.go +++ b/x/ccv/provider/keeper/key_assignment.go @@ -502,7 +502,6 @@ func (k Keeper) AssignConsumerKey( oldConsumerKey = providerKey } - // check whether the validator is valid, i.e., its power is positive power := k.stakingKeeper.GetLastValidatorPower(ctx, validator.GetOperator()) if 0 < power { // to enable multiple calls of AssignConsumerKey in the same block by the same validator @@ -568,10 +567,13 @@ func (k Keeper) MustApplyKeyAssignmentToValUpdates( // - and setting the new consumer key's power to the power in the update prevConsumerKey, _, found := k.GetKeyAssignmentReplacement(ctx, chainID, providerAddr) if found { + //if isOptedIn(providerAddr) && !isToBeOptedOut(providerAddr) { + // only generate a removal (i.e., power 0) if the validator was previously opted in newUpdates = append(newUpdates, abci.ValidatorUpdate{ PubKey: prevConsumerKey, Power: 0, }) + //} newConsumerKey, found := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) if !found { diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go index e1bf9cc14f..bf76e7d7c8 100644 --- a/x/ccv/provider/keeper/partial_set_security.go +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -7,12 +7,6 @@ import ( "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ) -type OptedInValidator struct { - ProviderAddr types.ProviderConsAddress - // block height the validator opted in at - BlockHeight uint64 -} - func (k Keeper) HandleOptIn(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress, consumerKey *string) error { if !k.IsConsumerProposedOrRegistered(ctx, chainID) { return errorsmod.Wrapf( diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go index 4831723bec..1af5f58c7c 100644 --- a/x/ccv/provider/keeper/partial_set_security_test.go +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -1,6 +1,8 @@ package keeper_test import ( + "testing" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" sdk "github.com/cosmos/cosmos-sdk/types" @@ -10,7 +12,6 @@ import ( ccvtypes "github.com/cosmos/interchain-security/v4/x/ccv/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - "testing" ) func TestHandleOptIn(t *testing.T) { @@ -32,7 +33,9 @@ func TestHandleOptIn(t *testing.T) { require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) // if validator (`providerAddr`) is already opted in, then the validator cannot be opted in - providerKeeper.SetOptedIn(ctx, "chainID", providerAddr, 1) + + providerKeeper.SetOptedIn(ctx, "chainID", + types.OptedInValidator{ProviderAddr: providerAddr.ToSdkConsAddr(), BlockHeight: 1, Power: 1, PublicKey: []byte{1}}) providerKeeper.HandleOptIn(ctx, "chainID", providerAddr, nil) require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) } diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index ed86ea0804..9fb9fc9caf 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -542,9 +542,9 @@ func TopNKey(chainID string) []byte { } // OptedInKey returns the key of consumer chain `chainID` and validator with `providerAddr` -func OptedInKey(chainID string, providerAddr ProviderConsAddress) []byte { +func OptedInKey(chainID string, providerAddr []byte) []byte { prefix := ChainIdWithLenKey(OptedInBytePrefix, chainID) - return append(prefix, providerAddr.ToSdkConsAddr().Bytes()...) + return append(prefix, providerAddr...) } // ToBeOptedInKey returns the key of consumer chain `chainID` and validator with `providerAddr` diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index 20733b4fa0..6e1a361df9 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -1392,7 +1392,9 @@ func (m *ConsumerAddrsToPrune) GetConsumerAddrs() *AddressList { return nil } -// ConsumerRewardsAllocation is used to serialize the allocation of consumer chain rewards +// ConsumerRewardsAllocation stores the rewards allocated by a consumer chain +// to the consumer rewards pool. It is used to allocate the tokens to the consumer +// opted-in validators and the community pool during BeginBlock. type ConsumerRewardsAllocation struct { Rewards github_com_cosmos_cosmos_sdk_types.DecCoins `protobuf:"bytes,1,rep,name=rewards,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.DecCoins" json:"rewards"` } @@ -1437,6 +1439,80 @@ func (m *ConsumerRewardsAllocation) GetRewards() github_com_cosmos_cosmos_sdk_ty return nil } +// OptedInValidator is used to store a opted-in validator +// to a consumer chain with the following mapping: (chainID, providerAddr) -> optedInValidator +type OptedInValidator struct { + // validator address + ProviderAddr []byte `protobuf:"bytes,1,opt,name=provider_addr,json=providerAddr,proto3" json:"provider_addr,omitempty"` + // block height at which the validator opted-in + BlockHeight int64 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + // validator voting power at the block it opted-in + Power int64 `protobuf:"varint,3,opt,name=power,proto3" json:"power,omitempty"` + // public key used by the validator on the consumer + PublicKey []byte `protobuf:"bytes,4,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` +} + +func (m *OptedInValidator) Reset() { *m = OptedInValidator{} } +func (m *OptedInValidator) String() string { return proto.CompactTextString(m) } +func (*OptedInValidator) ProtoMessage() {} +func (*OptedInValidator) Descriptor() ([]byte, []int) { + return fileDescriptor_f22ec409a72b7b72, []int{23} +} +func (m *OptedInValidator) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *OptedInValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_OptedInValidator.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 *OptedInValidator) XXX_Merge(src proto.Message) { + xxx_messageInfo_OptedInValidator.Merge(m, src) +} +func (m *OptedInValidator) XXX_Size() int { + return m.Size() +} +func (m *OptedInValidator) XXX_DiscardUnknown() { + xxx_messageInfo_OptedInValidator.DiscardUnknown(m) +} + +var xxx_messageInfo_OptedInValidator proto.InternalMessageInfo + +func (m *OptedInValidator) GetProviderAddr() []byte { + if m != nil { + return m.ProviderAddr + } + return nil +} + +func (m *OptedInValidator) GetBlockHeight() int64 { + if m != nil { + return m.BlockHeight + } + return 0 +} + +func (m *OptedInValidator) GetPower() int64 { + if m != nil { + return m.Power + } + return 0 +} + +func (m *OptedInValidator) GetPublicKey() []byte { + if m != nil { + return m.PublicKey + } + return nil +} + func init() { proto.RegisterType((*ConsumerAdditionProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerAdditionProposal") proto.RegisterType((*ConsumerRemovalProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerRemovalProposal") @@ -1461,6 +1537,7 @@ func init() { proto.RegisterType((*ValidatorByConsumerAddr)(nil), "interchain_security.ccv.provider.v1.ValidatorByConsumerAddr") proto.RegisterType((*ConsumerAddrsToPrune)(nil), "interchain_security.ccv.provider.v1.ConsumerAddrsToPrune") proto.RegisterType((*ConsumerRewardsAllocation)(nil), "interchain_security.ccv.provider.v1.ConsumerRewardsAllocation") + proto.RegisterType((*OptedInValidator)(nil), "interchain_security.ccv.provider.v1.OptedInValidator") } func init() { @@ -1468,119 +1545,122 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1787 bytes of a gzipped FileDescriptorProto + // 1833 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x73, 0x1c, 0x47, 0x15, 0xd7, 0x68, 0x57, 0x1f, 0xfb, 0x56, 0x9f, 0x23, 0x25, 0x1e, 0x19, 0xb1, 0x92, 0x27, 0x24, 0x08, 0x5c, 0x9e, 0x41, 0x0a, 0x54, 0xb9, 0x5c, 0xa4, 0x52, 0xd2, 0xca, 0x89, 0x65, 0x11, 0x5b, 0x19, 0x09, 0xb9, 0x80, 0xc3, 0x54, 0x6f, 0x4f, 0x7b, 0xb7, 0x4b, 0xb3, 0xd3, 0xe3, 0xee, 0xde, - 0x71, 0xf6, 0xc2, 0x99, 0x0b, 0x55, 0xe1, 0x96, 0xe2, 0x42, 0xe0, 0x44, 0x71, 0x81, 0x2b, 0xff, - 0x41, 0x8e, 0x39, 0x72, 0x4a, 0x28, 0xfb, 0xc0, 0x81, 0x2b, 0x7f, 0x00, 0xd5, 0x3d, 0x9f, 0xbb, - 0x92, 0xcc, 0xba, 0x42, 0x2e, 0xd2, 0xcc, 0xeb, 0xf7, 0x7e, 0xef, 0x75, 0xbf, 0xf7, 0x7e, 0xaf, - 0x77, 0x60, 0x8f, 0x46, 0x92, 0x70, 0xdc, 0x43, 0x34, 0xf2, 0x05, 0xc1, 0x03, 0x4e, 0xe5, 0xd0, - 0xc5, 0x38, 0x71, 0x63, 0xce, 0x12, 0x1a, 0x10, 0xee, 0x26, 0xbb, 0xc5, 0xb3, 0x13, 0x73, 0x26, - 0x99, 0xf9, 0xd6, 0x15, 0x36, 0x0e, 0xc6, 0x89, 0x53, 0xe8, 0x25, 0xbb, 0x37, 0xdf, 0xbe, 0x0e, - 0x38, 0xd9, 0x75, 0x9f, 0x53, 0x4e, 0x52, 0xac, 0x9b, 0xeb, 0x5d, 0xd6, 0x65, 0xfa, 0xd1, 0x55, - 0x4f, 0x99, 0x74, 0xab, 0xcb, 0x58, 0x37, 0x24, 0xae, 0x7e, 0xeb, 0x0c, 0x9e, 0xba, 0x92, 0xf6, - 0x89, 0x90, 0xa8, 0x1f, 0x67, 0x0a, 0xad, 0x71, 0x85, 0x60, 0xc0, 0x91, 0xa4, 0x2c, 0xca, 0x01, - 0x68, 0x07, 0xbb, 0x98, 0x71, 0xe2, 0xe2, 0x90, 0x92, 0x48, 0x2a, 0xaf, 0xe9, 0x53, 0xa6, 0xe0, - 0x2a, 0x85, 0x90, 0x76, 0x7b, 0x32, 0x15, 0x0b, 0x57, 0x92, 0x28, 0x20, 0xbc, 0x4f, 0x53, 0xe5, - 0xf2, 0x2d, 0x33, 0xd8, 0xac, 0xac, 0x63, 0x3e, 0x8c, 0x25, 0x73, 0x2f, 0xc8, 0x50, 0x64, 0xab, - 0xef, 0x60, 0x26, 0xfa, 0x4c, 0xb8, 0x44, 0xed, 0x3f, 0xc2, 0xc4, 0x4d, 0x76, 0x3b, 0x44, 0xa2, - 0xdd, 0x42, 0x90, 0xc7, 0x9d, 0xe9, 0x75, 0x90, 0x28, 0x75, 0x30, 0xa3, 0x79, 0xdc, 0xab, 0xa8, - 0x4f, 0x23, 0xe6, 0xea, 0xbf, 0xa9, 0xc8, 0xfe, 0xcf, 0x2c, 0x58, 0x6d, 0x16, 0x89, 0x41, 0x9f, - 0xf0, 0xfd, 0x20, 0xa0, 0x6a, 0x97, 0x27, 0x9c, 0xc5, 0x4c, 0xa0, 0xd0, 0x5c, 0x87, 0x19, 0x49, - 0x65, 0x48, 0x2c, 0x63, 0xdb, 0xd8, 0x69, 0x78, 0xe9, 0x8b, 0xb9, 0x0d, 0xcd, 0x80, 0x08, 0xcc, - 0x69, 0xac, 0x94, 0xad, 0x69, 0xbd, 0x56, 0x15, 0x99, 0x1b, 0x30, 0x9f, 0xa6, 0x86, 0x06, 0x56, - 0x4d, 0x2f, 0xcf, 0xe9, 0xf7, 0xa3, 0xc0, 0xfc, 0x10, 0x96, 0x68, 0x44, 0x25, 0x45, 0xa1, 0xdf, - 0x23, 0xea, 0x80, 0xac, 0xfa, 0xb6, 0xb1, 0xd3, 0xdc, 0xbb, 0xe9, 0xd0, 0x0e, 0x76, 0xd4, 0x99, - 0x3a, 0xd9, 0x49, 0x26, 0xbb, 0xce, 0x03, 0xad, 0x71, 0x50, 0xff, 0xe2, 0xab, 0xad, 0x29, 0x6f, - 0x31, 0xb3, 0x4b, 0x85, 0xe6, 0x2d, 0x58, 0xe8, 0x92, 0x88, 0x08, 0x2a, 0xfc, 0x1e, 0x12, 0x3d, - 0x6b, 0x66, 0xdb, 0xd8, 0x59, 0xf0, 0x9a, 0x99, 0xec, 0x01, 0x12, 0x3d, 0x73, 0x0b, 0x9a, 0x1d, - 0x1a, 0x21, 0x3e, 0x4c, 0x35, 0x66, 0xb5, 0x06, 0xa4, 0x22, 0xad, 0xd0, 0x06, 0x10, 0x31, 0x7a, - 0x1e, 0xf9, 0xaa, 0x00, 0xac, 0xb9, 0x2c, 0x90, 0x34, 0xf9, 0x4e, 0x9e, 0x7c, 0xe7, 0x2c, 0xaf, - 0x8e, 0x83, 0x79, 0x15, 0xc8, 0xa7, 0x5f, 0x6f, 0x19, 0x5e, 0x43, 0xdb, 0xa9, 0x15, 0xf3, 0x11, - 0xac, 0x0c, 0xa2, 0x0e, 0x8b, 0x02, 0x1a, 0x75, 0xfd, 0x98, 0x70, 0xca, 0x02, 0x6b, 0x5e, 0x43, - 0x6d, 0x5c, 0x82, 0x3a, 0xcc, 0xea, 0x28, 0x45, 0xfa, 0x4c, 0x21, 0x2d, 0x17, 0xc6, 0x27, 0xda, - 0xd6, 0xfc, 0x18, 0x4c, 0x8c, 0x13, 0x1d, 0x12, 0x1b, 0xc8, 0x1c, 0xb1, 0x31, 0x39, 0xe2, 0x0a, - 0xc6, 0xc9, 0x59, 0x6a, 0x9d, 0x41, 0xfe, 0x0a, 0x6e, 0x48, 0x8e, 0x22, 0xf1, 0x94, 0xf0, 0x71, - 0x5c, 0x98, 0x1c, 0xf7, 0x8d, 0x1c, 0x63, 0x14, 0xfc, 0x01, 0x6c, 0xe3, 0xac, 0x80, 0x7c, 0x4e, - 0x02, 0x2a, 0x24, 0xa7, 0x9d, 0x81, 0xb2, 0xf5, 0x9f, 0x72, 0x84, 0x75, 0x8d, 0x34, 0x75, 0x11, - 0xb4, 0x72, 0x3d, 0x6f, 0x44, 0xed, 0x83, 0x4c, 0xcb, 0x7c, 0x0c, 0xdf, 0xeb, 0x84, 0x0c, 0x5f, - 0x08, 0x15, 0x9c, 0x3f, 0x82, 0xa4, 0x5d, 0xf7, 0xa9, 0x10, 0x0a, 0x6d, 0x61, 0xdb, 0xd8, 0xa9, - 0x79, 0xb7, 0x52, 0xdd, 0x13, 0xc2, 0x0f, 0x2b, 0x9a, 0x67, 0x15, 0x45, 0xf3, 0x0e, 0x98, 0x3d, - 0x2a, 0x24, 0xe3, 0x14, 0xa3, 0xd0, 0x27, 0x91, 0xe4, 0x94, 0x08, 0x6b, 0x51, 0x9b, 0xaf, 0x96, - 0x2b, 0xf7, 0xd3, 0x05, 0xf3, 0x21, 0xdc, 0xba, 0xd6, 0xa9, 0x8f, 0x7b, 0x28, 0x8a, 0x48, 0x68, - 0x2d, 0xe9, 0xad, 0x6c, 0x05, 0xd7, 0xf8, 0x6c, 0xa7, 0x6a, 0xe6, 0x1a, 0xcc, 0x48, 0x16, 0xfb, - 0x8f, 0xac, 0xe5, 0x6d, 0x63, 0x67, 0xd1, 0xab, 0x4b, 0x16, 0x3f, 0xba, 0x37, 0xff, 0x9b, 0xcf, - 0xb7, 0xa6, 0x3e, 0xfb, 0x7c, 0x6b, 0xca, 0xfe, 0xab, 0x01, 0x37, 0xda, 0xc5, 0x69, 0xf4, 0x59, - 0x82, 0xc2, 0x6f, 0xb3, 0xeb, 0xf6, 0xa1, 0x21, 0x54, 0x38, 0xba, 0xce, 0xeb, 0xaf, 0x51, 0xe7, - 0xf3, 0xca, 0x4c, 0x2d, 0xd8, 0x7f, 0x30, 0x60, 0xfd, 0xfe, 0xb3, 0x01, 0x4d, 0x18, 0x46, 0xff, - 0x17, 0x92, 0x38, 0x86, 0x45, 0x52, 0xc1, 0x13, 0x56, 0x6d, 0xbb, 0xb6, 0xd3, 0xdc, 0x7b, 0xdb, - 0x49, 0x49, 0xcc, 0x29, 0xb8, 0x2d, 0x23, 0x32, 0xa7, 0xea, 0xdd, 0x1b, 0xb5, 0xbd, 0x37, 0x6d, - 0x19, 0xf6, 0x9f, 0x0c, 0xb8, 0xa9, 0x8e, 0xbf, 0x4b, 0x3c, 0xf2, 0x1c, 0xf1, 0xe0, 0x90, 0x44, - 0xac, 0x2f, 0xbe, 0x71, 0x9c, 0x36, 0x2c, 0x06, 0x1a, 0xc9, 0x97, 0xcc, 0x47, 0x41, 0xa0, 0xe3, - 0xd4, 0x3a, 0x4a, 0x78, 0xc6, 0xf6, 0x83, 0xc0, 0xdc, 0x81, 0x95, 0x52, 0x87, 0xab, 0x7c, 0xaa, - 0x63, 0x56, 0x6a, 0x4b, 0xb9, 0x9a, 0xce, 0x32, 0xb1, 0xff, 0x6d, 0xc0, 0xca, 0x87, 0x21, 0xeb, - 0xa0, 0xf0, 0x34, 0x44, 0xa2, 0xa7, 0x4a, 0x6f, 0xa8, 0xd2, 0xc3, 0x49, 0xd6, 0xf3, 0x3a, 0xbc, - 0x89, 0xd3, 0xa3, 0xcc, 0x34, 0x0b, 0xbd, 0x0f, 0xab, 0x45, 0x17, 0x16, 0x55, 0xa0, 0x77, 0x73, - 0xb0, 0xf6, 0xe2, 0xab, 0xad, 0xe5, 0xbc, 0xd8, 0xda, 0xba, 0x22, 0x0e, 0xbd, 0x65, 0x3c, 0x22, - 0x08, 0xcc, 0x16, 0x34, 0x69, 0x07, 0xfb, 0x82, 0x3c, 0xf3, 0xa3, 0x41, 0x5f, 0x17, 0x50, 0xdd, - 0x6b, 0xd0, 0x0e, 0x3e, 0x25, 0xcf, 0x1e, 0x0d, 0xfa, 0xe6, 0xbb, 0xf0, 0x66, 0x3e, 0x80, 0xfd, - 0x04, 0x85, 0xbe, 0xb2, 0x57, 0xc7, 0xc1, 0x75, 0x3d, 0x2d, 0x78, 0x6b, 0xf9, 0xea, 0x39, 0x0a, - 0x95, 0xb3, 0xfd, 0x20, 0xe0, 0xf6, 0xdf, 0x67, 0x60, 0xf6, 0x04, 0x71, 0xd4, 0x17, 0xe6, 0x19, - 0x2c, 0x4b, 0xd2, 0x8f, 0x43, 0x24, 0x89, 0x9f, 0x32, 0x7c, 0xb6, 0xd3, 0xdb, 0x9a, 0xf9, 0xab, - 0xc3, 0xd2, 0xa9, 0x8c, 0xc7, 0x64, 0xd7, 0x69, 0x6b, 0xe9, 0xa9, 0x44, 0x92, 0x78, 0x4b, 0x39, - 0x46, 0x2a, 0x34, 0xef, 0x82, 0x25, 0xf9, 0x40, 0xc8, 0x92, 0x7b, 0x4b, 0xd2, 0x49, 0x73, 0xf9, - 0x66, 0xbe, 0x9e, 0xd2, 0x55, 0x41, 0x36, 0x57, 0xd3, 0x6c, 0xed, 0x9b, 0xd0, 0xec, 0x29, 0xac, - 0xa9, 0x19, 0x35, 0x8e, 0x59, 0x9f, 0x1c, 0x73, 0x55, 0xd9, 0x8f, 0x82, 0x7e, 0x0c, 0x66, 0x22, - 0xf0, 0x38, 0xe6, 0xcc, 0x6b, 0xc4, 0x99, 0x08, 0x3c, 0x0a, 0x19, 0xc0, 0xa6, 0x50, 0xc5, 0xe7, - 0xf7, 0x89, 0xd4, 0xa4, 0x1d, 0x87, 0x24, 0xa2, 0xa2, 0x97, 0x83, 0xcf, 0x4e, 0x0e, 0xbe, 0xa1, - 0x81, 0x3e, 0x52, 0x38, 0x5e, 0x0e, 0x93, 0x79, 0x69, 0x43, 0xeb, 0x6a, 0x2f, 0x45, 0x82, 0xe6, - 0x74, 0x82, 0xbe, 0x73, 0x05, 0x44, 0x91, 0x25, 0x01, 0xef, 0x54, 0x86, 0x8b, 0xea, 0x6a, 0x5f, - 0x37, 0x94, 0xcf, 0x49, 0x57, 0x31, 0x30, 0x4a, 0xe7, 0x0c, 0x21, 0xc5, 0x80, 0xcc, 0xd8, 0x43, - 0x5d, 0x81, 0x0a, 0xe6, 0x68, 0x33, 0x1a, 0x65, 0xb7, 0x08, 0xbb, 0x9c, 0x41, 0x05, 0x47, 0x78, - 0x15, 0xac, 0x0f, 0x08, 0x79, 0x58, 0x9f, 0x9f, 0x5f, 0x69, 0xd8, 0x3f, 0x80, 0x86, 0x6e, 0xd1, - 0x7d, 0x7c, 0x21, 0xcc, 0x4d, 0x68, 0xa8, 0x5a, 0x27, 0x42, 0x10, 0x61, 0x19, 0xba, 0xb3, 0x4b, - 0x81, 0x2d, 0x61, 0xe3, 0xba, 0x3b, 0x94, 0x30, 0x9f, 0xc0, 0x5c, 0x4c, 0xf4, 0x80, 0xd7, 0x86, - 0xcd, 0xbd, 0xf7, 0x9c, 0x09, 0x6e, 0xb8, 0xce, 0x75, 0x80, 0x5e, 0x8e, 0x66, 0xf3, 0xf2, 0xe6, - 0x36, 0x36, 0x42, 0x84, 0x79, 0x3e, 0xee, 0xf4, 0xa7, 0xaf, 0xe5, 0x74, 0x0c, 0xaf, 0xf4, 0x79, - 0x1b, 0x9a, 0xfb, 0xe9, 0xb6, 0x7f, 0x46, 0x85, 0xbc, 0x7c, 0x2c, 0x0b, 0xd5, 0x63, 0x79, 0x08, - 0x4b, 0xd9, 0x38, 0x3c, 0x63, 0x9a, 0x66, 0xcc, 0xef, 0x02, 0x64, 0x73, 0x54, 0xd1, 0x53, 0x4a, - 0xc4, 0x8d, 0x4c, 0x72, 0x14, 0x8c, 0x4c, 0xb0, 0xe9, 0x91, 0x09, 0x66, 0x7b, 0xb0, 0x7c, 0x2e, - 0xf0, 0xcf, 0xf3, 0xbb, 0xd2, 0xe3, 0x58, 0x98, 0x6f, 0xc0, 0xac, 0xea, 0x8c, 0x0c, 0xa8, 0xee, - 0xcd, 0x24, 0x02, 0x1f, 0x69, 0x2e, 0x2e, 0xef, 0x63, 0x2c, 0xf6, 0x69, 0x20, 0xac, 0xe9, 0xed, - 0xda, 0x4e, 0xdd, 0x5b, 0x1a, 0x94, 0xe6, 0x47, 0x81, 0xb0, 0x7f, 0x01, 0xcd, 0x0a, 0xa0, 0xb9, - 0x04, 0xd3, 0x05, 0xd6, 0x34, 0x0d, 0xcc, 0x7b, 0xb0, 0x51, 0x02, 0x8d, 0x92, 0x6b, 0x8a, 0xd8, - 0xf0, 0x6e, 0x14, 0x0a, 0x23, 0xfc, 0x2a, 0xec, 0xc7, 0xb0, 0x7e, 0x54, 0xb6, 0x72, 0x41, 0xdd, - 0x23, 0x3b, 0x34, 0x46, 0x67, 0xf4, 0x26, 0x34, 0x8a, 0xdf, 0x21, 0x7a, 0xf7, 0x75, 0xaf, 0x14, - 0xd8, 0x7d, 0x58, 0x39, 0x17, 0xf8, 0x94, 0x44, 0x41, 0x09, 0x76, 0xcd, 0x01, 0x1c, 0x8c, 0x03, - 0x4d, 0x7c, 0xa9, 0x2d, 0xdd, 0x31, 0xd8, 0x38, 0x47, 0x21, 0x0d, 0x90, 0x64, 0xfc, 0x94, 0xc8, - 0x74, 0xac, 0x9e, 0x20, 0x7c, 0x41, 0xa4, 0x30, 0x3d, 0xa8, 0x87, 0x54, 0xc8, 0xac, 0xb2, 0xee, - 0x5e, 0x5b, 0x59, 0xc9, 0xae, 0x73, 0x1d, 0xc8, 0x21, 0x92, 0x28, 0xeb, 0x48, 0x8d, 0x65, 0x7f, - 0x1f, 0xd6, 0x3e, 0x42, 0x72, 0xc0, 0x49, 0x30, 0x92, 0xe3, 0x15, 0xa8, 0xa9, 0xfc, 0x19, 0x3a, - 0x7f, 0xea, 0x51, 0x4d, 0x79, 0xeb, 0xfe, 0x27, 0x31, 0xe3, 0x92, 0x04, 0x97, 0x4e, 0xe4, 0x15, - 0xc7, 0x7b, 0x01, 0x6b, 0xea, 0xb0, 0x04, 0x89, 0x02, 0xbf, 0xd8, 0x67, 0x9a, 0xc7, 0xe6, 0xde, - 0x4f, 0x26, 0xea, 0x8e, 0x71, 0x77, 0xd9, 0x06, 0x56, 0x93, 0x31, 0xb9, 0xb0, 0x7f, 0x67, 0x80, - 0x75, 0x4c, 0x86, 0xfb, 0x42, 0xd0, 0x6e, 0xd4, 0x27, 0x91, 0x54, 0xcc, 0x86, 0x30, 0x51, 0x8f, - 0xe6, 0x5b, 0xb0, 0x58, 0x4c, 0x52, 0x3d, 0x40, 0x0d, 0x3d, 0x40, 0x17, 0x72, 0xa1, 0x6a, 0x30, - 0xf3, 0x1e, 0x40, 0xcc, 0x49, 0xe2, 0x63, 0xff, 0x82, 0x0c, 0xb3, 0x2c, 0x6e, 0x56, 0x07, 0x63, - 0xfa, 0x2b, 0xd1, 0x39, 0x19, 0x74, 0x42, 0x8a, 0x8f, 0xc9, 0xd0, 0x9b, 0x57, 0xfa, 0xed, 0x63, - 0x32, 0x54, 0x37, 0x9d, 0x98, 0x3d, 0x27, 0x5c, 0x4f, 0xb3, 0x9a, 0x97, 0xbe, 0xd8, 0xbf, 0x37, - 0xe0, 0x46, 0x91, 0x8e, 0xbc, 0x5c, 0x4f, 0x06, 0x1d, 0x65, 0xf1, 0x8a, 0x73, 0xbb, 0x14, 0xed, - 0xf4, 0x15, 0xd1, 0xbe, 0x0f, 0x0b, 0x45, 0x83, 0xa8, 0x78, 0x6b, 0x13, 0xc4, 0xdb, 0xcc, 0x2d, - 0x8e, 0xc9, 0xd0, 0xfe, 0x75, 0x25, 0xb6, 0x83, 0x61, 0x85, 0xfb, 0xf8, 0xff, 0x88, 0xad, 0x70, - 0x5b, 0x8d, 0x0d, 0x57, 0xed, 0x2f, 0x6d, 0xa0, 0x76, 0x79, 0x03, 0xf6, 0x1f, 0x0d, 0x58, 0xaf, - 0x7a, 0x15, 0x67, 0xec, 0x84, 0x0f, 0x22, 0xf2, 0x2a, 0xef, 0x65, 0xfb, 0x4d, 0x57, 0xdb, 0xef, - 0x09, 0x2c, 0x8d, 0x04, 0x25, 0xb2, 0xd3, 0xf8, 0xd1, 0x44, 0x35, 0x56, 0x61, 0x57, 0x6f, 0xb1, - 0xba, 0x0f, 0x61, 0xff, 0xd6, 0x28, 0xc7, 0x4c, 0x3a, 0xbd, 0xc4, 0x7e, 0x18, 0x66, 0x57, 0x60, - 0x33, 0x86, 0xb9, 0x74, 0x40, 0x8a, 0xac, 0x2f, 0x37, 0xaf, 0x1c, 0x85, 0x87, 0x04, 0xeb, 0x69, - 0x78, 0x57, 0x95, 0xee, 0x5f, 0xbe, 0xde, 0xba, 0xdd, 0xa5, 0xb2, 0x37, 0xe8, 0x38, 0x98, 0xf5, - 0xdd, 0xec, 0xeb, 0x41, 0xfa, 0xef, 0x8e, 0x08, 0x2e, 0x5c, 0x39, 0x8c, 0x89, 0xc8, 0x6d, 0xc4, - 0x9f, 0xff, 0xf5, 0xb7, 0x1f, 0x1a, 0x5e, 0xee, 0xe6, 0xe0, 0xc9, 0x17, 0x2f, 0x5a, 0xc6, 0x97, - 0x2f, 0x5a, 0xc6, 0x3f, 0x5f, 0xb4, 0x8c, 0x4f, 0x5f, 0xb6, 0xa6, 0xbe, 0x7c, 0xd9, 0x9a, 0xfa, - 0xc7, 0xcb, 0xd6, 0xd4, 0x2f, 0xdf, 0xbb, 0x0c, 0x5a, 0xee, 0xfd, 0x4e, 0xf1, 0xbd, 0x26, 0xf9, - 0xb1, 0xfb, 0xc9, 0xe8, 0xd7, 0x20, 0xed, 0xaf, 0x33, 0xab, 0x59, 0xea, 0xdd, 0xff, 0x06, 0x00, - 0x00, 0xff, 0xff, 0x2a, 0x2c, 0xd4, 0x2e, 0x3e, 0x12, 0x00, 0x00, + 0x71, 0xf6, 0xc2, 0x99, 0x0b, 0x45, 0xb8, 0xa5, 0xb8, 0x10, 0x38, 0x51, 0x5c, 0xe0, 0xca, 0x7f, + 0x90, 0x63, 0x8e, 0x9c, 0x12, 0xca, 0x3e, 0x70, 0xe0, 0xca, 0x1f, 0x40, 0x75, 0xcf, 0xe7, 0xae, + 0x24, 0xb3, 0xae, 0xc0, 0x45, 0x9a, 0x79, 0xfd, 0xde, 0xef, 0x7d, 0xbf, 0xd7, 0x3b, 0xb0, 0x47, + 0x23, 0x49, 0x38, 0xee, 0x21, 0x1a, 0xf9, 0x82, 0xe0, 0x01, 0xa7, 0x72, 0xe8, 0x62, 0x9c, 0xb8, + 0x31, 0x67, 0x09, 0x0d, 0x08, 0x77, 0x93, 0xdd, 0xe2, 0xd9, 0x89, 0x39, 0x93, 0xcc, 0x7c, 0xeb, + 0x0a, 0x19, 0x07, 0xe3, 0xc4, 0x29, 0xf8, 0x92, 0xdd, 0x9b, 0x6f, 0x5f, 0x07, 0x9c, 0xec, 0xba, + 0xcf, 0x29, 0x27, 0x29, 0xd6, 0xcd, 0xf5, 0x2e, 0xeb, 0x32, 0xfd, 0xe8, 0xaa, 0xa7, 0x8c, 0xba, + 0xd5, 0x65, 0xac, 0x1b, 0x12, 0x57, 0xbf, 0x75, 0x06, 0x4f, 0x5d, 0x49, 0xfb, 0x44, 0x48, 0xd4, + 0x8f, 0x33, 0x86, 0xd6, 0x38, 0x43, 0x30, 0xe0, 0x48, 0x52, 0x16, 0xe5, 0x00, 0xb4, 0x83, 0x5d, + 0xcc, 0x38, 0x71, 0x71, 0x48, 0x49, 0x24, 0x95, 0xd6, 0xf4, 0x29, 0x63, 0x70, 0x15, 0x43, 0x48, + 0xbb, 0x3d, 0x99, 0x92, 0x85, 0x2b, 0x49, 0x14, 0x10, 0xde, 0xa7, 0x29, 0x73, 0xf9, 0x96, 0x09, + 0x6c, 0x56, 0xce, 0x31, 0x1f, 0xc6, 0x92, 0xb9, 0x17, 0x64, 0x28, 0xb2, 0xd3, 0x77, 0x30, 0x13, + 0x7d, 0x26, 0x5c, 0xa2, 0xfc, 0x8f, 0x30, 0x71, 0x93, 0xdd, 0x0e, 0x91, 0x68, 0xb7, 0x20, 0xe4, + 0x76, 0x67, 0x7c, 0x1d, 0x24, 0x4a, 0x1e, 0xcc, 0x68, 0x6e, 0xf7, 0x2a, 0xea, 0xd3, 0x88, 0xb9, + 0xfa, 0x6f, 0x4a, 0xb2, 0xff, 0x3d, 0x0b, 0x56, 0x9b, 0x45, 0x62, 0xd0, 0x27, 0x7c, 0x3f, 0x08, + 0xa8, 0xf2, 0xf2, 0x84, 0xb3, 0x98, 0x09, 0x14, 0x9a, 0xeb, 0x30, 0x23, 0xa9, 0x0c, 0x89, 0x65, + 0x6c, 0x1b, 0x3b, 0x0d, 0x2f, 0x7d, 0x31, 0xb7, 0xa1, 0x19, 0x10, 0x81, 0x39, 0x8d, 0x15, 0xb3, + 0x35, 0xad, 0xcf, 0xaa, 0x24, 0x73, 0x03, 0xe6, 0xd3, 0xd4, 0xd0, 0xc0, 0xaa, 0xe9, 0xe3, 0x39, + 0xfd, 0x7e, 0x14, 0x98, 0x1f, 0xc2, 0x12, 0x8d, 0xa8, 0xa4, 0x28, 0xf4, 0x7b, 0x44, 0x05, 0xc8, + 0xaa, 0x6f, 0x1b, 0x3b, 0xcd, 0xbd, 0x9b, 0x0e, 0xed, 0x60, 0x47, 0xc5, 0xd4, 0xc9, 0x22, 0x99, + 0xec, 0x3a, 0x0f, 0x34, 0xc7, 0x41, 0xfd, 0x8b, 0xaf, 0xb6, 0xa6, 0xbc, 0xc5, 0x4c, 0x2e, 0x25, + 0x9a, 0xb7, 0x60, 0xa1, 0x4b, 0x22, 0x22, 0xa8, 0xf0, 0x7b, 0x48, 0xf4, 0xac, 0x99, 0x6d, 0x63, + 0x67, 0xc1, 0x6b, 0x66, 0xb4, 0x07, 0x48, 0xf4, 0xcc, 0x2d, 0x68, 0x76, 0x68, 0x84, 0xf8, 0x30, + 0xe5, 0x98, 0xd5, 0x1c, 0x90, 0x92, 0x34, 0x43, 0x1b, 0x40, 0xc4, 0xe8, 0x79, 0xe4, 0xab, 0x02, + 0xb0, 0xe6, 0x32, 0x43, 0xd2, 0xe4, 0x3b, 0x79, 0xf2, 0x9d, 0xb3, 0xbc, 0x3a, 0x0e, 0xe6, 0x95, + 0x21, 0x9f, 0x7e, 0xbd, 0x65, 0x78, 0x0d, 0x2d, 0xa7, 0x4e, 0xcc, 0x47, 0xb0, 0x32, 0x88, 0x3a, + 0x2c, 0x0a, 0x68, 0xd4, 0xf5, 0x63, 0xc2, 0x29, 0x0b, 0xac, 0x79, 0x0d, 0xb5, 0x71, 0x09, 0xea, + 0x30, 0xab, 0xa3, 0x14, 0xe9, 0x33, 0x85, 0xb4, 0x5c, 0x08, 0x9f, 0x68, 0x59, 0xf3, 0x63, 0x30, + 0x31, 0x4e, 0xb4, 0x49, 0x6c, 0x20, 0x73, 0xc4, 0xc6, 0xe4, 0x88, 0x2b, 0x18, 0x27, 0x67, 0xa9, + 0x74, 0x06, 0xf9, 0x0b, 0xb8, 0x21, 0x39, 0x8a, 0xc4, 0x53, 0xc2, 0xc7, 0x71, 0x61, 0x72, 0xdc, + 0x37, 0x72, 0x8c, 0x51, 0xf0, 0x07, 0xb0, 0x8d, 0xb3, 0x02, 0xf2, 0x39, 0x09, 0xa8, 0x90, 0x9c, + 0x76, 0x06, 0x4a, 0xd6, 0x7f, 0xca, 0x11, 0xd6, 0x35, 0xd2, 0xd4, 0x45, 0xd0, 0xca, 0xf9, 0xbc, + 0x11, 0xb6, 0x0f, 0x32, 0x2e, 0xf3, 0x31, 0x7c, 0xa7, 0x13, 0x32, 0x7c, 0x21, 0x94, 0x71, 0xfe, + 0x08, 0x92, 0x56, 0xdd, 0xa7, 0x42, 0x28, 0xb4, 0x85, 0x6d, 0x63, 0xa7, 0xe6, 0xdd, 0x4a, 0x79, + 0x4f, 0x08, 0x3f, 0xac, 0x70, 0x9e, 0x55, 0x18, 0xcd, 0x3b, 0x60, 0xf6, 0xa8, 0x90, 0x8c, 0x53, + 0x8c, 0x42, 0x9f, 0x44, 0x92, 0x53, 0x22, 0xac, 0x45, 0x2d, 0xbe, 0x5a, 0x9e, 0xdc, 0x4f, 0x0f, + 0xcc, 0x87, 0x70, 0xeb, 0x5a, 0xa5, 0x3e, 0xee, 0xa1, 0x28, 0x22, 0xa1, 0xb5, 0xa4, 0x5d, 0xd9, + 0x0a, 0xae, 0xd1, 0xd9, 0x4e, 0xd9, 0xcc, 0x35, 0x98, 0x91, 0x2c, 0xf6, 0x1f, 0x59, 0xcb, 0xdb, + 0xc6, 0xce, 0xa2, 0x57, 0x97, 0x2c, 0x7e, 0x74, 0x6f, 0xfe, 0x57, 0x9f, 0x6f, 0x4d, 0x7d, 0xf6, + 0xf9, 0xd6, 0x94, 0xfd, 0x17, 0x03, 0x6e, 0xb4, 0x8b, 0x68, 0xf4, 0x59, 0x82, 0xc2, 0xff, 0x67, + 0xd7, 0xed, 0x43, 0x43, 0x28, 0x73, 0x74, 0x9d, 0xd7, 0x5f, 0xa3, 0xce, 0xe7, 0x95, 0x98, 0x3a, + 0xb0, 0x7f, 0x6f, 0xc0, 0xfa, 0xfd, 0x67, 0x03, 0x9a, 0x30, 0x8c, 0xfe, 0x27, 0x43, 0xe2, 0x18, + 0x16, 0x49, 0x05, 0x4f, 0x58, 0xb5, 0xed, 0xda, 0x4e, 0x73, 0xef, 0x6d, 0x27, 0x1d, 0x62, 0x4e, + 0x31, 0xdb, 0xb2, 0x41, 0xe6, 0x54, 0xb5, 0x7b, 0xa3, 0xb2, 0xf7, 0xa6, 0x2d, 0xc3, 0xfe, 0xa3, + 0x01, 0x37, 0x55, 0xf8, 0xbb, 0xc4, 0x23, 0xcf, 0x11, 0x0f, 0x0e, 0x49, 0xc4, 0xfa, 0xe2, 0x1b, + 0xdb, 0x69, 0xc3, 0x62, 0xa0, 0x91, 0x7c, 0xc9, 0x7c, 0x14, 0x04, 0xda, 0x4e, 0xcd, 0xa3, 0x88, + 0x67, 0x6c, 0x3f, 0x08, 0xcc, 0x1d, 0x58, 0x29, 0x79, 0xb8, 0xca, 0xa7, 0x0a, 0xb3, 0x62, 0x5b, + 0xca, 0xd9, 0x74, 0x96, 0x89, 0xfd, 0x2f, 0x03, 0x56, 0x3e, 0x0c, 0x59, 0x07, 0x85, 0xa7, 0x21, + 0x12, 0x3d, 0x55, 0x7a, 0x43, 0x95, 0x1e, 0x4e, 0xb2, 0x9e, 0xd7, 0xe6, 0x4d, 0x9c, 0x1e, 0x25, + 0xa6, 0xa7, 0xd0, 0xfb, 0xb0, 0x5a, 0x74, 0x61, 0x51, 0x05, 0xda, 0x9b, 0x83, 0xb5, 0x17, 0x5f, + 0x6d, 0x2d, 0xe7, 0xc5, 0xd6, 0xd6, 0x15, 0x71, 0xe8, 0x2d, 0xe3, 0x11, 0x42, 0x60, 0xb6, 0xa0, + 0x49, 0x3b, 0xd8, 0x17, 0xe4, 0x99, 0x1f, 0x0d, 0xfa, 0xba, 0x80, 0xea, 0x5e, 0x83, 0x76, 0xf0, + 0x29, 0x79, 0xf6, 0x68, 0xd0, 0x37, 0xdf, 0x85, 0x37, 0xf3, 0x05, 0xec, 0x27, 0x28, 0xf4, 0x95, + 0xbc, 0x0a, 0x07, 0xd7, 0xf5, 0xb4, 0xe0, 0xad, 0xe5, 0xa7, 0xe7, 0x28, 0x54, 0xca, 0xf6, 0x83, + 0x80, 0xdb, 0x7f, 0x9b, 0x81, 0xd9, 0x13, 0xc4, 0x51, 0x5f, 0x98, 0x67, 0xb0, 0x2c, 0x49, 0x3f, + 0x0e, 0x91, 0x24, 0x7e, 0x3a, 0xe1, 0x33, 0x4f, 0x6f, 0xeb, 0xc9, 0x5f, 0x5d, 0x96, 0x4e, 0x65, + 0x3d, 0x26, 0xbb, 0x4e, 0x5b, 0x53, 0x4f, 0x25, 0x92, 0xc4, 0x5b, 0xca, 0x31, 0x52, 0xa2, 0x79, + 0x17, 0x2c, 0xc9, 0x07, 0x42, 0x96, 0xb3, 0xb7, 0x1c, 0x3a, 0x69, 0x2e, 0xdf, 0xcc, 0xcf, 0xd3, + 0x71, 0x55, 0x0c, 0x9b, 0xab, 0xc7, 0x6c, 0xed, 0x9b, 0x8c, 0xd9, 0x53, 0x58, 0x53, 0x3b, 0x6a, + 0x1c, 0xb3, 0x3e, 0x39, 0xe6, 0xaa, 0x92, 0x1f, 0x05, 0xfd, 0x18, 0xcc, 0x44, 0xe0, 0x71, 0xcc, + 0x99, 0xd7, 0xb0, 0x33, 0x11, 0x78, 0x14, 0x32, 0x80, 0x4d, 0xa1, 0x8a, 0xcf, 0xef, 0x13, 0xa9, + 0x87, 0x76, 0x1c, 0x92, 0x88, 0x8a, 0x5e, 0x0e, 0x3e, 0x3b, 0x39, 0xf8, 0x86, 0x06, 0xfa, 0x48, + 0xe1, 0x78, 0x39, 0x4c, 0xa6, 0xa5, 0x0d, 0xad, 0xab, 0xb5, 0x14, 0x09, 0x9a, 0xd3, 0x09, 0xfa, + 0xd6, 0x15, 0x10, 0x45, 0x96, 0x04, 0xbc, 0x53, 0x59, 0x2e, 0xaa, 0xab, 0x7d, 0xdd, 0x50, 0x3e, + 0x27, 0x5d, 0x35, 0x81, 0x51, 0xba, 0x67, 0x08, 0x29, 0x16, 0x64, 0x36, 0x3d, 0xd4, 0x15, 0xa8, + 0x98, 0x1c, 0x6d, 0x46, 0xa3, 0xec, 0x16, 0x61, 0x97, 0x3b, 0xa8, 0x98, 0x11, 0x5e, 0x05, 0xeb, + 0x03, 0x42, 0x1e, 0xd6, 0xe7, 0xe7, 0x57, 0x1a, 0xf6, 0xf7, 0xa0, 0xa1, 0x5b, 0x74, 0x1f, 0x5f, + 0x08, 0x73, 0x13, 0x1a, 0xaa, 0xd6, 0x89, 0x10, 0x44, 0x58, 0x86, 0xee, 0xec, 0x92, 0x60, 0x4b, + 0xd8, 0xb8, 0xee, 0x0e, 0x25, 0xcc, 0x27, 0x30, 0x17, 0x13, 0xbd, 0xe0, 0xb5, 0x60, 0x73, 0xef, + 0x3d, 0x67, 0x82, 0x1b, 0xae, 0x73, 0x1d, 0xa0, 0x97, 0xa3, 0xd9, 0xbc, 0xbc, 0xb9, 0x8d, 0xad, + 0x10, 0x61, 0x9e, 0x8f, 0x2b, 0xfd, 0xf1, 0x6b, 0x29, 0x1d, 0xc3, 0x2b, 0x75, 0xde, 0x86, 0xe6, + 0x7e, 0xea, 0xf6, 0x4f, 0xa8, 0x90, 0x97, 0xc3, 0xb2, 0x50, 0x0d, 0xcb, 0x43, 0x58, 0xca, 0xd6, + 0xe1, 0x19, 0xd3, 0x63, 0xc6, 0xfc, 0x36, 0x40, 0xb6, 0x47, 0xd5, 0x78, 0x4a, 0x07, 0x71, 0x23, + 0xa3, 0x1c, 0x05, 0x23, 0x1b, 0x6c, 0x7a, 0x64, 0x83, 0xd9, 0x1e, 0x2c, 0x9f, 0x0b, 0xfc, 0xd3, + 0xfc, 0xae, 0xf4, 0x38, 0x16, 0xe6, 0x1b, 0x30, 0xab, 0x3a, 0x23, 0x03, 0xaa, 0x7b, 0x33, 0x89, + 0xc0, 0x47, 0x7a, 0x16, 0x97, 0xf7, 0x31, 0x16, 0xfb, 0x34, 0x10, 0xd6, 0xf4, 0x76, 0x6d, 0xa7, + 0xee, 0x2d, 0x0d, 0x4a, 0xf1, 0xa3, 0x40, 0xd8, 0x3f, 0x83, 0x66, 0x05, 0xd0, 0x5c, 0x82, 0xe9, + 0x02, 0x6b, 0x9a, 0x06, 0xe6, 0x3d, 0xd8, 0x28, 0x81, 0x46, 0x87, 0x6b, 0x8a, 0xd8, 0xf0, 0x6e, + 0x14, 0x0c, 0x23, 0xf3, 0x55, 0xd8, 0x8f, 0x61, 0xfd, 0xa8, 0x6c, 0xe5, 0x62, 0x74, 0x8f, 0x78, + 0x68, 0x8c, 0xee, 0xe8, 0x4d, 0x68, 0x14, 0xbf, 0x43, 0xb4, 0xf7, 0x75, 0xaf, 0x24, 0xd8, 0x7d, + 0x58, 0x39, 0x17, 0xf8, 0x94, 0x44, 0x41, 0x09, 0x76, 0x4d, 0x00, 0x0e, 0xc6, 0x81, 0x26, 0xbe, + 0xd4, 0x96, 0xea, 0x18, 0x6c, 0x9c, 0xa3, 0x90, 0x06, 0x48, 0x32, 0x7e, 0x4a, 0x64, 0xba, 0x56, + 0x4f, 0x10, 0xbe, 0x20, 0x52, 0x98, 0x1e, 0xd4, 0x43, 0x2a, 0x64, 0x56, 0x59, 0x77, 0xaf, 0xad, + 0xac, 0x64, 0xd7, 0xb9, 0x0e, 0xe4, 0x10, 0x49, 0x94, 0x75, 0xa4, 0xc6, 0xb2, 0xbf, 0x0b, 0x6b, + 0x1f, 0x21, 0x39, 0xe0, 0x24, 0x18, 0xc9, 0xf1, 0x0a, 0xd4, 0x54, 0xfe, 0x0c, 0x9d, 0x3f, 0xf5, + 0xa8, 0xb6, 0xbc, 0x75, 0xff, 0x93, 0x98, 0x71, 0x49, 0x82, 0x4b, 0x11, 0x79, 0x45, 0x78, 0x2f, + 0x60, 0x4d, 0x05, 0x4b, 0x90, 0x28, 0xf0, 0x0b, 0x3f, 0xd3, 0x3c, 0x36, 0xf7, 0x7e, 0x34, 0x51, + 0x77, 0x8c, 0xab, 0xcb, 0x1c, 0x58, 0x4d, 0xc6, 0xe8, 0xc2, 0xfe, 0xad, 0x01, 0xd6, 0x31, 0x19, + 0xee, 0x0b, 0x41, 0xbb, 0x51, 0x9f, 0x44, 0x52, 0x4d, 0x36, 0x84, 0x89, 0x7a, 0x34, 0xdf, 0x82, + 0xc5, 0x62, 0x93, 0xea, 0x05, 0x6a, 0xe8, 0x05, 0xba, 0x90, 0x13, 0x55, 0x83, 0x99, 0xf7, 0x00, + 0x62, 0x4e, 0x12, 0x1f, 0xfb, 0x17, 0x64, 0x98, 0x65, 0x71, 0xb3, 0xba, 0x18, 0xd3, 0x5f, 0x89, + 0xce, 0xc9, 0xa0, 0x13, 0x52, 0x7c, 0x4c, 0x86, 0xde, 0xbc, 0xe2, 0x6f, 0x1f, 0x93, 0xa1, 0xba, + 0xe9, 0xc4, 0xec, 0x39, 0xe1, 0x7a, 0x9b, 0xd5, 0xbc, 0xf4, 0xc5, 0xfe, 0x9d, 0x01, 0x37, 0x8a, + 0x74, 0xe4, 0xe5, 0x7a, 0x32, 0xe8, 0x28, 0x89, 0x57, 0xc4, 0xed, 0x92, 0xb5, 0xd3, 0x57, 0x58, + 0xfb, 0x3e, 0x2c, 0x14, 0x0d, 0xa2, 0xec, 0xad, 0x4d, 0x60, 0x6f, 0x33, 0x97, 0x38, 0x26, 0x43, + 0xfb, 0x97, 0x15, 0xdb, 0x0e, 0x86, 0x95, 0xd9, 0xc7, 0xff, 0x8b, 0x6d, 0x85, 0xda, 0xaa, 0x6d, + 0xb8, 0x2a, 0x7f, 0xc9, 0x81, 0xda, 0x65, 0x07, 0xec, 0x3f, 0x18, 0xb0, 0x5e, 0xd5, 0x2a, 0xce, + 0xd8, 0x09, 0x1f, 0x44, 0xe4, 0x55, 0xda, 0xcb, 0xf6, 0x9b, 0xae, 0xb6, 0xdf, 0x13, 0x58, 0x1a, + 0x31, 0x4a, 0x64, 0xd1, 0xf8, 0xc1, 0x44, 0x35, 0x56, 0x99, 0xae, 0xde, 0x62, 0xd5, 0x0f, 0x61, + 0xff, 0xda, 0x28, 0xd7, 0x4c, 0xba, 0xbd, 0xc4, 0x7e, 0x18, 0x66, 0x57, 0x60, 0x33, 0x86, 0xb9, + 0x74, 0x41, 0x8a, 0xac, 0x2f, 0x37, 0xaf, 0x5c, 0x85, 0x87, 0x04, 0xeb, 0x6d, 0x78, 0x57, 0x95, + 0xee, 0x9f, 0xbf, 0xde, 0xba, 0xdd, 0xa5, 0xb2, 0x37, 0xe8, 0x38, 0x98, 0xf5, 0xdd, 0xec, 0xeb, + 0x41, 0xfa, 0xef, 0x8e, 0x08, 0x2e, 0x5c, 0x39, 0x8c, 0x89, 0xc8, 0x65, 0xc4, 0x9f, 0xfe, 0xf9, + 0xd7, 0xef, 0x1b, 0x5e, 0xae, 0xc6, 0xfe, 0x8d, 0x01, 0x2b, 0x8f, 0x63, 0x49, 0x82, 0xa3, 0xa8, + 0xc8, 0xdd, 0x64, 0xc5, 0x7d, 0x0b, 0x16, 0xf4, 0x8f, 0xb7, 0xfc, 0x13, 0xc0, 0xb4, 0xae, 0xd3, + 0xa6, 0xa6, 0x65, 0x3f, 0xef, 0xaf, 0xac, 0x61, 0xb5, 0x3f, 0x62, 0x5d, 0x40, 0xba, 0xca, 0xd2, + 0x8b, 0x67, 0x23, 0xce, 0x4b, 0xea, 0xe0, 0xc9, 0x17, 0x2f, 0x5a, 0xc6, 0x97, 0x2f, 0x5a, 0xc6, + 0x3f, 0x5e, 0xb4, 0x8c, 0x4f, 0x5f, 0xb6, 0xa6, 0xbe, 0x7c, 0xd9, 0x9a, 0xfa, 0xfb, 0xcb, 0xd6, + 0xd4, 0xcf, 0xdf, 0xbb, 0xec, 0x66, 0x99, 0x8d, 0x3b, 0xc5, 0x17, 0xa4, 0xe4, 0x87, 0xee, 0x27, + 0xa3, 0xdf, 0xa7, 0x74, 0x04, 0x3a, 0xb3, 0x7a, 0x6e, 0xbe, 0xfb, 0x9f, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xc6, 0x15, 0xa0, 0x59, 0xd0, 0x12, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -2686,6 +2766,53 @@ func (m *ConsumerRewardsAllocation) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } +func (m *OptedInValidator) 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 *OptedInValidator) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *OptedInValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.PublicKey) > 0 { + i -= len(m.PublicKey) + copy(dAtA[i:], m.PublicKey) + i = encodeVarintProvider(dAtA, i, uint64(len(m.PublicKey))) + i-- + dAtA[i] = 0x22 + } + if m.Power != 0 { + i = encodeVarintProvider(dAtA, i, uint64(m.Power)) + i-- + dAtA[i] = 0x18 + } + if m.BlockHeight != 0 { + i = encodeVarintProvider(dAtA, i, uint64(m.BlockHeight)) + i-- + dAtA[i] = 0x10 + } + if len(m.ProviderAddr) > 0 { + i -= len(m.ProviderAddr) + copy(dAtA[i:], m.ProviderAddr) + i = encodeVarintProvider(dAtA, i, uint64(len(m.ProviderAddr))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintProvider(dAtA []byte, offset int, v uint64) int { offset -= sovProvider(v) base := offset @@ -3172,6 +3299,29 @@ func (m *ConsumerRewardsAllocation) Size() (n int) { return n } +func (m *OptedInValidator) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ProviderAddr) + if l > 0 { + n += 1 + l + sovProvider(uint64(l)) + } + if m.BlockHeight != 0 { + n += 1 + sovProvider(uint64(m.BlockHeight)) + } + if m.Power != 0 { + n += 1 + sovProvider(uint64(m.Power)) + } + l = len(m.PublicKey) + if l > 0 { + n += 1 + l + sovProvider(uint64(l)) + } + return n +} + func sovProvider(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -6549,6 +6699,162 @@ func (m *ConsumerRewardsAllocation) Unmarshal(dAtA []byte) error { } return nil } +func (m *OptedInValidator) 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: OptedInValidator: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: OptedInValidator: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProviderAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthProvider + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthProvider + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProviderAddr = append(m.ProviderAddr[:0], dAtA[iNdEx:postIndex]...) + if m.ProviderAddr == nil { + m.ProviderAddr = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockHeight", wireType) + } + m.BlockHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BlockHeight |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Power", wireType) + } + m.Power = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Power |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PublicKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthProvider + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthProvider + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PublicKey = append(m.PublicKey[:0], dAtA[iNdEx:postIndex]...) + if m.PublicKey == nil { + m.PublicKey = []byte{} + } + 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 skipProvider(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 890c58f2d13ad17e18ee3e86ba3b9ec94d82c2f6 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 8 Mar 2024 09:24:32 +0100 Subject: [PATCH 17/18] feat!: PSS enable per-consumer chain commission (#1657) * add draft commission * implement consumer commission draft * formatting * add msg handling * improve UT * nits * Update x/ccv/provider/keeper/keeper.go Co-authored-by: insumity * Update proto/interchain_security/ccv/provider/v1/tx.proto Co-authored-by: Marius Poke * optimize keys * Update x/ccv/provider/keeper/keeper.go Co-authored-by: insumity * address comments * address comments * remove unnecessary check * Revert "remove unnecessary check" This reverts commit 2951e9bace04f6436d6ad1e4a11efcedd0be8cb1. * fix minor bug in StopConsumerChain --------- Co-authored-by: insumity Co-authored-by: Marius Poke --- .../ccv/provider/v1/provider.proto | 2 +- .../ccv/provider/v1/tx.proto | 23 +- tests/integration/distribution.go | 121 +++-- testutil/integration/interfaces.go | 1 + testutil/keeper/mocks.go | 128 ----- testutil/keeper/unit_test_helpers.go | 5 + x/ccv/provider/keeper/distribution.go | 53 +- x/ccv/provider/keeper/keeper.go | 69 +++ x/ccv/provider/keeper/keeper_test.go | 41 +- x/ccv/provider/keeper/msg_server.go | 41 +- .../keeper/partial_set_security_test.go | 25 + x/ccv/provider/keeper/proposal.go | 6 + x/ccv/provider/keeper/proposal_test.go | 4 - x/ccv/provider/types/errors.go | 1 + x/ccv/provider/types/events.go | 3 + x/ccv/provider/types/keys.go | 13 + x/ccv/provider/types/keys_test.go | 1 + x/ccv/provider/types/msg.go | 44 ++ x/ccv/provider/types/tx.pb.go | 510 ++++++++++++++++-- 19 files changed, 864 insertions(+), 227 deletions(-) diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index 39e71df522..40c5258880 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -325,4 +325,4 @@ message OptedInValidator { int64 power = 3; // public key used by the validator on the consumer bytes public_key = 4; -} +} \ No newline at end of file diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 51003bdf05..96844c2e72 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -17,6 +17,7 @@ service Msg { rpc SubmitConsumerDoubleVoting(MsgSubmitConsumerDoubleVoting) returns (MsgSubmitConsumerDoubleVotingResponse); rpc OptIn(MsgOptIn) returns (MsgOptInResponse); rpc OptOut(MsgOptOut) returns (MsgOptOutResponse); + rpc SetConsumerCommissionRate(MsgSetConsumerCommissionRate) returns (MsgSetConsumerCommissionRateResponse); } message MsgAssignConsumerKey { @@ -88,4 +89,24 @@ message MsgOptOut { string provider_addr = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ]; } -message MsgOptOutResponse {} \ No newline at end of file +message MsgOptOutResponse {} + +// MsgSetConsumerCommissionRate allows validators to set +// a per-consumer chain commission rate +message MsgSetConsumerCommissionRate { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + // The validator address on the provider + string provider_addr = 1 [ (gogoproto.moretags) = "yaml:\"address\"" ]; + // The chain id of the consumer chain to set a commission rate + string chain_id = 2; + // The rate to charge delegators on the consumer chain, as a fraction + string rate = 3 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} + + +message MsgSetConsumerCommissionRateResponse {} \ No newline at end of file diff --git a/tests/integration/distribution.go b/tests/integration/distribution.go index f06c6f9482..65473d3bcf 100644 --- a/tests/integration/distribution.go +++ b/tests/integration/distribution.go @@ -6,7 +6,7 @@ import ( "cosmossdk.io/math" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/libs/bytes" - "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" @@ -575,7 +575,7 @@ func (s *CCVTestSuite) TestIBCTransferMiddleware() { bankKeeper := s.providerApp.GetTestBankKeeper() amount := sdk.NewInt(100) - data = types.NewFungibleTokenPacketData( // can be explicitly changed in setup + data = transfertypes.NewFungibleTokenPacketData( // can be explicitly changed in setup sdk.DefaultBondDenom, amount.String(), authtypes.NewModuleAddress(consumertypes.ConsumerToSendToProviderName).String(), @@ -734,11 +734,11 @@ func (s *CCVTestSuite) TestAllocateTokens() { perValExpReward := validatorsExpRewards.QuoDec(sdk.NewDec(int64(valNum))) // verify the validator tokens allocation - // note all validators have the same voting power to keep things simple + // note that the validators have the same voting power to keep things simple for _, val := range s.providerChain.Vals.Validators { - valReward := distributionKeeper.GetValidatorOutstandingRewards(s.providerCtx(), sdk.ValAddress(val.Address)) + valRewards := distributionKeeper.GetValidatorOutstandingRewards(s.providerCtx(), sdk.ValAddress(val.Address)) s.Require().Equal( - valReward.Rewards, + valRewards.Rewards, lastValOutRewards[sdk.ValAddress(val.Address).String()].Add(perValExpReward...), ) } @@ -902,80 +902,133 @@ func (s *CCVTestSuite) prepareRewardDist() { } func (s *CCVTestSuite) TestAllocateTokensToValidator() { - - providerkeepr := s.providerApp.GetProviderKeeper() + providerKeeper := s.providerApp.GetProviderKeeper() + distributionKeeper := s.providerApp.GetTestDistributionKeeper() + bankKeeper := s.providerApp.GetTestBankKeeper() chainID := "consumer" - validators := []bytes.HexBytes{ s.providerChain.Vals.Validators[0].Address, s.providerChain.Vals.Validators[1].Address, } - votes := []abci.VoteInfo{ {Validator: abci.Validator{Address: validators[0], Power: 1}}, {Validator: abci.Validator{Address: validators[1], Power: 1}}, } testCases := []struct { - name string - votes []abci.VoteInfo - tokens sdk.DecCoins - expCoinTransferred sdk.DecCoins + name string + votes []abci.VoteInfo + tokens sdk.DecCoins + rate sdk.Dec + expAllocated sdk.DecCoins }{ { - name: "reward tokens are empty", + name: "tokens are empty", + tokens: sdk.DecCoins{}, + rate: sdk.ZeroDec(), + expAllocated: nil, }, { - name: "total voting power is zero", - tokens: sdk.DecCoins{sdk.NewDecCoin("uatom", math.NewInt(100_000))}, + name: "total voting power is zero", + tokens: sdk.DecCoins{sdk.NewDecCoin(sdk.DefaultBondDenom, math.NewInt(100_000))}, + rate: sdk.ZeroDec(), + expAllocated: nil, }, { - name: "expect all tokens to be allocated to a single validator", - votes: []abci.VoteInfo{votes[0]}, - tokens: sdk.DecCoins{sdk.NewDecCoin("uatom", math.NewInt(100_000))}, - expCoinTransferred: sdk.DecCoins{sdk.NewDecCoin("uatom", math.NewInt(100_000))}, + name: "expect all tokens to be allocated to a single validator", + votes: []abci.VoteInfo{votes[0]}, + tokens: sdk.DecCoins{sdk.NewDecCoin(sdk.DefaultBondDenom, math.NewInt(999))}, + rate: sdk.NewDecWithPrec(5, 1), + expAllocated: sdk.DecCoins{sdk.NewDecCoin(sdk.DefaultBondDenom, math.NewInt(999))}, }, { - name: "expect tokens to be allocated evenly between validators", - votes: []abci.VoteInfo{votes[0], votes[1]}, - tokens: sdk.DecCoins{sdk.NewDecCoin("uatom", math.NewInt(555_555))}, - expCoinTransferred: sdk.DecCoins{sdk.NewDecCoin("uatom", math.NewInt(555_555))}, + name: "expect tokens to be allocated evenly between validators", + votes: []abci.VoteInfo{votes[0], votes[1]}, + tokens: sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, math.LegacyNewDecFromIntWithPrec(math.NewInt(999), 2))}, + rate: sdk.OneDec(), + expAllocated: sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, math.LegacyNewDecFromIntWithPrec(math.NewInt(999), 2))}, }, } for _, tc := range testCases { s.Run(tc.name, func() { - // TODO: opt validators in and verify - // that rewards are solely allocated to them + // set the same consumer commission rate for all validators + for _, v := range s.providerChain.Vals.Validators { + provAddr := providertypes.NewProviderConsAddress(sdk.ConsAddress(v.Address)) + + providerKeeper.SetConsumerCommissionRate( + s.providerCtx(), + chainID, + provAddr, + tc.rate, + ) + } + + // TODO: opt validators in and verify + // that rewards are only allocated to them ctx, _ := s.providerCtx().CacheContext() // allocate tokens - res := providerkeepr.AllocateTokensToConsumerValidators( + res := providerKeeper.AllocateTokensToConsumerValidators( ctx, chainID, tc.votes, tc.tokens, ) - // check that the expect result is returned - s.Require().Equal(tc.expCoinTransferred, res) + // check that the expected result is returned + s.Require().Equal(tc.expAllocated, res) - if !tc.expCoinTransferred.Empty() { + if !tc.expAllocated.Empty() { // rewards are expected to be allocated evenly between validators - rewardsPerVal := tc.expCoinTransferred.QuoDec(sdk.NewDec(int64(len(tc.votes)))) + rewardsPerVal := tc.expAllocated.QuoDec(sdk.NewDec(int64(len(tc.votes)))) - // check that the rewards are allocated to validators as expected + // check that the rewards are allocated to validators for _, v := range tc.votes { + valAddr := sdk.ValAddress(v.Validator.Address) rewards := s.providerApp.GetTestDistributionKeeper().GetValidatorOutstandingRewards( ctx, - sdk.ValAddress(v.Validator.Address), + valAddr, ) s.Require().Equal(rewardsPerVal, rewards.Rewards) + + // send rewards to the distribution module + valRewardsTrunc, _ := rewards.Rewards.TruncateDecimal() + err := bankKeeper.SendCoinsFromAccountToModule( + ctx, + s.providerChain.SenderAccount.GetAddress(), + distrtypes.ModuleName, + valRewardsTrunc) + s.Require().NoError(err) + + // check that validators can withdraw their rewards + withdrawnCoins, err := distributionKeeper.WithdrawValidatorCommission( + ctx, + valAddr, + ) + s.Require().NoError(err) + + // check that the withdrawn coins is equal to the entire reward amount + // times the set consumer commission rate + commission := rewards.Rewards.MulDec(tc.rate) + c, _ := commission.TruncateDecimal() + s.Require().Equal(withdrawnCoins, c) + + // check that validators get rewards in their balance + s.Require().Equal(withdrawnCoins, bankKeeper.GetAllBalances(ctx, sdk.AccAddress(valAddr))) + } + } else { + for _, v := range tc.votes { + valAddr := sdk.ValAddress(v.Validator.Address) + rewards := s.providerApp.GetTestDistributionKeeper().GetValidatorOutstandingRewards( + ctx, + valAddr, + ) + s.Require().Zero(rewards.Rewards) } } - }) } } diff --git a/testutil/integration/interfaces.go b/testutil/integration/interfaces.go index 89d59904df..fe3382b524 100644 --- a/testutil/integration/interfaces.go +++ b/testutil/integration/interfaces.go @@ -142,6 +142,7 @@ type TestDistributionKeeper interface { GetValidatorOutstandingRewards(ctx sdk.Context, val sdk.ValAddress) (rewards distributiontypes.ValidatorOutstandingRewards) GetCommunityTax(ctx sdk.Context) (percent sdk.Dec) + WithdrawValidatorCommission(ctx sdk.Context, valAddr sdk.ValAddress) (sdk.Coins, error) } type TestMintKeeper interface { diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index a192765ec9..78c0fbedc9 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -1114,134 +1114,6 @@ func (m *MockIBCTransferKeeper) EXPECT() *MockIBCTransferKeeperMockRecorder { return m.recorder } -// OnAcknowledgementPacket mocks base method. -func (m *MockIBCTransferKeeper) OnAcknowledgementPacket(ctx types0.Context, packet types9.Packet, acknowledgement []byte, relayer types0.AccAddress) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OnAcknowledgementPacket", ctx, packet, acknowledgement, relayer) - ret0, _ := ret[0].(error) - return ret0 -} - -// OnAcknowledgementPacket indicates an expected call of OnAcknowledgementPacket. -func (mr *MockIBCTransferKeeperMockRecorder) OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAcknowledgementPacket", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnAcknowledgementPacket), ctx, packet, acknowledgement, relayer) -} - -// OnChanCloseConfirm mocks base method. -func (m *MockIBCTransferKeeper) OnChanCloseConfirm(ctx types0.Context, portID, channelID string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OnChanCloseConfirm", ctx, portID, channelID) - ret0, _ := ret[0].(error) - return ret0 -} - -// OnChanCloseConfirm indicates an expected call of OnChanCloseConfirm. -func (mr *MockIBCTransferKeeperMockRecorder) OnChanCloseConfirm(ctx, portID, channelID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanCloseConfirm", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanCloseConfirm), ctx, portID, channelID) -} - -// OnChanCloseInit mocks base method. -func (m *MockIBCTransferKeeper) OnChanCloseInit(ctx types0.Context, portID, channelID string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OnChanCloseInit", ctx, portID, channelID) - ret0, _ := ret[0].(error) - return ret0 -} - -// OnChanCloseInit indicates an expected call of OnChanCloseInit. -func (mr *MockIBCTransferKeeperMockRecorder) OnChanCloseInit(ctx, portID, channelID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanCloseInit", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanCloseInit), ctx, portID, channelID) -} - -// OnChanOpenAck mocks base method. -func (m *MockIBCTransferKeeper) OnChanOpenAck(ctx types0.Context, portID, channelID, counterpartyChannelID, counterpartyVersion string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OnChanOpenAck", ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) - ret0, _ := ret[0].(error) - return ret0 -} - -// OnChanOpenAck indicates an expected call of OnChanOpenAck. -func (mr *MockIBCTransferKeeperMockRecorder) OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanOpenAck", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanOpenAck), ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) -} - -// OnChanOpenConfirm mocks base method. -func (m *MockIBCTransferKeeper) OnChanOpenConfirm(ctx types0.Context, portID, channelID string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OnChanOpenConfirm", ctx, portID, channelID) - ret0, _ := ret[0].(error) - return ret0 -} - -// OnChanOpenConfirm indicates an expected call of OnChanOpenConfirm. -func (mr *MockIBCTransferKeeperMockRecorder) OnChanOpenConfirm(ctx, portID, channelID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanOpenConfirm", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanOpenConfirm), ctx, portID, channelID) -} - -// OnChanOpenInit mocks base method. -func (m *MockIBCTransferKeeper) OnChanOpenInit(ctx types0.Context, order types9.Order, connectionHops []string, portID, channelID string, channelCap *types2.Capability, counterparty types9.Counterparty, version string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OnChanOpenInit", ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// OnChanOpenInit indicates an expected call of OnChanOpenInit. -func (mr *MockIBCTransferKeeperMockRecorder) OnChanOpenInit(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanOpenInit", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanOpenInit), ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version) -} - -// OnChanOpenTry mocks base method. -func (m *MockIBCTransferKeeper) OnChanOpenTry(ctx types0.Context, order types9.Order, connectionHops []string, portID, channelID string, channelCap *types2.Capability, counterparty types9.Counterparty, counterpartyVersion string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OnChanOpenTry", ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// OnChanOpenTry indicates an expected call of OnChanOpenTry. -func (mr *MockIBCTransferKeeperMockRecorder) OnChanOpenTry(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChanOpenTry", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnChanOpenTry), ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion) -} - -// OnRecvPacket mocks base method. -func (m *MockIBCTransferKeeper) OnRecvPacket(ctx types0.Context, packet types9.Packet, relayer types0.AccAddress) exported.Acknowledgement { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OnRecvPacket", ctx, packet, relayer) - ret0, _ := ret[0].(exported.Acknowledgement) - return ret0 -} - -// OnRecvPacket indicates an expected call of OnRecvPacket. -func (mr *MockIBCTransferKeeperMockRecorder) OnRecvPacket(ctx, packet, relayer interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnRecvPacket", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnRecvPacket), ctx, packet, relayer) -} - -// OnTimeoutPacket mocks base method. -func (m *MockIBCTransferKeeper) OnTimeoutPacket(ctx types0.Context, packet types9.Packet, relayer types0.AccAddress) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OnTimeoutPacket", ctx, packet, relayer) - ret0, _ := ret[0].(error) - return ret0 -} - -// OnTimeoutPacket indicates an expected call of OnTimeoutPacket. -func (mr *MockIBCTransferKeeperMockRecorder) OnTimeoutPacket(ctx, packet, relayer interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnTimeoutPacket", reflect.TypeOf((*MockIBCTransferKeeper)(nil).OnTimeoutPacket), ctx, packet, relayer) -} - // Transfer mocks base method. func (m *MockIBCTransferKeeper) Transfer(arg0 context.Context, arg1 *types6.MsgTransfer) (*types6.MsgTransferResponse, error) { m.ctrl.T.Helper() diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index 468b0cb5e9..d17433ca66 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -249,11 +249,16 @@ func TestProviderStateIsCleanedAfterConsumerChainIsStopped(t *testing.T, ctx sdk require.Empty(t, providerKeeper.GetAllVscSendTimestamps(ctx, expectedChainID)) + // in case the chain was successfully stopped, it should not contain a Top N associated to it + _, found = providerKeeper.GetTopN(ctx, expectedChainID) + require.False(t, found) + // test key assignment state is cleaned require.Empty(t, providerKeeper.GetAllValidatorConsumerPubKeys(ctx, &expectedChainID)) require.Empty(t, providerKeeper.GetAllValidatorsByConsumerAddr(ctx, &expectedChainID)) require.Empty(t, providerKeeper.GetAllKeyAssignmentReplacements(ctx, expectedChainID)) require.Empty(t, providerKeeper.GetAllConsumerAddrsToPrune(ctx, expectedChainID)) + require.Empty(t, providerKeeper.GetAllCommissionRateValidators(ctx, expectedChainID)) } func GetTestConsumerAdditionProp() *providertypes.ConsumerAdditionProposal { diff --git a/x/ccv/provider/keeper/distribution.go b/x/ccv/provider/keeper/distribution.go index 45af3eb262..16511f8bde 100644 --- a/x/ccv/provider/keeper/distribution.go +++ b/x/ccv/provider/keeper/distribution.go @@ -5,9 +5,9 @@ import ( "cosmossdk.io/math" abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ) @@ -138,16 +138,17 @@ func (k Keeper) AllocateTokensToConsumerValidators( chainID string, bondedVotes []abci.VoteInfo, tokens sdk.DecCoins, -) (totalReward sdk.DecCoins) { +) (allocated sdk.DecCoins) { + // return early if the tokens are empty if tokens.Empty() { - return totalReward + return allocated } // get the consumer total voting power from the votes totalPower := k.ComputeConsumerTotalVotingPower(ctx, chainID, bondedVotes) if totalPower == 0 { - return totalReward + return allocated } for _, vote := range bondedVotes { @@ -157,19 +158,31 @@ func (k Keeper) AllocateTokensToConsumerValidators( powerFraction := math.LegacyNewDec(vote.Validator.Power).QuoTruncate(math.LegacyNewDec(totalPower)) tokensFraction := tokens.MulDecTruncate(powerFraction) + // get the validator type struct for the consensus address + val := k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr).(stakingtypes.Validator) + + // check if the validator set a custom commission rate for the consumer chain + if cr, found := k.GetConsumerCommissionRate(ctx, chainID, types.NewProviderConsAddress(consAddr)); found { + // set the validator commission rate + val.Commission.CommissionRates.Rate = cr + } + + // allocate the consumer reward tokens to the validator k.distributionKeeper.AllocateTokensToValidator( ctx, - k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr), + val, tokensFraction, ) - totalReward = totalReward.Add(tokensFraction...) + + // sum the tokens allocated + allocated = allocated.Add(tokensFraction...) } - return totalReward + return allocated } -// TransferConsumerRewardsToDistributionModule transfers the collected rewards of the given consumer chain -// from the consumer rewards pool module account to a the distribution module +// TransferConsumerRewardsToDistributionModule transfers the rewards allocation of the given consumer chain +// from the consumer rewards pool to a the distribution module func (k Keeper) TransferConsumerRewardsToDistributionModule( ctx sdk.Context, chainID string, @@ -267,3 +280,23 @@ func (k Keeper) IdentifyConsumerChainIDFromIBCPacket(ctx sdk.Context, packet cha return chainID, nil } + +// HandleSetConsumerCommissionRate sets a per-consumer chain commission rate for the given provider address +// on the condition that the given consumer chain exists. +func (k Keeper) HandleSetConsumerCommissionRate(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress, commissionRate sdk.Dec) error { + // check that the consumer chain exists + if !k.IsConsumerProposedOrRegistered(ctx, chainID) { + return errorsmod.Wrapf( + types.ErrUnknownConsumerChainId, + "unknown consumer chain, with id: %s", chainID) + } + // set per-consumer chain commission rate for the validator address + k.SetConsumerCommissionRate( + ctx, + chainID, + providerAddr, + commissionRate, + ) + + return nil +} diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index e6b1c589aa..ec73d85383 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -1248,6 +1248,75 @@ func (k Keeper) SetToBeOptedIn( store.Set(types.ToBeOptedInKey(chainID, providerAddr), []byte{}) } +// SetConsumerCommissionRate sets a per-consumer chain commission rate +// for the given validator address +func (k Keeper) SetConsumerCommissionRate( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, + commissionRate sdk.Dec, +) { + store := ctx.KVStore(k.storeKey) + bz, err := commissionRate.Marshal() + if err != nil { + panic(fmt.Errorf("consumer commission rate marshalling failed: %s", err)) + } + + store.Set(types.ConsumerCommissionRateKey(chainID, providerAddr), bz) +} + +// GetConsumerCommissionRate returns the per-consumer commission rate set +// for the given validator address +func (k Keeper) GetConsumerCommissionRate( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) (sdk.Dec, bool) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.ConsumerCommissionRateKey(chainID, providerAddr)) + if bz == nil { + return sdk.ZeroDec(), false + } + + cr := sdk.Dec{} + if err := cr.Unmarshal(bz); err != nil { + k.Logger(ctx).Error("consumer commission rate unmarshalling failed: %s", err) + return sdk.ZeroDec(), false + } + + return cr, true +} + +// GetAllCommissionRateValidators returns all the validator address +// that set a commission rate for the given chain ID +func (k Keeper) GetAllCommissionRateValidators( + ctx sdk.Context, + chainID string) (addresses []types.ProviderConsAddress) { + + store := ctx.KVStore(k.storeKey) + key := types.ChainIdWithLenKey(types.ConsumerCommissionRatePrefix, chainID) + iterator := sdk.KVStorePrefixIterator(store, key) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + providerAddr := types.NewProviderConsAddress(iterator.Key()[len(key):]) + addresses = append(addresses, providerAddr) + } + + return addresses +} + +// DeleteConsumerCommissionRate the per-consumer chain commission rate +// associated to the given validator address +func (k Keeper) DeleteConsumerCommissionRate( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.ConsumerCommissionRateKey(chainID, providerAddr)) +} + func (k Keeper) DeleteToBeOptedIn( ctx sdk.Context, chainID string, diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index a231eb4c0c..595e01100a 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -15,6 +15,7 @@ import ( abci "github.com/cometbft/cometbft/abci/types" tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" cryptotestutil "github.com/cosmos/interchain-security/v4/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v4/testutil/keeper" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" @@ -692,7 +693,8 @@ func TestGetAllOptedIn(t *testing.T) { ProviderAddr: expectedOptedInValidator.ProviderAddr, BlockHeight: expectedOptedInValidator.BlockHeight, Power: expectedOptedInValidator.Power, - PublicKey: expectedOptedInValidator.PublicKey}) + PublicKey: expectedOptedInValidator.PublicKey, + }) } actualOptedInValidators := providerKeeper.GetAllOptedIn(ctx, "chainID") @@ -858,3 +860,40 @@ func TestToBeOptedOut(t *testing.T) { providerKeeper.DeleteToBeOptedOut(ctx, "chainID", providerAddr) require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) } + +// TestToBeOptedOut tests the `SetConsumerCommissionRate`, `GetConsumerCommissionRate`, and `DeleteConsumerCommissionRate` methods +func TestConsumerCommissionRate(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + providerAddr1 := types.NewProviderConsAddress([]byte("providerAddr1")) + providerAddr2 := types.NewProviderConsAddress([]byte("providerAddr2")) + + cr, found := providerKeeper.GetConsumerCommissionRate(ctx, "chainID", providerAddr1) + require.False(t, found) + require.Equal(t, sdk.ZeroDec(), cr) + + providerKeeper.SetConsumerCommissionRate(ctx, "chainID", providerAddr1, sdk.OneDec()) + cr, found = providerKeeper.GetConsumerCommissionRate(ctx, "chainID", providerAddr1) + require.True(t, found) + require.Equal(t, sdk.OneDec(), cr) + + providerKeeper.SetConsumerCommissionRate(ctx, "chainID", providerAddr2, sdk.ZeroDec()) + cr, found = providerKeeper.GetConsumerCommissionRate(ctx, "chainID", providerAddr2) + require.True(t, found) + require.Equal(t, sdk.ZeroDec(), cr) + + provAddrs := providerKeeper.GetAllCommissionRateValidators(ctx, "chainID") + require.Len(t, provAddrs, 2) + + for _, addr := range provAddrs { + providerKeeper.DeleteConsumerCommissionRate(ctx, "chainID", addr) + } + + _, found = providerKeeper.GetConsumerCommissionRate(ctx, "chainID", providerAddr1) + require.False(t, found) + + _, found = providerKeeper.GetConsumerCommissionRate(ctx, "chainID", providerAddr2) + require.False(t, found) + +} diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index 4863cd0d66..9e019bcb22 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -2,14 +2,12 @@ package keeper import ( "context" - errorsmod "cosmossdk.io/errors" + 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" - - tmtypes "github.com/cometbft/cometbft/types" - "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ccvtypes "github.com/cosmos/interchain-security/v4/x/ccv/types" ) @@ -195,3 +193,38 @@ func (k msgServer) OptOut(goCtx context.Context, msg *types.MsgOptOut) (*types.M return &types.MsgOptOutResponse{}, nil } + +func (k msgServer) SetConsumerCommissionRate(goCtx context.Context, msg *types.MsgSetConsumerCommissionRate) (*types.MsgSetConsumerCommissionRateResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + providerValidatorAddr, err := sdk.ValAddressFromBech32(msg.ProviderAddr) + if err != nil { + return nil, err + } + + // validator must already be registered + validator, found := k.stakingKeeper.GetValidator(ctx, providerValidatorAddr) + if !found { + return nil, stakingtypes.ErrNoValidatorFound + } + + consAddr, err := validator.GetConsAddr() + if err != nil { + return nil, err + } + + if err := k.HandleSetConsumerCommissionRate(ctx, msg.ChainId, types.NewProviderConsAddress(consAddr), msg.Rate); err != nil { + return nil, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeSetConsumerCommissionRate, + sdk.NewAttribute(types.AttributeConsumerChainID, msg.ChainId), + sdk.NewAttribute(types.AttributeProviderValidatorAddress, msg.ProviderAddr), + sdk.NewAttribute(types.AttributeConsumerCommissionRate, msg.Rate.String()), + ), + }) + + return &types.MsgSetConsumerCommissionRateResponse{}, nil +} diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go index 1af5f58c7c..a6b2ec45e7 100644 --- a/x/ccv/provider/keeper/partial_set_security_test.go +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -112,3 +112,28 @@ func TestHandleOptOut(t *testing.T) { require.NoError(t, err) require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) } + +func TestHandleSetConsumerCommissionRate(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + providerAddr := types.NewProviderConsAddress([]byte("providerAddr")) + + // trying to set a commission rate to a unknown consumer chain + require.Error(t, providerKeeper.HandleSetConsumerCommissionRate(ctx, "unknownChainID", providerAddr, sdk.ZeroDec())) + + // setup a pending consumer chain + chainID := "pendingChainID" + providerKeeper.SetPendingConsumerAdditionProp(ctx, &types.ConsumerAdditionProposal{ChainId: chainID}) + + // check that there's no commission rate set for the validator yet + _, found := providerKeeper.GetConsumerCommissionRate(ctx, chainID, providerAddr) + require.False(t, found) + + require.NoError(t, providerKeeper.HandleSetConsumerCommissionRate(ctx, chainID, providerAddr, sdk.OneDec())) + + // check that the commission rate is now set + cr, found := providerKeeper.GetConsumerCommissionRate(ctx, chainID, providerAddr) + require.Equal(t, sdk.OneDec(), cr) + require.True(t, found) +} diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 50dc69d080..399989354c 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -190,6 +190,12 @@ func (k Keeper) StopConsumerChain(ctx sdk.Context, chainID string, closeChan boo k.DeleteVscSendTimestampsForConsumer(ctx, chainID) } + // delete consumer commission rate + provAddrs := k.GetAllCommissionRateValidators(ctx, chainID) + for _, addr := range provAddrs { + k.DeleteConsumerCommissionRate(ctx, chainID, addr) + } + k.DeleteInitChainHeight(ctx, chainID) k.DeleteSlashAcks(ctx, chainID) k.DeletePendingVSCPackets(ctx, chainID) diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index dacc456d2f..a979301521 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -554,10 +554,6 @@ func TestStopConsumerChain(t *testing.T) { require.Error(t, err) } else { require.NoError(t, err) - - // in case the chain was successfully stopped, it should not contain a Top N associated to it - _, found := providerKeeper.GetTopN(ctx, "chainID") - require.False(t, found) } testkeeper.TestProviderStateIsCleanedAfterConsumerChainIsStopped(t, ctx, providerKeeper, "chainID", "channelID") diff --git a/x/ccv/provider/types/errors.go b/x/ccv/provider/types/errors.go index 6c19a7b396..271ea90329 100644 --- a/x/ccv/provider/types/errors.go +++ b/x/ccv/provider/types/errors.go @@ -24,4 +24,5 @@ var ( ErrInvalidConsumerClient = errorsmod.Register(ModuleName, 16, "ccv channel is not built on correct client") ErrDuplicateConsumerChain = errorsmod.Register(ModuleName, 17, "consumer chain already exists") ErrConsumerChainNotFound = errorsmod.Register(ModuleName, 18, "consumer chain not found") + ErrInvalidConsumerCommissionRate = errorsmod.Register(ModuleName, 19, "consumer commission rate is invalid") ) diff --git a/x/ccv/provider/types/events.go b/x/ccv/provider/types/events.go index 58d686020f..4838c1ba43 100644 --- a/x/ccv/provider/types/events.go +++ b/x/ccv/provider/types/events.go @@ -7,6 +7,7 @@ const ( EventTypeAddConsumerRewardDenom = "add_consumer_reward_denom" EventTypeRemoveConsumerRewardDenom = "remove_consumer_reward_denom" EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash" + EventTypeSetConsumerCommissionRate = "set_consumer_commission_rate" AttributeInfractionHeight = "infraction_height" AttributeInitialHeight = "initial_height" AttributeInitializationTimeout = "initialization_timeout" @@ -15,4 +16,6 @@ const ( AttributeProviderValidatorAddress = "provider_validator_address" AttributeConsumerConsensusPubKey = "consumer_consensus_pub_key" AttributeConsumerRewardDenom = "consumer_reward_denom" + AttributeConsumerCommissionRate = "consumer_commission_rate" + AttributeConsumerChainID = "consumer_chain_id" ) diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 9fb9fc9caf..c7b4a01d14 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -164,6 +164,10 @@ const ( // it allocated to the consumer rewards pool ConsumerRewardsAllocationBytePrefix + // ConsumerCommissionRatePrefix is the byte prefix used when storing a validator a per-consumer chain commission rate + // for a validator address + ConsumerCommissionRatePrefix + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -564,6 +568,15 @@ func ConsumerRewardsAllocationKey(chainID string) []byte { return append([]byte{ConsumerRewardsAllocationBytePrefix}, []byte(chainID)...) } +// ConsumerCommissionRateKey returns the key of consumer chain `chainID` and validator with `providerAddr` +func ConsumerCommissionRateKey(chainID string, providerAddr ProviderConsAddress) []byte { + return ChainIdAndConsAddrKey( + ConsumerCommissionRatePrefix, + chainID, + providerAddr.ToSdkConsAddr(), + ) +} + // // End of generic helpers section // diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index 9d797011b6..6bd05f2759 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -61,6 +61,7 @@ func getAllKeyPrefixes() []byte { providertypes.ToBeOptedInBytePrefix, providertypes.ToBeOptedOutBytePrefix, providertypes.ConsumerRewardsAllocationBytePrefix, + providertypes.ConsumerCommissionRatePrefix, } } diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 20aae43f6c..1a8d6fa145 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -24,6 +24,7 @@ const ( TypeMsgSubmitConsumerDoubleVoting = "submit_consumer_double_vote" TypeMsgOptIn = "opt_in" TypeMsgOptOut = "opt_out" + TypeMsgSetConsumerCommissionRate = "set_consumer_commission_rate" ) var ( @@ -32,6 +33,7 @@ var ( _ sdk.Msg = &MsgSubmitConsumerDoubleVoting{} _ sdk.Msg = &MsgOptIn{} _ sdk.Msg = &MsgOptOut{} + _ sdk.Msg = &MsgSetConsumerCommissionRate{} ) // NewMsgAssignConsumerKey creates a new MsgAssignConsumerKey instance. @@ -316,3 +318,45 @@ func (msg MsgOptOut) ValidateBasic() error { func (msg MsgOptOut) Type() string { return TypeMsgOptOut } + +func (msg MsgSetConsumerCommissionRate) Route() string { + return RouterKey +} + +func (msg MsgSetConsumerCommissionRate) Type() string { + return TypeMsgSetConsumerCommissionRate +} + +func (msg MsgSetConsumerCommissionRate) ValidateBasic() error { + if strings.TrimSpace(msg.ChainId) == "" { + return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot be blank") + } + + if 128 < len(msg.ChainId) { + return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot exceed 128 length") + } + _, err := sdk.ValAddressFromBech32(msg.ProviderAddr) + if err != nil { + return ErrInvalidProviderAddress + } + + if msg.Rate.IsNegative() || msg.Rate.GT(sdk.OneDec()) { + return errorsmod.Wrapf(ErrInvalidConsumerCommissionRate, "consumer commission rate should be in the range [0, 1]") + } + + return nil +} + +func (msg MsgSetConsumerCommissionRate) GetSigners() []sdk.AccAddress { + valAddr, err := sdk.ValAddressFromBech32(msg.ProviderAddr) + if err != nil { + // same behavior as in cosmos-sdk + panic(err) + } + return []sdk.AccAddress{valAddr.Bytes()} +} + +func (msg MsgSetConsumerCommissionRate) GetSignBytes() []byte { + bz := ccvtypes.ModuleCdc.MustMarshalJSON(&msg) + return sdk.MustSortJSON(bz) +} diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index 647f5f49dc..7330433f5b 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -9,6 +9,7 @@ import ( types "github.com/cometbft/cometbft/proto/tendermint/types" _ "github.com/cosmos/cosmos-proto" _ "github.com/cosmos/cosmos-sdk/codec/types" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" @@ -426,6 +427,86 @@ func (m *MsgOptOutResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgOptOutResponse proto.InternalMessageInfo +// MsgSetConsumerCommissionRate allows validators to set +// a per-consumer chain commission rate +type MsgSetConsumerCommissionRate struct { + // The validator address on the provider + ProviderAddr string `protobuf:"bytes,1,opt,name=provider_addr,json=providerAddr,proto3" json:"provider_addr,omitempty" yaml:"address"` + // The chain id of the consumer chain to set a commission rate + ChainId string `protobuf:"bytes,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + // The rate to charge delegators on the consumer chain, as a fraction + Rate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=rate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"rate"` +} + +func (m *MsgSetConsumerCommissionRate) Reset() { *m = MsgSetConsumerCommissionRate{} } +func (m *MsgSetConsumerCommissionRate) String() string { return proto.CompactTextString(m) } +func (*MsgSetConsumerCommissionRate) ProtoMessage() {} +func (*MsgSetConsumerCommissionRate) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{10} +} +func (m *MsgSetConsumerCommissionRate) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSetConsumerCommissionRate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSetConsumerCommissionRate.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 *MsgSetConsumerCommissionRate) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetConsumerCommissionRate.Merge(m, src) +} +func (m *MsgSetConsumerCommissionRate) XXX_Size() int { + return m.Size() +} +func (m *MsgSetConsumerCommissionRate) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetConsumerCommissionRate.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSetConsumerCommissionRate proto.InternalMessageInfo + +type MsgSetConsumerCommissionRateResponse struct { +} + +func (m *MsgSetConsumerCommissionRateResponse) Reset() { *m = MsgSetConsumerCommissionRateResponse{} } +func (m *MsgSetConsumerCommissionRateResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSetConsumerCommissionRateResponse) ProtoMessage() {} +func (*MsgSetConsumerCommissionRateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{11} +} +func (m *MsgSetConsumerCommissionRateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSetConsumerCommissionRateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSetConsumerCommissionRateResponse.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 *MsgSetConsumerCommissionRateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetConsumerCommissionRateResponse.Merge(m, src) +} +func (m *MsgSetConsumerCommissionRateResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSetConsumerCommissionRateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetConsumerCommissionRateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSetConsumerCommissionRateResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgAssignConsumerKey)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKey") proto.RegisterType((*MsgAssignConsumerKeyResponse)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKeyResponse") @@ -437,6 +518,8 @@ func init() { proto.RegisterType((*MsgOptInResponse)(nil), "interchain_security.ccv.provider.v1.MsgOptInResponse") proto.RegisterType((*MsgOptOut)(nil), "interchain_security.ccv.provider.v1.MsgOptOut") proto.RegisterType((*MsgOptOutResponse)(nil), "interchain_security.ccv.provider.v1.MsgOptOutResponse") + proto.RegisterType((*MsgSetConsumerCommissionRate)(nil), "interchain_security.ccv.provider.v1.MsgSetConsumerCommissionRate") + proto.RegisterType((*MsgSetConsumerCommissionRateResponse)(nil), "interchain_security.ccv.provider.v1.MsgSetConsumerCommissionRateResponse") } func init() { @@ -444,50 +527,57 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 683 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x55, 0x4d, 0x4f, 0xd4, 0x40, - 0x18, 0xde, 0x42, 0x40, 0x18, 0xd0, 0x48, 0x85, 0x00, 0x1b, 0xec, 0xea, 0x1a, 0xc5, 0x03, 0xcc, - 0x04, 0xfc, 0x8a, 0x24, 0x1e, 0x58, 0x31, 0x11, 0xcd, 0x06, 0xb3, 0x26, 0x98, 0x78, 0xb0, 0x69, - 0xa7, 0x43, 0x77, 0x42, 0x3b, 0xd3, 0xcc, 0x4c, 0x1b, 0xf6, 0x1f, 0x90, 0x78, 0xd1, 0x93, 0xf1, - 0xc6, 0x0f, 0xf0, 0x87, 0x78, 0xe4, 0xc8, 0xc9, 0x18, 0xb8, 0x78, 0xf6, 0x17, 0x98, 0x9d, 0x7e, - 0x6c, 0x89, 0x2b, 0x5f, 0xc6, 0x78, 0xeb, 0xbc, 0xef, 0x33, 0xcf, 0xf3, 0xbc, 0x6f, 0xe7, 0x9d, - 0x01, 0x0b, 0x94, 0x29, 0x22, 0x70, 0xdb, 0xa1, 0xcc, 0x96, 0x04, 0xc7, 0x82, 0xaa, 0x0e, 0xc2, - 0x38, 0x41, 0x91, 0xe0, 0x09, 0xf5, 0x88, 0x40, 0xc9, 0x12, 0x52, 0x3b, 0x30, 0x12, 0x5c, 0x71, - 0xf3, 0x56, 0x1f, 0x34, 0xc4, 0x38, 0x81, 0x39, 0x1a, 0x26, 0x4b, 0xd5, 0x49, 0x9f, 0xfb, 0x5c, - 0xe3, 0x51, 0xf7, 0x2b, 0xdd, 0x5a, 0x9d, 0xc5, 0x5c, 0x86, 0x5c, 0xda, 0x69, 0x22, 0x5d, 0xe4, - 0x29, 0x9f, 0x73, 0x3f, 0x20, 0x48, 0xaf, 0xdc, 0x78, 0x0b, 0x39, 0xac, 0x93, 0xa5, 0x10, 0x75, - 0x31, 0x0a, 0xa8, 0xdf, 0x56, 0x38, 0xa0, 0x84, 0x29, 0x89, 0x14, 0x61, 0x1e, 0x11, 0x21, 0x65, - 0x4a, 0x3b, 0x2b, 0x56, 0xd9, 0x86, 0x5a, 0x29, 0xaf, 0x3a, 0x11, 0x91, 0x88, 0x74, 0x8d, 0x31, - 0x4c, 0x52, 0x40, 0xfd, 0x93, 0x01, 0x26, 0x9b, 0xd2, 0x5f, 0x95, 0x92, 0xfa, 0xec, 0x29, 0x67, - 0x32, 0x0e, 0x89, 0x78, 0x49, 0x3a, 0xe6, 0x2c, 0x18, 0x49, 0x0b, 0xa3, 0xde, 0x8c, 0x71, 0xc3, - 0xb8, 0x3b, 0xda, 0xba, 0xa4, 0xd7, 0xeb, 0x9e, 0xf9, 0x08, 0x5c, 0xce, 0x0b, 0xb4, 0x1d, 0xcf, - 0x13, 0x33, 0x03, 0xdd, 0x7c, 0xc3, 0xfc, 0xf9, 0xad, 0x76, 0xa5, 0xe3, 0x84, 0xc1, 0x4a, 0xbd, - 0x1b, 0x25, 0x52, 0xd6, 0x5b, 0xe3, 0x39, 0x70, 0xd5, 0xf3, 0x84, 0x79, 0x13, 0x8c, 0xe3, 0x4c, - 0xc2, 0xde, 0x26, 0x9d, 0x99, 0x41, 0xcd, 0x3b, 0x86, 0x7b, 0xb2, 0x2b, 0x23, 0xbb, 0x7b, 0xb5, - 0xca, 0x8f, 0xbd, 0x5a, 0xa5, 0x6e, 0x81, 0xb9, 0x7e, 0xc6, 0x5a, 0x44, 0x46, 0x9c, 0x49, 0x52, - 0xff, 0x6c, 0x80, 0xeb, 0x4d, 0xe9, 0xbf, 0x8e, 0xdd, 0x90, 0xaa, 0x1c, 0xd0, 0xa4, 0xd2, 0x25, - 0x6d, 0x27, 0xa1, 0x3c, 0x16, 0xe6, 0x1c, 0x18, 0x95, 0x3a, 0xab, 0x88, 0xc8, 0x6a, 0xe8, 0x05, - 0xcc, 0x57, 0x60, 0x3c, 0x2c, 0xa1, 0x75, 0x11, 0x63, 0xcb, 0x0b, 0x90, 0xba, 0x18, 0x96, 0x5b, - 0x0c, 0x4b, 0x4d, 0x4d, 0x96, 0x60, 0x59, 0xa1, 0x75, 0x8c, 0xa1, 0xe4, 0x7d, 0x1e, 0xdc, 0x3e, - 0xd1, 0x5a, 0x51, 0xc4, 0xee, 0x40, 0x9f, 0x22, 0xd6, 0x78, 0xec, 0x06, 0x64, 0x93, 0x2b, 0xca, - 0xfc, 0x53, 0x8a, 0xb0, 0xc1, 0xb4, 0x17, 0x47, 0x01, 0xc5, 0x8e, 0x22, 0x76, 0xc2, 0x15, 0xb1, - 0xf3, 0xff, 0x9b, 0xd5, 0x33, 0x5f, 0xb6, 0xaf, 0x4f, 0x00, 0x5c, 0xcb, 0x37, 0x6c, 0x72, 0x45, - 0x9e, 0x65, 0xf0, 0xd6, 0x94, 0xd7, 0x2f, 0x6c, 0xbe, 0x03, 0xd3, 0x94, 0x6d, 0x09, 0x07, 0x2b, - 0xca, 0x99, 0xed, 0x06, 0x1c, 0x6f, 0xdb, 0x6d, 0xe2, 0x78, 0x44, 0xe8, 0xbf, 0x37, 0xb6, 0x7c, - 0xe7, 0xb4, 0x86, 0x3d, 0xd7, 0xe8, 0xd6, 0x54, 0x8f, 0xa6, 0xd1, 0x65, 0x49, 0xc3, 0xa7, 0xf4, - 0xac, 0xdc, 0x89, 0xa2, 0x67, 0xef, 0x0d, 0x30, 0xd2, 0x94, 0xfe, 0x46, 0xa4, 0xd6, 0xd9, 0xff, - 0x3f, 0xa6, 0x26, 0xb8, 0x9a, 0x9b, 0x29, 0x1c, 0x52, 0x30, 0x9a, 0xc6, 0x36, 0x62, 0xf5, 0x2f, - 0x1c, 0x96, 0xe4, 0xaf, 0x81, 0x89, 0x42, 0x2a, 0xd7, 0x5f, 0x3e, 0x18, 0x02, 0x83, 0x4d, 0xe9, - 0x9b, 0x1f, 0x0d, 0x30, 0xf1, 0xfb, 0x64, 0x3f, 0x86, 0x67, 0xb8, 0xb6, 0x60, 0xbf, 0xd9, 0xab, - 0xae, 0x5e, 0x78, 0x6b, 0xee, 0xcd, 0xfc, 0x62, 0x80, 0xea, 0x09, 0x33, 0xdb, 0x38, 0xab, 0xc2, - 0x9f, 0x39, 0xaa, 0x2f, 0xfe, 0x9e, 0xe3, 0x04, 0xbb, 0xc7, 0xa6, 0xf3, 0x82, 0x76, 0xcb, 0x1c, - 0x17, 0xb5, 0xdb, 0x6f, 0x36, 0xcc, 0x10, 0x0c, 0xa5, 0x73, 0xb1, 0x78, 0x56, 0x52, 0x0d, 0xaf, - 0x3e, 0x38, 0x17, 0xbc, 0x90, 0x8b, 0xc0, 0x70, 0x76, 0xca, 0xe1, 0x39, 0x08, 0x36, 0x62, 0x55, - 0x7d, 0x78, 0x3e, 0x7c, 0xae, 0xd8, 0x78, 0xf3, 0xf5, 0xd0, 0x32, 0xf6, 0x0f, 0x2d, 0xe3, 0xfb, - 0xa1, 0x65, 0x7c, 0x38, 0xb2, 0x2a, 0xfb, 0x47, 0x56, 0xe5, 0xe0, 0xc8, 0xaa, 0xbc, 0x7d, 0xe2, - 0x53, 0xd5, 0x8e, 0x5d, 0x88, 0x79, 0x98, 0xbd, 0xa7, 0xa8, 0x27, 0xb1, 0x58, 0x3c, 0xe6, 0xc9, - 0x7d, 0xb4, 0x73, 0xfc, 0x45, 0xd7, 0xb7, 0xa2, 0x3b, 0xac, 0xdf, 0xc3, 0x7b, 0xbf, 0x02, 0x00, - 0x00, 0xff, 0xff, 0x3b, 0x47, 0x43, 0x85, 0x02, 0x08, 0x00, 0x00, + // 787 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x56, 0xcd, 0x4f, 0xd4, 0x4e, + 0x18, 0xde, 0xc2, 0xef, 0xc7, 0xc7, 0x80, 0x46, 0x2a, 0x04, 0x68, 0x70, 0x57, 0x57, 0x05, 0x0f, + 0x6c, 0x1b, 0xf0, 0x2b, 0x12, 0x3d, 0xb0, 0x60, 0x22, 0x9a, 0x0d, 0xa4, 0x24, 0x98, 0x78, 0xb0, + 0x69, 0xa7, 0x43, 0x77, 0xc2, 0x76, 0xa6, 0xe9, 0x4c, 0x1b, 0xf6, 0xee, 0x81, 0xc4, 0x8b, 0x9e, + 0x8c, 0x37, 0xae, 0x26, 0x1e, 0xfd, 0x23, 0x38, 0x19, 0xe2, 0xc9, 0x78, 0x20, 0x06, 0x2e, 0x9e, + 0xfd, 0x0b, 0xcc, 0x4e, 0x3f, 0xb6, 0x1b, 0x96, 0x65, 0x81, 0x18, 0x4f, 0xbb, 0x33, 0xef, 0x33, + 0xcf, 0xfb, 0x3c, 0x6f, 0xa7, 0x4f, 0x0a, 0x66, 0x31, 0xe1, 0xc8, 0x87, 0x55, 0x13, 0x13, 0x83, + 0x21, 0x18, 0xf8, 0x98, 0xd7, 0x35, 0x08, 0x43, 0xcd, 0xf3, 0x69, 0x88, 0x6d, 0xe4, 0x6b, 0xe1, + 0x9c, 0xc6, 0xb7, 0x55, 0xcf, 0xa7, 0x9c, 0xca, 0x37, 0xdb, 0xa0, 0x55, 0x08, 0x43, 0x35, 0x41, + 0xab, 0xe1, 0x9c, 0x32, 0xea, 0x50, 0x87, 0x0a, 0xbc, 0xd6, 0xf8, 0x17, 0x1d, 0x55, 0x26, 0x21, + 0x65, 0x2e, 0x65, 0x46, 0x54, 0x88, 0x16, 0x49, 0xc9, 0xa1, 0xd4, 0xa9, 0x21, 0x4d, 0xac, 0xac, + 0x60, 0x53, 0x33, 0x49, 0x3d, 0x2e, 0x69, 0xd8, 0x82, 0x5a, 0x0d, 0x3b, 0x55, 0x0e, 0x6b, 0x18, + 0x11, 0xce, 0x34, 0x8e, 0x88, 0x8d, 0x7c, 0x17, 0x13, 0x2e, 0x94, 0xa5, 0xab, 0xf8, 0x40, 0x21, + 0x53, 0xe7, 0x75, 0x0f, 0x31, 0x0d, 0x35, 0x84, 0x11, 0x88, 0x22, 0x40, 0xf1, 0x83, 0x04, 0x46, + 0x2b, 0xcc, 0x59, 0x64, 0x0c, 0x3b, 0x64, 0x89, 0x12, 0x16, 0xb8, 0xc8, 0x7f, 0x81, 0xea, 0xf2, + 0x24, 0x18, 0x88, 0x8c, 0x61, 0x7b, 0x42, 0xba, 0x2e, 0xdd, 0x19, 0xd4, 0xfb, 0xc5, 0x7a, 0xc5, + 0x96, 0x1f, 0x82, 0x4b, 0x89, 0x41, 0xc3, 0xb4, 0x6d, 0x7f, 0xa2, 0xa7, 0x51, 0x2f, 0xcb, 0xbf, + 0x0f, 0x0a, 0x97, 0xeb, 0xa6, 0x5b, 0x5b, 0x28, 0x36, 0x76, 0x11, 0x63, 0x45, 0x7d, 0x38, 0x01, + 0x2e, 0xda, 0xb6, 0x2f, 0xdf, 0x00, 0xc3, 0x30, 0x6e, 0x61, 0x6c, 0xa1, 0xfa, 0x44, 0xaf, 0xe0, + 0x1d, 0x82, 0xcd, 0xb6, 0x0b, 0x03, 0x3b, 0xbb, 0x85, 0xdc, 0xaf, 0xdd, 0x42, 0xae, 0x98, 0x07, + 0x53, 0xed, 0x84, 0xe9, 0x88, 0x79, 0x94, 0x30, 0x54, 0xfc, 0x28, 0x81, 0x6b, 0x15, 0xe6, 0xac, + 0x07, 0x96, 0x8b, 0x79, 0x02, 0xa8, 0x60, 0x66, 0xa1, 0xaa, 0x19, 0x62, 0x1a, 0xf8, 0xf2, 0x14, + 0x18, 0x64, 0xa2, 0xca, 0x91, 0x1f, 0x7b, 0x68, 0x6e, 0xc8, 0x6b, 0x60, 0xd8, 0xcd, 0xa0, 0x85, + 0x89, 0xa1, 0xf9, 0x59, 0x15, 0x5b, 0x50, 0xcd, 0x8e, 0x58, 0xcd, 0x0c, 0x35, 0x9c, 0x53, 0xb3, + 0x1d, 0xf4, 0x16, 0x86, 0x8c, 0xf6, 0x19, 0x70, 0xbb, 0xa3, 0xb4, 0xd4, 0xc4, 0x4e, 0x4f, 0x1b, + 0x13, 0xcb, 0x34, 0xb0, 0x6a, 0x68, 0x83, 0x72, 0x4c, 0x9c, 0x53, 0x4c, 0x18, 0x60, 0xdc, 0x0e, + 0xbc, 0x1a, 0x86, 0x26, 0x47, 0x46, 0x48, 0x39, 0x32, 0x92, 0xe7, 0x1b, 0xfb, 0x99, 0xc9, 0xca, + 0x17, 0x37, 0x40, 0x5d, 0x4e, 0x0e, 0x6c, 0x50, 0x8e, 0x9e, 0xc6, 0x70, 0x7d, 0xcc, 0x6e, 0xb7, + 0x2d, 0xbf, 0x06, 0xe3, 0x98, 0x6c, 0xfa, 0x26, 0xe4, 0x98, 0x12, 0xc3, 0xaa, 0x51, 0xb8, 0x65, + 0x54, 0x91, 0x69, 0x23, 0x5f, 0x3c, 0xbd, 0xa1, 0xf9, 0xe9, 0xd3, 0x06, 0xf6, 0x4c, 0xa0, 0xf5, + 0xb1, 0x26, 0x4d, 0xb9, 0xc1, 0x12, 0x6d, 0x9f, 0x32, 0xb3, 0xec, 0x24, 0xd2, 0x99, 0xbd, 0x95, + 0xc0, 0x40, 0x85, 0x39, 0xab, 0x1e, 0x5f, 0x21, 0xff, 0xfe, 0x9a, 0xca, 0xe0, 0x4a, 0x22, 0x26, + 0x55, 0x88, 0xc1, 0x60, 0xb4, 0xb7, 0x1a, 0xf0, 0xbf, 0xa1, 0x30, 0xd3, 0xfe, 0x2a, 0x18, 0x49, + 0x5b, 0xa5, 0xfd, 0xbf, 0x4a, 0xe2, 0xdd, 0x59, 0x47, 0xe9, 0x20, 0x97, 0xa8, 0xeb, 0x62, 0xc6, + 0x30, 0x25, 0xba, 0xc9, 0xd1, 0xf1, 0xc6, 0x52, 0x97, 0xa3, 0xc9, 0x9a, 0xe9, 0x69, 0x35, 0xb3, + 0x06, 0xfe, 0xf3, 0x4d, 0x8e, 0xa2, 0x69, 0x95, 0x1f, 0xef, 0x1d, 0x14, 0x72, 0x3f, 0x0e, 0x0a, + 0xd3, 0x0e, 0xe6, 0xd5, 0xc0, 0x52, 0x21, 0x75, 0xe3, 0x94, 0x8b, 0x7f, 0x4a, 0xcc, 0xde, 0xd2, + 0xe2, 0x0b, 0x89, 0xe0, 0xb7, 0x2f, 0x25, 0x10, 0x87, 0xe0, 0x32, 0x82, 0xba, 0x60, 0xca, 0xb8, + 0x9c, 0x06, 0xb7, 0x3a, 0xf9, 0x49, 0x8c, 0xcf, 0xbf, 0xe9, 0x07, 0xbd, 0x15, 0xe6, 0xc8, 0xef, + 0x25, 0x30, 0x72, 0x3c, 0xd2, 0x1e, 0xa9, 0x5d, 0xe4, 0xb5, 0xda, 0x2e, 0x74, 0x94, 0xc5, 0x73, + 0x1f, 0x4d, 0xb4, 0xc9, 0x9f, 0x25, 0xa0, 0x74, 0x08, 0xab, 0x72, 0xb7, 0x1d, 0x4e, 0xe6, 0x50, + 0x9e, 0x5f, 0x9c, 0xa3, 0x83, 0xdc, 0x96, 0x58, 0x3a, 0xa7, 0xdc, 0x2c, 0xc7, 0x79, 0xe5, 0xb6, + 0x0b, 0x05, 0xd9, 0x05, 0xff, 0x47, 0x81, 0x50, 0xea, 0x96, 0x54, 0xc0, 0x95, 0xfb, 0x67, 0x82, + 0xa7, 0xed, 0x3c, 0xd0, 0x17, 0xbf, 0xde, 0xea, 0x19, 0x08, 0x56, 0x03, 0xae, 0x3c, 0x38, 0x1b, + 0x3e, 0xed, 0xf8, 0x49, 0x02, 0x93, 0x27, 0xbf, 0xd0, 0x5d, 0xdf, 0xcf, 0x13, 0x29, 0x94, 0x95, + 0x0b, 0x53, 0x24, 0x5a, 0xcb, 0x2f, 0xf7, 0x0e, 0xf3, 0xd2, 0xfe, 0x61, 0x5e, 0xfa, 0x79, 0x98, + 0x97, 0xde, 0x1d, 0xe5, 0x73, 0xfb, 0x47, 0xf9, 0xdc, 0xf7, 0xa3, 0x7c, 0xee, 0xd5, 0x93, 0xe3, + 0x71, 0xd0, 0xec, 0x5a, 0x4a, 0xbf, 0xb8, 0xc2, 0x7b, 0xda, 0x76, 0xeb, 0x67, 0x97, 0x48, 0x0a, + 0xab, 0x4f, 0x7c, 0xb4, 0xdc, 0xfd, 0x13, 0x00, 0x00, 0xff, 0xff, 0x76, 0xb7, 0xbf, 0x16, 0xa7, + 0x09, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -507,6 +597,7 @@ type MsgClient interface { SubmitConsumerDoubleVoting(ctx context.Context, in *MsgSubmitConsumerDoubleVoting, opts ...grpc.CallOption) (*MsgSubmitConsumerDoubleVotingResponse, error) OptIn(ctx context.Context, in *MsgOptIn, opts ...grpc.CallOption) (*MsgOptInResponse, error) OptOut(ctx context.Context, in *MsgOptOut, opts ...grpc.CallOption) (*MsgOptOutResponse, error) + SetConsumerCommissionRate(ctx context.Context, in *MsgSetConsumerCommissionRate, opts ...grpc.CallOption) (*MsgSetConsumerCommissionRateResponse, error) } type msgClient struct { @@ -562,6 +653,15 @@ func (c *msgClient) OptOut(ctx context.Context, in *MsgOptOut, opts ...grpc.Call return out, nil } +func (c *msgClient) SetConsumerCommissionRate(ctx context.Context, in *MsgSetConsumerCommissionRate, opts ...grpc.CallOption) (*MsgSetConsumerCommissionRateResponse, error) { + out := new(MsgSetConsumerCommissionRateResponse) + err := c.cc.Invoke(ctx, "/interchain_security.ccv.provider.v1.Msg/SetConsumerCommissionRate", 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) @@ -569,6 +669,7 @@ type MsgServer interface { SubmitConsumerDoubleVoting(context.Context, *MsgSubmitConsumerDoubleVoting) (*MsgSubmitConsumerDoubleVotingResponse, error) OptIn(context.Context, *MsgOptIn) (*MsgOptInResponse, error) OptOut(context.Context, *MsgOptOut) (*MsgOptOutResponse, error) + SetConsumerCommissionRate(context.Context, *MsgSetConsumerCommissionRate) (*MsgSetConsumerCommissionRateResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -590,6 +691,9 @@ func (*UnimplementedMsgServer) OptIn(ctx context.Context, req *MsgOptIn) (*MsgOp func (*UnimplementedMsgServer) OptOut(ctx context.Context, req *MsgOptOut) (*MsgOptOutResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method OptOut not implemented") } +func (*UnimplementedMsgServer) SetConsumerCommissionRate(ctx context.Context, req *MsgSetConsumerCommissionRate) (*MsgSetConsumerCommissionRateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetConsumerCommissionRate not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -685,6 +789,24 @@ func _Msg_OptOut_Handler(srv interface{}, ctx context.Context, dec func(interfac return interceptor(ctx, in, info, handler) } +func _Msg_SetConsumerCommissionRate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSetConsumerCommissionRate) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SetConsumerCommissionRate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/interchain_security.ccv.provider.v1.Msg/SetConsumerCommissionRate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SetConsumerCommissionRate(ctx, req.(*MsgSetConsumerCommissionRate)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "interchain_security.ccv.provider.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -709,6 +831,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "OptOut", Handler: _Msg_OptOut_Handler, }, + { + MethodName: "SetConsumerCommissionRate", + Handler: _Msg_SetConsumerCommissionRate_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "interchain_security/ccv/provider/v1/tx.proto", @@ -1050,6 +1176,76 @@ func (m *MsgOptOutResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *MsgSetConsumerCommissionRate) 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 *MsgSetConsumerCommissionRate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSetConsumerCommissionRate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.Rate.Size() + i -= size + if _, err := m.Rate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.ChainId) > 0 { + i -= len(m.ChainId) + copy(dAtA[i:], m.ChainId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChainId))) + i-- + dAtA[i] = 0x12 + } + if len(m.ProviderAddr) > 0 { + i -= len(m.ProviderAddr) + copy(dAtA[i:], m.ProviderAddr) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProviderAddr))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgSetConsumerCommissionRateResponse) 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 *MsgSetConsumerCommissionRateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSetConsumerCommissionRateResponse) 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 @@ -1203,6 +1399,34 @@ func (m *MsgOptOutResponse) Size() (n int) { return n } +func (m *MsgSetConsumerCommissionRate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ProviderAddr) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ChainId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Rate.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgSetConsumerCommissionRateResponse) 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 } @@ -2137,6 +2361,204 @@ func (m *MsgOptOutResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgSetConsumerCommissionRate) 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: MsgSetConsumerCommissionRate: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSetConsumerCommissionRate: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProviderAddr", 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.ProviderAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", 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.ChainId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Rate", 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 + } + if err := m.Rate.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 *MsgSetConsumerCommissionRateResponse) 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: MsgSetConsumerCommissionRateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSetConsumerCommissionRateResponse: 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 From 2588e310a12cc5d8fc4a6067c5be27d0475553c5 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 8 Mar 2024 15:43:07 +0100 Subject: [PATCH 18/18] test: update integration test suite for PSS (#1687) * draft multi consumer transfer setup and test * format multi consumer distribution test * update test for democ consumer chains * nits * nit --- tests/integration/distribution.go | 92 +++++++++++++++++++++++++++ tests/integration/setup.go | 67 +++++++++++++++---- testutil/ibc_testing/generic_setup.go | 19 ++++++ testutil/integration/debug_test.go | 4 ++ 4 files changed, 168 insertions(+), 14 deletions(-) diff --git a/tests/integration/distribution.go b/tests/integration/distribution.go index 65473d3bcf..055777b86f 100644 --- a/tests/integration/distribution.go +++ b/tests/integration/distribution.go @@ -1032,3 +1032,95 @@ func (s *CCVTestSuite) TestAllocateTokensToValidator() { }) } } + +// TestMultiConsumerRewardsDistribution tests the rewards distribution of multiple consumers chains +func (s *CCVTestSuite) TestMultiConsumerRewardsDistribution() { + s.SetupAllCCVChannels() + s.SetupAllTransferChannels() + + providerBankKeeper := s.providerApp.GetTestBankKeeper() + providerAccountKeeper := s.providerApp.GetTestAccountKeeper() + + // check that the reward provider pool is empty + rewardPool := providerAccountKeeper.GetModuleAccount(s.providerCtx(), providertypes.ConsumerRewardsPool).GetAddress() + rewardCoins := providerBankKeeper.GetAllBalances(s.providerCtx(), rewardPool) + s.Require().Empty(rewardCoins) + + totalConsumerRewards := sdk.Coins{} + + // Iterate over the consumers and perform the reward distribution + // to the provider + for chainID := range s.consumerBundles { + bundle := s.consumerBundles[chainID] + consumerKeeper := bundle.App.GetConsumerKeeper() + bankKeeper := bundle.App.GetTestBankKeeper() + accountKeeper := bundle.App.GetTestAccountKeeper() + + // set the consumer reward denom and the block per distribution params + params := consumerKeeper.GetConsumerParams(bundle.GetCtx()) + params.RewardDenoms = []string{sdk.DefaultBondDenom} + // set the reward distribution to be performed during the next block + params.BlocksPerDistributionTransmission = int64(1) + consumerKeeper.SetParams(bundle.GetCtx(), params) + + // transfer the consumer reward pool to the provider + var rewardsPerConsumer sdk.Coin + + // check the consumer pool balance + // Note that for a democracy consumer chain the pool may already be filled + if pool := bankKeeper.GetAllBalances( + bundle.GetCtx(), + accountKeeper.GetModuleAccount(bundle.GetCtx(), consumertypes.ConsumerToSendToProviderName).GetAddress(), + ); pool.Empty() { + // if pool is empty, fill it with some tokens + rewardsPerConsumer = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) + err := bankKeeper.SendCoinsFromAccountToModule( + bundle.GetCtx(), + bundle.Chain.SenderAccount.GetAddress(), + consumertypes.ConsumerToSendToProviderName, + sdk.NewCoins(rewardsPerConsumer), + ) + s.Require().NoError(err) + } else { + // execute the internal rewards distribution + // to save the pool's balance before + // it gets transferred to the provider in EndBlock + consumerKeeper.DistributeRewardsInternally(bundle.GetCtx()) + pool = bankKeeper.GetAllBalances( + bundle.GetCtx(), + accountKeeper.GetModuleAccount(bundle.GetCtx(), consumertypes.ConsumerToSendToProviderName).GetAddress(), + ) + s.Require().Len(pool, 1, "consumer reward pool cannot have mutiple token denoms") + rewardsPerConsumer = pool[0] + } + + // perform the reward transfer + bundle.Chain.NextBlock() + + // relay IBC transfer packet from consumer to provider + relayAllCommittedPackets( + s, + bundle.Chain, + bundle.TransferPath, + transfertypes.PortID, + bundle.TransferPath.EndpointA.ChannelID, + 1, + ) + + // construct the denom of the reward tokens for the provider + prefixedDenom := ibctransfertypes.GetPrefixedDenom( + transfertypes.PortID, + bundle.TransferPath.EndpointB.ChannelID, + rewardsPerConsumer.Denom, + ) + provIBCDenom := ibctransfertypes.ParseDenomTrace(prefixedDenom).IBCDenom() + + // sum the total rewards transferred to the provider + totalConsumerRewards = totalConsumerRewards. + Add(sdk.NewCoin(provIBCDenom, rewardsPerConsumer.Amount)) + } + + // Check that the provider receives the rewards of each consumer + rewardCoins = providerBankKeeper.GetAllBalances(s.providerCtx(), rewardPool) + s.Require().Equal(totalConsumerRewards, rewardCoins, totalConsumerRewards.String(), rewardCoins.String()) +} diff --git a/tests/integration/setup.go b/tests/integration/setup.go index e401324c82..91c4c4d21f 100644 --- a/tests/integration/setup.go +++ b/tests/integration/setup.go @@ -136,12 +136,16 @@ func (suite *CCVTestSuite) SetupTest() { preProposalKeyAssignment(suite, icstestingutils.FirstConsumerChainID) // start consumer chains - numConsumers := 5 suite.consumerBundles = make(map[string]*icstestingutils.ConsumerBundle) - for i := 0; i < numConsumers; i++ { + for i := 0; i < icstestingutils.NumConsumers; i++ { bundle := suite.setupConsumerCallback(&suite.Suite, suite.coordinator, i) suite.consumerBundles[bundle.Chain.ChainID] = bundle suite.registerPacketSniffer(bundle.Chain) + + // check that TopN is correctly set for the consumer + topN, found := providerKeeper.GetTopN(suite.providerCtx(), bundle.Chain.ChainID) + suite.Require().True(found) + suite.Require().Equal(bundle.TopN, topN) } // initialize each consumer chain with it's corresponding genesis state @@ -222,7 +226,6 @@ func initConsumerChain( ) s.Require().True(found, "provider endpoint clientID not found") bundle.Path.EndpointB.ClientID = providerEndpointClientID - // Set consumer endpoint's clientID consumerKeeper := bundle.GetKeeper() consumerEndpointClientID, found := consumerKeeper.GetProviderClientID(bundle.GetCtx()) @@ -302,34 +305,70 @@ func (suite *CCVTestSuite) ExecuteCCVChannelHandshake(path *ibctesting.Path) { // TODO: Make SetupTransferChannel functional for multiple consumers by pattern matching SetupCCVChannel. // See: https://github.com/cosmos/interchain-security/issues/506 +// SetupTransferChannel setup the transfer channel of the first consumer chain among multiple func (suite *CCVTestSuite) SetupTransferChannel() { - // transfer path will use the same connection as ccv path + suite.setupTransferChannel( + suite.transferPath, + suite.path, + suite.consumerApp.GetConsumerKeeper().GetDistributionTransmissionChannel( + suite.consumerChain.GetContext(), + ), + ) +} - suite.transferPath.EndpointA.ClientID = suite.path.EndpointA.ClientID - suite.transferPath.EndpointA.ConnectionID = suite.path.EndpointA.ConnectionID - suite.transferPath.EndpointB.ClientID = suite.path.EndpointB.ClientID - suite.transferPath.EndpointB.ConnectionID = suite.path.EndpointB.ConnectionID +func (suite *CCVTestSuite) setupTransferChannel( + transferPath *ibctesting.Path, + ccvPath *ibctesting.Path, + channelID string, +) { + // transfer path will use the same connection as ccv path + transferPath.EndpointA.ClientID = ccvPath.EndpointA.ClientID + transferPath.EndpointA.ConnectionID = ccvPath.EndpointA.ConnectionID + transferPath.EndpointB.ClientID = ccvPath.EndpointB.ClientID + transferPath.EndpointB.ConnectionID = ccvPath.EndpointB.ConnectionID // CCV channel handshake will automatically initiate transfer channel handshake on ACK // so transfer channel will be on stage INIT when CompleteSetupCCVChannel returns. - suite.transferPath.EndpointA.ChannelID = suite.consumerApp.GetConsumerKeeper().GetDistributionTransmissionChannel( - suite.consumerChain.GetContext()) + transferPath.EndpointA.ChannelID = channelID // Complete TRY, ACK, CONFIRM for transfer path - err := suite.transferPath.EndpointB.ChanOpenTry() + err := transferPath.EndpointB.ChanOpenTry() suite.Require().NoError(err) - err = suite.transferPath.EndpointA.ChanOpenAck() + err = transferPath.EndpointA.ChanOpenAck() suite.Require().NoError(err) - err = suite.transferPath.EndpointB.ChanOpenConfirm() + err = transferPath.EndpointB.ChanOpenConfirm() suite.Require().NoError(err) // ensure counterparty is up to date - err = suite.transferPath.EndpointA.UpdateClient() + err = transferPath.EndpointA.UpdateClient() suite.Require().NoError(err) } +// SetupAllTransferChannel setup all consumer chains transfer channel +func (suite *CCVTestSuite) SetupAllTransferChannels() { + // setup the first consumer transfer channel + suite.SetupTransferChannel() + + // setup all the remaining consumers transfer channels + for chainID := range suite.consumerBundles { + // skip fist consumer + if chainID == suite.consumerChain.ChainID { + continue + } + + // get the bundle for the chain ID + bundle := suite.consumerBundles[chainID] + // setup the transfer channel + suite.setupTransferChannel( + bundle.TransferPath, + bundle.Path, + bundle.App.GetConsumerKeeper().GetDistributionTransmissionChannel(bundle.GetCtx()), + ) + } +} + func (s CCVTestSuite) validateEndpointsClientConfig(consumerBundle icstestingutils.ConsumerBundle) { //nolint:govet // this is a test so we can copy locks consumerKeeper := consumerBundle.GetKeeper() providerStakingKeeper := s.providerApp.GetTestStakingKeeper() diff --git a/testutil/ibc_testing/generic_setup.go b/testutil/ibc_testing/generic_setup.go index 6d17337853..da00d76b85 100644 --- a/testutil/ibc_testing/generic_setup.go +++ b/testutil/ibc_testing/generic_setup.go @@ -30,10 +30,16 @@ type ( // and/or democracy consumer app.go implementation. You should not need to modify or replicate this file // to run integration tests against your app.go implementations! +const ( + // Default number of consumer chains + NumConsumers = 5 +) + var ( FirstConsumerChainID string provChainID string democConsumerChainID string + consumerTopNParams [NumConsumers]uint32 ) func init() { @@ -42,6 +48,9 @@ func init() { FirstConsumerChainID = ibctesting.GetChainID(2) provChainID = ibctesting.GetChainID(1) democConsumerChainID = ibctesting.GetChainID(5000) + // TopN parameter values per consumer chain initiated + // sorted in ascending order i.e. testchain2, testchain3, ..., testchain6 + consumerTopNParams = [NumConsumers]uint32{100, 100, 100, 100, 100} } // ConsumerBundle serves as a way to store useful in-mem consumer app chain state @@ -51,6 +60,7 @@ type ConsumerBundle struct { App testutil.ConsumerApp Path *ibctesting.Path TransferPath *ibctesting.Path + TopN uint32 } // GetCtx returns the context for the ConsumerBundle @@ -116,6 +126,9 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp]( index int, appIniter ValSetAppIniter, ) *ConsumerBundle { + // check index isn't bigger that the number of consumers + s.Require().LessOrEqual(index, NumConsumers) + // consumer chain ID chainID := ibctesting.GetChainID(index + 2) @@ -126,6 +139,7 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp]( prop := testkeeper.GetTestConsumerAdditionProp() prop.ChainId = chainID + prop.Top_N = consumerTopNParams[index] // isn't used in CreateConsumerClient // NOTE: the initial height passed to CreateConsumerClient // must be the height on the consumer when InitGenesis is called prop.InitialHeight = clienttypes.Height{RevisionNumber: 0, RevisionHeight: 3} @@ -135,6 +149,10 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp]( ) s.Require().NoError(err) + // set the consumer TopN here since the test suite setup only used the consumer addition prop + // to create the consumer genesis, see BeginBlockInit in /x/ccv/provider/keeper/proposal.go. + providerKeeper.SetTopN(providerChain.GetContext(), chainID, prop.Top_N) + // commit the state on the provider chain coordinator.CommitBlock(providerChain) @@ -174,5 +192,6 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp]( return &ConsumerBundle{ Chain: testChain, App: consumerToReturn, + TopN: prop.Top_N, } } diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index c370e8e701..2481e865ab 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -299,3 +299,7 @@ func TestTransferConsumerRewardsToDistributionModule(t *testing.T) { func TestAllocateTokensToValidator(t *testing.T) { runCCVTestByName(t, "TestAllocateTokensToValidator") } + +func TestMultiConsumerRewardsDistribution(t *testing.T) { + runCCVTestByName(t, "TestMultiConsumerRewardsDistribution") +}