From 157efcbbe7743d5aff8c55bf652d57fe60ad1a92 Mon Sep 17 00:00:00 2001 From: insumity Date: Thu, 7 Mar 2024 14:48:39 +0100 Subject: [PATCH 1/9] docs: modify epochs ADR to capture latest design (#1668) * modified ADR to capture the epoch design --- docs/docs/adrs/adr-014-epochs.md | 48 +++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/docs/docs/adrs/adr-014-epochs.md b/docs/docs/adrs/adr-014-epochs.md index ce75063ec7..2f4f5df087 100644 --- a/docs/docs/adrs/adr-014-epochs.md +++ b/docs/docs/adrs/adr-014-epochs.md @@ -5,7 +5,8 @@ title: Epochs # ADR 014: Epochs ## Changelog -* 2024-01-105: Proposed, first draft of ADR. +* 2024-01-05: Proposed, first draft of ADR. +* 2024-02-29: Updated so that it describes the implementation where we store the whole consumer validator set. ## Status @@ -22,24 +23,38 @@ As a matter of fact, this already happens due to relaying delays. As a solution, this ADR introduces the concept of _epochs_. An epoch consists of multiple blocks. The provider sends `VSCPacket`s once per epoch. -A `VSCPacket` contains all the valset changes that occurred throughout the epoch. +A `VSCPacket` contains all the validator updates that are needed by consumer chains. ## Decision The implementation of epochs requires the following changes: -- Add a param that sets the number of blocks in an epoch, i.e., `BlocksPerEpoch`. - We can use `BlockHeight() % BlocksPerEpoch == 0` to decide when an epoch is over. - Note that `BlocksPerEpoch` can also be a hardcoded constant as it's unlikely that it will change often. -- In every provider `EndBlock()`, instead of queueing `VSCPacket` data for every consumer chain, we accumulate the validator changes (similarly to how is done on the consumer, see `AccumulateChanges`). -- Modify the key assignment logic to allow for `MustApplyKeyAssignmentToValUpdates` to be called once per epoch. - Currently, this method is called in every block before queueing a `VSCPacket`. - Also, the method uses the `KeyAssignmentReplacement` state, which is pruned at the end of every block. - This needs to be done once per epoch instead. -- At the end of every epoch, if there were validator set changes on the provider, then for every consumer chain, construct a `VSCPacket` with all the accumulated validator changes and add it to the list of `PendingVSCPackets`. - -As an optional change, to better accommodate [the Partial Set Security design](https://informalsystems.notion.site/Partial-Set-Security-398ca9a1453740068be5c7964a4059bb), the validator changes should be accumulated per consumer chain. -Like this, it would make it easier to have validators opting out from certain consumer chains. +- For each consumer chain, we store the consumer validator set that is currently (i.e., in this epoch) validating the + consumer chain. For each validator in the set we store i) its voting power, and ii) the public key that it is + using on the consumer chain during the current (i.e., ongoing) epoch. + The initial consumer validator set for a chain is set during the creation of the consumer genesis. +- We introduce the `BlocksPerEpoch` param that sets the number of blocks in an epoch. By default, `BlocksPerEpoch` is + set to be 600 which corresponds to 1 hour, assuming 6 seconds per block. This param can be changed through + a _governance proposal_ to be anywhere between `[1, MaxBlocksPerEpoch]` where `MaxBlocksPerEpoch` can be up to 1200 + (2 hours if we assume 6 seconds per block). In the provider `EndBlock` we check `BlockHeight() % BlocksPerEpoch() == 0` + to decide when an epoch has ended. +- At the end of every epoch, if there were validator set changes on the provider, then for every consumer chain, we + construct a `VSCPacket` with all the validator updates and add it to the list of `PendingVSCPackets`. We compute the + validator updates needed by a consumer chain by comparing the stored list of consumer validators with the current + bonded validators on the provider, with something similar to this: +```go +// get the valset that has been validating the consumer chain during this epoch +currentValidators := GetConsumerValSet(consumerChain) +// generate the validator updates needed to be sent through a `VSCPacket` by comparing the current validators +// in the epoch with the latest bonded validators +valUpdates := DiffValidators(currentValidators, stakingmodule.GetBondedValidators()) +// update the current validators set for the upcoming epoch to be the latest bonded validators instead +SetConsumerValSet(stakingmodule.GetBondedValidators()) +``` +Note that a validator can change its consumer public key for a specific consumer chain an arbitrary amount of times during +a block and during an epoch. Then, when we generate the validator updates in `DiffValidators`, we have to check on whether +the current consumer public key (retrieved by calling `GetValidatorConsumerPubKey`) is different from the consumer public +key the validator was using in the current epoch. ## Consequences @@ -47,12 +62,13 @@ Like this, it would make it easier to have validators opting out from certain co - Reduce the cost of relaying. - Reduce the amount of IBC packets needed for ICS. +- Simplifies [key-assignment code](https://github.com/cosmos/interchain-security/blob/main/docs/docs/adrs/adr-001-key-assignment.md) because + we only need to check if the `consumer_public_key` has been modified since the last epoch to generate an update. ### Negative -- Additional logic on the provider side as valset changes need to be accumulated. -- The changes might impact the key-assignment logic so special care is needed to avoid introducing bugs. - Increase the delay in the propagation of validator set changes (but for reasonable epoch lengths on the order of ~hours or less, this is unlikely to be significant). + ### Neutral N/A From 6c0549cbff0bf8af5944be67f5625da3c4d9b7a1 Mon Sep 17 00:00:00 2001 From: insumity Date: Thu, 7 Mar 2024 16:19:51 +0100 Subject: [PATCH 2/9] feat!: introduce epochs (#1660) * cleanup ./changelog entries * rebase * 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) Co-authored-by: Marius Poke * docs: update changelog for v4.0.0 (#1578) update changelog * docs: prepare for v4.0.0 (#1581) * unclog build * update release notes * update release date * added proto declaration * temp commit * temp commit * more changes * first commit * add param and fix tests * reduce epoch size for e2e * clean up * mbt fix * fix diff bug * cleaning up * cleaning up * cleaning up * cleaning up * cleaning up * cleaning up * added more tests * more fixes * nit fixes * cleaning up * increase downtime by one block * fix logs * took into account Marius' comments * tiny fixes * Update x/ccv/provider/keeper/params.go Co-authored-by: Simon Noetzlin * use Bech32 addresses as keys for maps * refactor nextBlocks(epoch) to nextEpoch * fixed comment * Remove new block creation during consumer chain setup * Revert "Remove new block creation during consumer chain setup" This reverts commit 85a52b74c1998dfebadfedbf287c9c9547cfec78. * added simple param test * added upper bound and addressed a comment * Add another edge case for diffing * used smarted solution (based on Philip's comment) for diffing validators * refactor!: remove key-assignment replacements (#1672) * initial commit * removed KeyAssignmentReplacementsKey * refactor: simplify key-assignment logic (#1684) * fixed typo: depreciated to deprecated --------- Co-authored-by: Marius Poke * add the epoch param in the docs --------- Co-authored-by: mpoke Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Simon Noetzlin Co-authored-by: Philip Offtermatt --- docs/docs/adrs/adr-001-key-assignment.md | 116 +--- docs/docs/introduction/params.md | 7 + .../ccv/provider/v1/provider.proto | 14 + tests/e2e/actions.go | 14 +- tests/e2e/config.go | 33 +- tests/integration/common.go | 11 +- tests/integration/distribution.go | 6 +- tests/integration/expired_client.go | 16 +- tests/integration/key_assignment.go | 18 +- tests/integration/setup.go | 6 + tests/integration/slashing.go | 4 +- tests/integration/soft_opt_out.go | 6 +- tests/integration/unbonding.go | 11 +- tests/integration/valset_update.go | 4 +- tests/mbt/driver/mbt_test.go | 20 +- tests/mbt/driver/setup.go | 20 + testutil/keeper/unit_test_helpers.go | 1 - x/ccv/consumer/types/keys.go | 4 +- x/ccv/provider/keeper/grpc_query_test.go | 1 - x/ccv/provider/keeper/key_assignment.go | 244 +------- x/ccv/provider/keeper/key_assignment_test.go | 105 +--- x/ccv/provider/keeper/params.go | 8 + x/ccv/provider/keeper/params_test.go | 1 + x/ccv/provider/keeper/proposal.go | 12 +- x/ccv/provider/keeper/proposal_test.go | 1 + x/ccv/provider/keeper/relay.go | 29 +- x/ccv/provider/keeper/relay_test.go | 60 +- x/ccv/provider/keeper/validator_set_update.go | 178 ++++++ .../keeper/validator_set_update_test.go | 355 ++++++++++++ x/ccv/provider/types/genesis_test.go | 26 +- x/ccv/provider/types/keys.go | 19 +- x/ccv/provider/types/keys_test.go | 2 +- x/ccv/provider/types/params.go | 30 + x/ccv/provider/types/params_test.go | 33 +- x/ccv/provider/types/provider.pb.go | 526 ++++++++++++++---- 35 files changed, 1313 insertions(+), 628 deletions(-) create mode 100644 x/ccv/provider/keeper/validator_set_update.go create mode 100644 x/ccv/provider/keeper/validator_set_update_test.go diff --git a/docs/docs/adrs/adr-001-key-assignment.md b/docs/docs/adrs/adr-001-key-assignment.md index 874321db0c..36dbdfdb09 100644 --- a/docs/docs/adrs/adr-001-key-assignment.md +++ b/docs/docs/adrs/adr-001-key-assignment.md @@ -7,6 +7,7 @@ title: Key Assignment ## Changelog * 2022-12-01: Initial Draft +* 2024-03-01: Updated to take into account they key-assigment-replacement deprecation. ## Status @@ -30,10 +31,6 @@ ConsumerValidatorsBytePrefix | len(chainID) | chainID | providerConsAddress -> c ```golang ValidatorsByConsumerAddrBytePrefix | len(chainID) | chainID | consumerConsAddress -> providerConsAddress ``` -- `KeyAssignmentReplacements` - Stores the key assignments that need to be replaced in the current block. Needed to apply the key assignments received in a block to the validator updates sent to the consumer chains. -```golang -KeyAssignmentReplacementsBytePrefix | len(chainID) | chainID | providerConsAddress -> abci.ValidatorUpdate{PubKey: oldConsumerKey, Power: currentPower}, -``` - `ConsumerAddrsToPrune` - Stores the mapping from VSC ids to consumer validators addresses. Needed for pruning `ValidatorByConsumerAddr`. ```golang ConsumerAddrsToPruneBytePrefix | len(chainID) | chainID | vscID -> []consumerConsAddresses @@ -67,20 +64,6 @@ if _, consumerRegistered := GetConsumerClientId(chainID); consumerRegistered { oldConsumerAddr := utils.TMCryptoPublicKeyToConsAddr(oldConsumerKey) vscID := GetValidatorSetUpdateId() AppendConsumerAddrsToPrune(chainID, vscID, oldConsumerAddr) - } else { - // the validator had no key assigned on this consumer chain - oldConsumerKey := validator.TmConsPublicKey() - } - - // check whether the validator is valid, i.e., its power is positive - if currentPower := stakingKeeper.GetLastValidatorPower(providerAddr); currentPower > 0 { - // to enable multiple calls of AssignConsumerKey in the same block by the same validator - // the key assignment replacement should not be overwritten - if _, found := GetKeyAssignmentReplacement(chainID, providerConsAddr); !found { - // store old key and power for modifying the valset update in EndBlock - oldKeyAssignment := abci.ValidatorUpdate{PubKey: oldConsumerKey, Power: currentPower} - SetKeyAssignmentReplacement(chainID, providerConsAddr, oldKeyAssignment) - } } } else { // if the consumer chain is not registered, then remove the previous reverse mapping @@ -129,89 +112,24 @@ func (k Keeper) MakeConsumerGenesis(chainID string) (gen consumertypes.GenesisSt } ``` -On `EndBlock` while queueing `VSCPacket`s to send to registered consumer chains: +Note that key assignment works hand-in-hand with [epochs](https://github.com/cosmos/interchain-security/blob/main/docs/docs/adrs/adr-014-epochs.md). +For each consumer chain, we store the consumer validator set that is currently (i.e., in this epoch) validating the consumer chain. +Specifically, for each validator in the set we store among others, the public key that it is using on the consumer chain during the current (i.e., ongoing) epoch. +At the end of every epoch, if there were validator set changes on the provider, then for every consumer chain, we construct a `VSCPacket` +with all the validator updates and add it to the list of `PendingVSCPacket`s. We compute the validator updates needed by a consumer chain by +comparing the stored list of consumer validators with the current bonded validators on the provider, with something similar to this: ```golang -func QueueVSCPackets() { - valUpdateID := GetValidatorSetUpdateId() - // get the validator updates from the staking module - valUpdates := stakingKeeper.GetValidatorUpdates() - - IterateConsumerChains(func(chainID, clientID string) (stop bool) { - // apply the key assignment to the validator updates - valUpdates := ApplyKeyAssignmentToValUpdates(chainID, valUpdates) - // .. - }) - // ... -} - -func ApplyKeyAssignmentToValUpdates( - chainID string, - valUpdates []abci.ValidatorUpdate, -) (newUpdates []abci.ValidatorUpdate) { - for _, valUpdate := range valUpdates { - providerAddr := utils.TMCryptoPublicKeyToConsAddr(valUpdate.PubKey) - - // if a key assignment replacement is found, then - // remove the valupdate with the old consumer key - // and create two new valupdates - prevConsumerKey, _, found := GetKeyAssignmentReplacement(chainID, providerAddr) - if found { - // set the old consumer key's power to 0 - newUpdates = append(newUpdates, abci.ValidatorUpdate{ - PubKey: prevConsumerKey, - Power: 0, - }) - // set the new consumer key's power to the power in the update - newConsumerKey := GetValidatorConsumerPubKey(chainID, providerAddr) - newUpdates = append(newUpdates, abci.ValidatorUpdate{ - PubKey: newConsumerKey, - Power: valUpdate.Power, - }) - // delete key assignment replacement - DeleteKeyAssignmentReplacement(chainID, providerAddr) - } else { - // there is no key assignment replacement; - // check if the validator's key is assigned - consumerKey, found := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) - if found { - // replace the update containing the provider key - // with an update containing the consumer key - newUpdates = append(newUpdates, abci.ValidatorUpdate{ - PubKey: consumerKey, - Power: valUpdate.Power, - }) - } else { - // keep the same update - newUpdates = append(newUpdates, valUpdate) - } - } - } - - // iterate over the remaining key assignment replacements - IterateKeyAssignmentReplacements(chainID, func( - pAddr sdk.ConsAddress, - prevCKey tmprotocrypto.PublicKey, - power int64, - ) (stop bool) { - // set the old consumer key's power to 0 - newUpdates = append(newUpdates, abci.ValidatorUpdate{ - PubKey: prevCKey, - Power: 0, - }) - // set the new consumer key's power to the power in key assignment replacement - newConsumerKey := GetValidatorConsumerPubKey(chainID, pAddr) - newUpdates = append(newUpdates, abci.ValidatorUpdate{ - PubKey: newConsumerKey, - Power: power, - }) - return false - }) - - // remove all the key assignment replacements - - return newUpdates -} +// get the valset that has been validating the consumer chain during this epoch +currentValidators := GetConsumerValSet(consumerChain) +// generate the validator updates needed to be sent through a `VSCPacket` by comparing the current validators +// in the epoch with the latest bonded validators +valUpdates := DiffValidators(currentValidators, stakingmodule.GetBondedValidators()) +// update the current validators set for the upcoming epoch to be the latest bonded validators instead +SetConsumerValSet(stakingmodule.GetBondedValidators()) ``` +where `DiffValidators` internally checks if the consumer public key for a validator has changed since the last +epoch and if so generates a validator update. This way, a validator can change its consumer public key for a consumer +chain an arbitrary amount of times and only the last set consumer public key would be taken into account. On receiving a `SlashPacket` from a consumer chain with id `chainID` for a infraction of a validator `data.Validator`: ```golang diff --git a/docs/docs/introduction/params.md b/docs/docs/introduction/params.md index 5a9e8462c7..69994f261c 100644 --- a/docs/docs/introduction/params.md +++ b/docs/docs/introduction/params.md @@ -149,3 +149,10 @@ This param would allow provider binaries to panic deterministically in the event `RetryDelayPeriod` exists on the consumer for **ICS versions >= v3.2.0** (introduced by the implementation of [ADR-008](../adrs/adr-008-throttle-retries.md)) and is the period at which the consumer retries to send a `SlashPacket` that was rejected by the provider. + +## Epoch Parameters + +### BlocksPerEpoch +`BlocksPerEpoch` exists on the provider for **ICS versions >= 3.3.0** (introduced by the implementation of [ADR-014](../adrs/adr-014-epochs.md)) +and corresponds to the number of blocks that constitute an epoch. This param is set to 600 by default and cannot exceed 1200. +Assuming we need 6 seconds per block, the default value corresponds to 1 hour and the maximum to 2 hours. \ 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..4da89022e8 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -188,6 +188,9 @@ message Params { // The fee required to be paid to add a reward denom cosmos.base.v1beta1.Coin consumer_reward_denom_registration_fee = 9 [ (gogoproto.nullable) = false ]; + + // The number of blocks that comprise an epoch. + int64 blocks_per_epoch = 10; } // SlashAcks contains cons addresses of consumer chain validators @@ -295,3 +298,14 @@ message ConsumerAddrsToPrune { uint64 vsc_id = 2; AddressList consumer_addrs = 3; } + +// ConsumerValidator is used to facilitate epoch-based transitions. It contains relevant info for +// a validator that is expected to validate on a consumer chain during an epoch. +message ConsumerValidator { + // validator's consensus address on the provider chain + bytes provider_cons_addr = 1; + // voting power the validator has during this epoch + int64 power = 2; + // public key the validator uses on the consumer chain during this epoch + tendermint.crypto.PublicKey consumer_public_key = 3; +} \ No newline at end of file diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 0ed7ea8e49..9db1bac2ca 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -1431,6 +1431,12 @@ func (tr TestConfig) relayPacketsGorelayer( target ExecutionTarget, verbose bool, ) { + // Because `.app_state.provider.params.blocks_per_epoch` is set to 3 in the E2E tests, we wait 3 blocks + // before relaying the packets to guarantee that at least one epoch passes and hence any `VSCPacket`s get + // queued and are subsequently relayed. + tr.waitBlocks(action.ChainA, 3, 90*time.Second) + tr.waitBlocks(action.ChainB, 3, 90*time.Second) + pathName := tr.GetPathNameForGorelayer(action.ChainA, action.ChainB) // rly transact relay-packets [path-name] --channel [channel-id] @@ -1455,6 +1461,12 @@ func (tr TestConfig) relayPacketsHermes( target ExecutionTarget, verbose bool, ) { + // Because `.app_state.provider.params.blocks_per_epoch` is set to 3 in the E2E tests, we wait 3 blocks + // before relaying the packets to guarantee that at least one epoch passes and hence any `VSCPacket`s get + // queued and are subsequently relayed. + tr.waitBlocks(action.ChainA, 3, 90*time.Second) + tr.waitBlocks(action.ChainB, 3, 90*time.Second) + // hermes clear packets ibc0 transfer channel-13 cmd := target.ExecCommand("hermes", "clear", "packets", "--chain", string(tr.chainConfigs[action.ChainA].ChainId), @@ -1763,7 +1775,7 @@ func (tr TestConfig) invokeDowntimeSlash(action DowntimeSlashAction, target Exec // Bring validator down tr.setValidatorDowntime(action.Chain, action.Validator, true, target, verbose) // Wait appropriate amount of blocks for validator to be slashed - tr.waitBlocks(action.Chain, 10, 3*time.Minute) + tr.waitBlocks(action.Chain, 11, 3*time.Minute) // Bring validator back up tr.setValidatorDowntime(action.Chain, action.Validator, false, target, verbose) } diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 243c43ca3d..e71f3d5b4c 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -375,7 +375,8 @@ func SlashThrottleTestConfig() TestConfig { ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + ".app_state.provider.params.slash_meter_replenish_fraction = \"0.10\" | " + - ".app_state.provider.params.slash_meter_replenish_period = \"20s\"", + ".app_state.provider.params.slash_meter_replenish_period = \"20s\" | " + + ".app_state.provider.params.blocks_per_epoch = 3", }, ChainID("consu"): { ChainId: ChainID("consu"), @@ -384,7 +385,7 @@ func SlashThrottleTestConfig() TestConfig { IpPrefix: "7.7.8", VotingWaitTime: 20, GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " + - ".app_state.slashing.params.signed_blocks_window = \"15\" | " + + ".app_state.slashing.params.signed_blocks_window = \"20\" | " + ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + @@ -522,7 +523,8 @@ func DefaultTestConfig() TestConfig { ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + ".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling - ".app_state.provider.params.slash_meter_replenish_period = \"3s\"", + ".app_state.provider.params.slash_meter_replenish_period = \"3s\" | " + + ".app_state.provider.params.blocks_per_epoch = 3", }, ChainID("consu"): { ChainId: ChainID("consu"), @@ -531,7 +533,7 @@ func DefaultTestConfig() TestConfig { IpPrefix: "7.7.8", VotingWaitTime: 20, GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " + - ".app_state.slashing.params.signed_blocks_window = \"15\" | " + + ".app_state.slashing.params.signed_blocks_window = \"20\" | " + ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"", @@ -551,7 +553,8 @@ func DemocracyTestConfig(allowReward bool) TestConfig { ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + - ".app_state.transfer.params.send_enabled = false" + ".app_state.transfer.params.send_enabled = false | " + + ".app_state.provider.params.blocks_per_epoch = 3" name := string(DemocracyTestCfg) if allowReward { @@ -583,7 +586,8 @@ func DemocracyTestConfig(allowReward bool) TestConfig { ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + - ".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\"", // This disables slash packet throttling + ".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling + ".app_state.provider.params.blocks_per_epoch = 3", }, ChainID("democ"): { ChainId: ChainID("democ"), @@ -625,7 +629,8 @@ func MultiConsumerTestConfig() TestConfig { ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + - ".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\"", // This disables slash packet throttling + ".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling + ".app_state.provider.params.blocks_per_epoch = 3", }, ChainID("consu"): { ChainId: ChainID("consu"), @@ -634,7 +639,7 @@ func MultiConsumerTestConfig() TestConfig { IpPrefix: "7.7.8", VotingWaitTime: 20, GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " + - ".app_state.slashing.params.signed_blocks_window = \"10\" | " + + ".app_state.slashing.params.signed_blocks_window = \"20\" | " + ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"", @@ -646,7 +651,7 @@ func MultiConsumerTestConfig() TestConfig { IpPrefix: "7.7.9", VotingWaitTime: 20, GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " + - ".app_state.slashing.params.signed_blocks_window = \"10\" | " + + ".app_state.slashing.params.signed_blocks_window = \"20\" | " + ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"", @@ -684,7 +689,8 @@ func ChangeoverTestConfig() TestConfig { ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + ".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling - ".app_state.provider.params.slash_meter_replenish_period = \"3s\"", + ".app_state.provider.params.slash_meter_replenish_period = \"3s\" | " + + ".app_state.provider.params.blocks_per_epoch = 3", }, ChainID("sover"): { ChainId: ChainID("sover"), @@ -694,7 +700,7 @@ func ChangeoverTestConfig() TestConfig { IpPrefix: "7.7.8", VotingWaitTime: 20, GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " + - ".app_state.slashing.params.signed_blocks_window = \"15\" | " + + ".app_state.slashing.params.signed_blocks_window = \"20\" | " + ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + @@ -784,7 +790,8 @@ func ConsumerMisbehaviourTestConfig() TestConfig { ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + ".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling - ".app_state.provider.params.slash_meter_replenish_period = \"3s\"", + ".app_state.provider.params.slash_meter_replenish_period = \"3s\" | " + + ".app_state.provider.params.blocks_per_epoch = 3", }, ChainID("consu"): { ChainId: ChainID("consu"), @@ -793,7 +800,7 @@ func ConsumerMisbehaviourTestConfig() TestConfig { IpPrefix: "7.7.8", VotingWaitTime: 20, GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " + - ".app_state.slashing.params.signed_blocks_window = \"15\" | " + + ".app_state.slashing.params.signed_blocks_window = \"20\" | " + ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"", diff --git a/tests/integration/common.go b/tests/integration/common.go index a4ff9e254a..18b657ae56 100644 --- a/tests/integration/common.go +++ b/tests/integration/common.go @@ -127,7 +127,7 @@ func delegateAndRedelegate(s *CCVTestSuite, delAddr sdk.AccAddress, srcValTokensAfter := s.getVal(s.providerCtx(), srcValAddr).GetBondedTokens() s.Require().Equal(srcValTokensAfter.Sub(srcValTokensBefore), amount) - s.providerChain.NextBlock() + s.nextEpoch() dstValTokensBefore := s.getVal(s.providerCtx(), dstValAddr).GetBondedTokens() @@ -625,3 +625,12 @@ func (s *CCVTestSuite) mustGetStakingValFromTmVal(tmVal tmtypes.Validator) (stak s.Require().True(found) return stakingVal } + +// nextEpoch moves `chain` forward by an epoch +func (s *CCVTestSuite) nextEpoch() { + blocksPerEpoch := s.providerApp.GetProviderKeeper().GetParams(s.providerCtx()).BlocksPerEpoch + + for i := int64(0); i < blocksPerEpoch; i++ { + s.providerChain.NextBlock() + } +} diff --git a/tests/integration/distribution.go b/tests/integration/distribution.go index 25cbcb3132..1ea5bfcd00 100644 --- a/tests/integration/distribution.go +++ b/tests/integration/distribution.go @@ -23,7 +23,7 @@ func (s *CCVTestSuite) TestRewardsDistribution() { bondAmt := sdk.NewInt(10000000) delAddr := s.providerChain.SenderAccount.GetAddress() delegate(s, delAddr, bondAmt) - s.providerChain.NextBlock() + s.nextEpoch() // register a consumer reward denom params := s.consumerApp.GetConsumerKeeper().GetConsumerParams(s.consumerCtx()) @@ -124,7 +124,7 @@ func (s *CCVTestSuite) TestSendRewardsRetries() { bondAmt := sdk.NewInt(10000000) delAddr := s.providerChain.SenderAccount.GetAddress() delegate(s, delAddr, bondAmt) - s.providerChain.NextBlock() + s.nextEpoch() // Register denom on consumer chain params := s.consumerApp.GetConsumerKeeper().GetConsumerParams(s.consumerCtx()) @@ -253,7 +253,7 @@ func (s *CCVTestSuite) TestEndBlockRD() { bondAmt := sdk.NewInt(10000000) delAddr := s.providerChain.SenderAccount.GetAddress() delegate(s, delAddr, bondAmt) - s.providerChain.NextBlock() + s.nextEpoch() if tc.denomRegistered { params := s.consumerApp.GetConsumerKeeper().GetConsumerParams(s.consumerCtx()) diff --git a/tests/integration/expired_client.go b/tests/integration/expired_client.go index 1981e85828..cfba19803e 100644 --- a/tests/integration/expired_client.go +++ b/tests/integration/expired_client.go @@ -33,7 +33,7 @@ func (s *CCVTestSuite) TestVSCPacketSendExpiredClient() { delegate(s, delAddr, bondAmt) // try to send CCV packet to consumer - s.providerChain.NextBlock() + s.nextEpoch() // check that the packet was added to the list of pending VSC packets packets := providerKeeper.GetPendingVSCPackets(s.providerCtx(), s.consumerChain.ChainID) @@ -41,7 +41,7 @@ func (s *CCVTestSuite) TestVSCPacketSendExpiredClient() { s.Require().Equal(1, len(packets), "unexpected number of pending VSC packets") // try again to send CCV packet to consumer - s.providerChain.NextBlock() + s.nextEpoch() // check that the packet is still in the list of pending VSC packets packets = providerKeeper.GetPendingVSCPackets(s.providerCtx(), s.consumerChain.ChainID) @@ -52,7 +52,7 @@ func (s *CCVTestSuite) TestVSCPacketSendExpiredClient() { delegate(s, delAddr, bondAmt) // try again to send CCV packets to consumer - s.providerChain.NextBlock() + s.nextEpoch() // check that the packets are still in the list of pending VSC packets packets = providerKeeper.GetPendingVSCPackets(s.providerCtx(), s.consumerChain.ChainID) @@ -62,8 +62,8 @@ func (s *CCVTestSuite) TestVSCPacketSendExpiredClient() { // upgrade expired client to the consumer upgradeExpiredClient(s, Consumer) - // go to next block - s.providerChain.NextBlock() + // go to next epoch + s.nextEpoch() // check that the packets are not in the list of pending VSC packets packets = providerKeeper.GetPendingVSCPackets(s.providerCtx(), s.consumerChain.ChainID) @@ -73,7 +73,7 @@ func (s *CCVTestSuite) TestVSCPacketSendExpiredClient() { // - bond more tokens on provider to change validator powers delegate(s, delAddr, bondAmt) // - send CCV packet to consumer - s.providerChain.NextBlock() + s.nextEpoch() // - relay all VSC packet from provider to consumer relayAllCommittedPackets(s, s.providerChain, s.path, ccv.ProviderPortID, s.path.EndpointB.ChannelID, 3) // - increment time so that the unbonding period ends on the consumer @@ -102,7 +102,7 @@ func (s *CCVTestSuite) TestConsumerPacketSendExpiredClient() { delegate(s, delAddr, bondAmt) // send CCV packets to consumer - s.providerChain.NextBlock() + s.nextEpoch() // check that the packets are not in the list of pending VSC packets providerPackets := providerKeeper.GetPendingVSCPackets(s.providerCtx(), s.consumerChain.ChainID) @@ -172,7 +172,7 @@ func (s *CCVTestSuite) TestConsumerPacketSendExpiredClient() { // - bond more tokens on provider to change validator powers delegate(s, delAddr, bondAmt) // - send CCV packet to consumer - s.providerChain.NextBlock() + s.nextEpoch() // - relay 1 VSC packet from provider to consumer relayAllCommittedPackets(s, s.providerChain, s.path, ccv.ProviderPortID, s.path.EndpointB.ChannelID, 1) // - increment time so that the unbonding period ends on the provider diff --git a/tests/integration/key_assignment.go b/tests/integration/key_assignment.go index 20e746ae63..ab6cb63146 100644 --- a/tests/integration/key_assignment.go +++ b/tests/integration/key_assignment.go @@ -30,7 +30,7 @@ func (s *CCVTestSuite) TestKeyAssignment() { } // check that a VSCPacket is queued - s.providerChain.NextBlock() + s.nextEpoch() pendingPackets := pk.GetPendingVSCPackets(s.providerCtx(), s.consumerChain.ChainID) s.Require().Len(pendingPackets, 1) @@ -51,7 +51,7 @@ func (s *CCVTestSuite) TestKeyAssignment() { if err != nil { return err } - s.providerChain.NextBlock() + s.nextEpoch() return nil }, false, 2, @@ -73,7 +73,7 @@ func (s *CCVTestSuite) TestKeyAssignment() { delAddr := s.providerChain.SenderAccount.GetAddress() delegate(s, delAddr, bondAmt) - s.providerChain.NextBlock() + s.nextEpoch() return nil }, false, 2, @@ -95,7 +95,7 @@ func (s *CCVTestSuite) TestKeyAssignment() { if err != nil { return err } - s.providerChain.NextBlock() + s.nextEpoch() return nil }, true, 2, @@ -118,7 +118,7 @@ func (s *CCVTestSuite) TestKeyAssignment() { if err != nil { return err } - s.providerChain.NextBlock() + s.nextEpoch() return nil }, false, 2, @@ -134,14 +134,14 @@ func (s *CCVTestSuite) TestKeyAssignment() { if err != nil { return err } - s.providerChain.NextBlock() + s.nextEpoch() // same key assignment err = pk.AssignConsumerKey(s.providerCtx(), s.consumerChain.ChainID, validator, consumerKey) if err != nil { return err } - s.providerChain.NextBlock() + s.nextEpoch() return nil }, true, 2, @@ -157,7 +157,7 @@ func (s *CCVTestSuite) TestKeyAssignment() { if err != nil { return err } - s.providerChain.NextBlock() + s.nextEpoch() // same key assignment validator, consumerKey = generateNewConsumerKey(s, 0) @@ -165,7 +165,7 @@ func (s *CCVTestSuite) TestKeyAssignment() { if err != nil { return err } - s.providerChain.NextBlock() + s.nextEpoch() return nil }, false, 3, diff --git a/tests/integration/setup.go b/tests/integration/setup.go index e401324c82..03c63d5502 100644 --- a/tests/integration/setup.go +++ b/tests/integration/setup.go @@ -129,6 +129,12 @@ func (suite *CCVTestSuite) SetupTest() { suite.registerPacketSniffer(suite.providerChain) providerKeeper := suite.providerApp.GetProviderKeeper() + // set `BlocksPerEpoch` to 10: a reasonable small value greater than 1 that prevents waiting for too + // many blocks and slowing down the integration tests + params := providerKeeper.GetParams(suite.providerCtx()) + params.BlocksPerEpoch = 10 + providerKeeper.SetParams(suite.providerCtx(), params) + // re-assign all validator keys for the first consumer chain providerKeeper.SetPendingConsumerAdditionProp(suite.providerCtx(), &types.ConsumerAdditionProposal{ ChainId: icstestingutils.FirstConsumerChainID, diff --git a/tests/integration/slashing.go b/tests/integration/slashing.go index 2339538292..e7f585f756 100644 --- a/tests/integration/slashing.go +++ b/tests/integration/slashing.go @@ -107,8 +107,10 @@ func (s *CCVTestSuite) TestRelayAndApplyDowntimePacket() { s.Require().True(found) } + s.nextEpoch() + // Confirm the valset update Id was incremented twice on provider, - // since two endblockers have passed. + // since an epoch has passed. s.Require().Equal(valsetUpdateIdN+2, providerKeeper.GetValidatorSetUpdateId(s.providerCtx())) diff --git a/tests/integration/soft_opt_out.go b/tests/integration/soft_opt_out.go index a5ee566a4b..a9508118bd 100644 --- a/tests/integration/soft_opt_out.go +++ b/tests/integration/soft_opt_out.go @@ -73,7 +73,7 @@ func (suite *CCVTestSuite) TestSoftOptOut() { bondAmt := sdk.NewInt(100).Mul(sdk.DefaultPowerReduction) delegateByIdx(suite, delAddr, bondAmt, valIdx) - suite.providerChain.NextBlock() + suite.nextEpoch() // Relay 1 VSC packet from provider to consumer relayAllCommittedPackets(suite, suite.providerChain, suite.path, ccv.ProviderPortID, suite.path.EndpointB.ChannelID, 1) @@ -112,7 +112,7 @@ func (suite *CCVTestSuite) TestSoftOptOut() { bondAmt := sdk.NewInt(100).Mul(sdk.DefaultPowerReduction) delegateByIdx(suite, delAddr, bondAmt, valIdx) - suite.providerChain.NextBlock() + suite.nextEpoch() // Relay 1 VSC packet from provider to consumer relayAllCommittedPackets(suite, suite.providerChain, suite.path, ccv.ProviderPortID, suite.path.EndpointB.ChannelID, 1) @@ -149,6 +149,8 @@ func (suite *CCVTestSuite) TestSoftOptOut() { validatorPowers := []int64{1000, 500, 50, 10} suite.setupValidatorPowers(validatorPowers) + suite.nextEpoch() + // Relay 1 VSC packet from provider to consumer relayAllCommittedPackets(suite, suite.providerChain, suite.path, ccv.ProviderPortID, suite.path.EndpointB.ChannelID, 1) diff --git a/tests/integration/unbonding.go b/tests/integration/unbonding.go index 00f48871c2..7f87516444 100644 --- a/tests/integration/unbonding.go +++ b/tests/integration/unbonding.go @@ -231,8 +231,7 @@ func (s *CCVTestSuite) TestUndelegationDuringInit() { // update init timeout timestamp tc.updateInitTimeoutTimestamp(&providerKeeper, providerUnbondingPeriod) - // call NextBlock on the provider (which increments the height) - s.providerChain.NextBlock() + s.nextEpoch() // check that the VSC packet is stored in state as pending pendingVSCs := providerKeeper.GetPendingVSCPackets(s.providerCtx(), s.consumerChain.ChainID) @@ -241,8 +240,7 @@ func (s *CCVTestSuite) TestUndelegationDuringInit() { // delegate again to create another VSC packet delegate(s, delAddr, bondAmt) - // call NextBlock on the provider (which increments the height) - s.providerChain.NextBlock() + s.nextEpoch() // check that the VSC packet is stored in state as pending pendingVSCs = providerKeeper.GetPendingVSCPackets(s.providerCtx(), s.consumerChain.ChainID) @@ -266,6 +264,7 @@ func (s *CCVTestSuite) TestUndelegationDuringInit() { // complete CCV channel setup s.SetupCCVChannel(s.path) + s.nextEpoch() // relay VSC packets from provider to consumer relayAllCommittedPackets(s, s.providerChain, s.path, ccv.ProviderPortID, s.path.EndpointB.ChannelID, 2) @@ -429,8 +428,8 @@ func (s *CCVTestSuite) TestRedelegationProviderFirst() { // Check that CCV unbonding op was created from AfterUnbondingInitiated hook checkCCVUnbondingOp(s, s.providerCtx(), s.consumerChain.ChainID, valsetUpdateID, true) - // Call NextBlock on the provider (which increments the height) - s.providerChain.NextBlock() + // move forward by an epoch to be able to relay VSC packets + s.nextEpoch() // Relay 2 VSC packets from provider to consumer (original delegation, and redelegation) relayAllCommittedPackets(s, s.providerChain, s.path, diff --git a/tests/integration/valset_update.go b/tests/integration/valset_update.go index dedcce2b86..eb0560a35e 100644 --- a/tests/integration/valset_update.go +++ b/tests/integration/valset_update.go @@ -23,8 +23,8 @@ func (s *CCVTestSuite) TestPacketRoundtrip() { delAddr := s.providerChain.SenderAccount.GetAddress() delegate(s, delAddr, bondAmt) - // Send CCV packet to consumer - s.providerChain.NextBlock() + // Send CCV packet to consumer at the end of the epoch + s.nextEpoch() // Relay 1 VSC packet from provider to consumer relayAllCommittedPackets(s, s.providerChain, s.path, ccv.ProviderPortID, s.path.EndpointB.ChannelID, 1) diff --git a/tests/mbt/driver/mbt_test.go b/tests/mbt/driver/mbt_test.go index a55d870dda..183839dc9a 100644 --- a/tests/mbt/driver/mbt_test.go +++ b/tests/mbt/driver/mbt_test.go @@ -184,6 +184,12 @@ func RunItfTrace(t *testing.T, path string) { driver.setupProvider(modelParams, valSet, signers, nodes, valNames) + // set `BlocksPerEpoch` to 10: a reasonable small value greater than 1 that prevents waiting for too + // many blocks and slowing down the tests + providerParams := driver.providerKeeper().GetParams(driver.providerCtx()) + providerParams.BlocksPerEpoch = 10 + driver.providerKeeper().SetParams(driver.providerCtx(), providerParams) + // remember the time offsets to be able to compare times to the model // this is necessary because the system needs to do many steps to initialize the chains, // which is abstracted away in the model @@ -233,17 +239,23 @@ func RunItfTrace(t *testing.T, path string) { stats.numStartedChains += len(consumersToStart) stats.numStops += len(consumersToStop) - // we need 2 blocks, because for a packet sent at height H, the receiving chain + // we need at least 2 blocks, because for a packet sent at height H, the receiving chain // needs a header of height H+1 to accept the packet - // so we do one time advancement with a very small increment, + // so, we do `blocksPerEpoch` time advancements with a very small increment, // and then increment the rest of the time runningConsumersBefore := driver.runningConsumers() - driver.endAndBeginBlock("provider", 1*time.Nanosecond) + + // going through `blocksPerEpoch` blocks to take into account an epoch + blocksPerEpoch := driver.providerKeeper().GetBlocksPerEpoch(driver.providerCtx()) + for i := int64(0); i < blocksPerEpoch; i = i + 1 { + 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) + driver.endAndBeginBlock("provider", time.Duration(timeAdvancement)*time.Second-time.Nanosecond*time.Duration(blocksPerEpoch)) + runningConsumersAfter := driver.runningConsumers() // the consumers that were running before but not after must have timed out diff --git a/tests/mbt/driver/setup.go b/tests/mbt/driver/setup.go index 83fa6e0669..69b385cb77 100644 --- a/tests/mbt/driver/setup.go +++ b/tests/mbt/driver/setup.go @@ -356,6 +356,26 @@ func (s *Driver) ConfigureNewPath(consumerChain, providerChain *ibctesting.TestC NewChain: consumerGenesis.NewChain, } + var stakingValidators []stakingtypes.Validator + + // set up the current consumer validators by utilizing the initial validator set + for _, val := range consumerGenesisForProvider.Provider.InitialValSet { + pubKey := val.PubKey + consAddr, err := ccvtypes.TMCryptoPublicKeyToConsAddr(pubKey) + if err != nil { + continue + } + + v, found := s.providerStakingKeeper().GetValidatorByConsAddr(s.providerCtx(), consAddr) + if !found { + continue + } + stakingValidators = append(stakingValidators, v) + } + + nextValidators := s.providerKeeper().ComputeNextEpochConsumerValSet(s.providerCtx(), string(consumerChainId), stakingValidators) + s.providerKeeper().SetConsumerValSet(s.providerCtx(), string(consumerChainId), nextValidators) + err = s.providerKeeper().SetConsumerGenesis( providerChain.GetContext(), string(consumerChainId), diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index dc901712ff..ff3df99763 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -252,7 +252,6 @@ func TestProviderStateIsCleanedAfterConsumerChainIsStopped(t *testing.T, ctx sdk // 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)) } diff --git a/x/ccv/consumer/types/keys.go b/x/ccv/consumer/types/keys.go index 0292ca84c5..20163f5ed9 100644 --- a/x/ccv/consumer/types/keys.go +++ b/x/ccv/consumer/types/keys.go @@ -51,7 +51,7 @@ const ( // received over CCV channel but not yet flushed over ABCI PendingChangesByteKey - // NOTE: This prefix is depreciated, but left in place to avoid consumer state migrations + // NOTE: This prefix is deprecated, but left in place to avoid consumer state migrations // [DEPRECATED] PendingDataPacketsByteKey @@ -61,7 +61,7 @@ const ( // InitialValSetByteKey is the byte to store the initial validator set for a consumer InitialValSetByteKey - // NOTE: This prefix is depreciated, but left in place to avoid consumer state migrations + // NOTE: This prefix is deprecated, but left in place to avoid consumer state migrations // [DEPRECATED] LastStandaloneHeightByteKey diff --git a/x/ccv/provider/keeper/grpc_query_test.go b/x/ccv/provider/keeper/grpc_query_test.go index dfe0a73895..e3273d1fb5 100644 --- a/x/ccv/provider/keeper/grpc_query_test.go +++ b/x/ccv/provider/keeper/grpc_query_test.go @@ -23,7 +23,6 @@ func TestQueryAllPairsValConAddrByConsumerChainID(t *testing.T) { defer ctrl.Finish() pk.SetValidatorConsumerPubKey(ctx, chainID, providerAddr, consumerKey) - pk.SetKeyAssignmentReplacement(ctx, chainID, providerAddr, consumerKey, 100) consumerPubKey, found := pk.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) require.True(t, found, "consumer pubkey not found") diff --git a/x/ccv/provider/keeper/key_assignment.go b/x/ccv/provider/keeper/key_assignment.go index c54d922f0f..89dbcfda4e 100644 --- a/x/ccv/provider/keeper/key_assignment.go +++ b/x/ccv/provider/keeper/key_assignment.go @@ -8,7 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - abci "github.com/cometbft/cometbft/abci/types" tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" @@ -184,99 +183,6 @@ func (k Keeper) DeleteValidatorByConsumerAddr(ctx sdk.Context, chainID string, c store.Delete(types.ValidatorsByConsumerAddrKey(chainID, consumerAddr)) } -// GetKeyAssignmentReplacement returns the previous assigned consumer key and the current power -// for a provider validator for which a key assignment was received in this block. Both are -// needed to update the validator's power on the consumer chain at the end of the current block. -func (k Keeper) GetKeyAssignmentReplacement( - ctx sdk.Context, - chainID string, - providerAddr types.ProviderConsAddress, -) (prevCKey tmprotocrypto.PublicKey, power int64, found bool) { - var pubKeyAndPower abci.ValidatorUpdate - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.KeyAssignmentReplacementsKey(chainID, providerAddr)) - if bz == nil { - return pubKeyAndPower.PubKey, pubKeyAndPower.Power, false - } - - err := pubKeyAndPower.Unmarshal(bz) - if err != nil { - // An error here would indicate something is very wrong, - // the public key and power are assumed to be correctly serialized in SetKeyAssignmentReplacement. - panic(fmt.Sprintf("failed to unmarshal public key and power: %v", err)) - } - return pubKeyAndPower.PubKey, pubKeyAndPower.Power, true -} - -// SetKeyAssignmentReplacement sets the previous assigned consumer key and the current power -// for a provider validator for which a key assignment was received in this block. Both are -// needed to update the validator's power on the consumer chain at the end of the current block. -func (k Keeper) SetKeyAssignmentReplacement( - ctx sdk.Context, - chainID string, - providerAddr types.ProviderConsAddress, - prevCKey tmprotocrypto.PublicKey, - power int64, -) { - store := ctx.KVStore(k.storeKey) - pubKeyAndPower := abci.ValidatorUpdate{PubKey: prevCKey, Power: power} - bz, err := pubKeyAndPower.Marshal() - if err != nil { - // An error here would indicate something is very wrong, - // prevCKey is obtained from GetValidatorConsumerPubKey (called from AssignConsumerKey), - // and power is obtained from GetLastValidatorPower (called from AssignConsumerKey). - // Both of which are assumed to return valid values. - panic(fmt.Sprintf("failed to marshal public key and power: %v", err)) - } - store.Set(types.KeyAssignmentReplacementsKey(chainID, providerAddr), bz) -} - -// GetAllKeyAssignmentReplacements gets all pairs of previous assigned consumer keys -// and current powers for all provider validator for which key assignments were received in this block. -// -// Note that the pairs are stored under keys with the following format: -// KeyAssignmentReplacementsBytePrefix | len(chainID) | chainID | providerAddress -// Thus, the iteration is in ascending order of providerAddresses. -func (k Keeper) GetAllKeyAssignmentReplacements(ctx sdk.Context, chainID string) (replacements []types.KeyAssignmentReplacement) { - store := ctx.KVStore(k.storeKey) - iteratorPrefix := types.ChainIdWithLenKey(types.KeyAssignmentReplacementsBytePrefix, chainID) - iterator := sdk.KVStorePrefixIterator(store, iteratorPrefix) - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - // TODO: store chainID and provider cons address in value bytes, marshaled as protobuf type - _, providerAddrTmp, err := types.ParseChainIdAndConsAddrKey(types.KeyAssignmentReplacementsBytePrefix, iterator.Key()) - if err != nil { - // An error here would indicate something is very wrong, - // store keys are assumed to be correctly serialized in SetKeyAssignmentReplacement. - panic(err) - } - providerAddr := types.NewProviderConsAddress(providerAddrTmp) - var pubKeyAndPower abci.ValidatorUpdate - err = pubKeyAndPower.Unmarshal(iterator.Value()) - if err != nil { - // An error here would indicate something is very wrong, - // the public key and power are assumed to be correctly serialized in SetKeyAssignmentReplacement. - panic(fmt.Sprintf("failed to unmarshal public key and power: %v", err)) - } - - replacements = append(replacements, types.KeyAssignmentReplacement{ - ProviderAddr: providerAddr.ToSdkConsAddr(), - PrevCKey: &pubKeyAndPower.PubKey, - Power: pubKeyAndPower.Power, - }) - } - - return replacements -} - -// DeleteKeyAssignmentReplacement deletes the previous assigned consumer key and the current power -// for a provider validator for which a key assignment was received in this block. Both are -// needed to update the validator's power on the consumer chain at the end of the current block. -func (k Keeper) DeleteKeyAssignmentReplacement(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.KeyAssignmentReplacementsKey(chainID, providerAddr)) -} - // AppendConsumerAddrsToPrune appends a consumer validator address to the list of consumer addresses // that can be pruned once the VSCMaturedPacket with vscID is received. // @@ -425,20 +331,20 @@ func (k Keeper) AssignConsumerKey( ) } - // check whether the consumer chain is already registered, - // i.e., a client to the consumer was already created - if _, consumerRegistered := k.GetConsumerClientId(ctx, chainID); consumerRegistered { - // get the previous key assigned for this validator on this consumer chain - oldConsumerKey, found := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) - if found { - // mark this old consumer key as prunable once the VSCMaturedPacket + // get the previous key assigned for this validator on this consumer chain + if oldConsumerKey, found := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr); found { + oldConsumerAddrTmp, err := ccvtypes.TMCryptoPublicKeyToConsAddr(oldConsumerKey) + if err != nil { + return err + } + oldConsumerAddr := types.NewConsumerConsAddress(oldConsumerAddrTmp) + + // check whether the consumer chain is already registered, + // i.e., a client to the consumer was already created + if _, consumerRegistered := k.GetConsumerClientId(ctx, chainID); consumerRegistered { + // mark the old consumer address as prunable once the VSCMaturedPacket // for the current VSC ID is received; // note: this state is removed on receiving the VSCMaturedPacket - oldConsumerAddrTmp, err := ccvtypes.TMCryptoPublicKeyToConsAddr(oldConsumerKey) - if err != nil { - return err - } - oldConsumerAddr := types.NewConsumerConsAddress(oldConsumerAddrTmp) k.AppendConsumerAddrsToPrune( ctx, chainID, @@ -446,42 +352,8 @@ func (k Keeper) AssignConsumerKey( oldConsumerAddr, ) } else { - // the validator had no key assigned on this consumer chain - providerKey, err := validator.TmConsPublicKey() - if err != nil { - return err - } - 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 - - // the key assignment replacement should not be overwritten - if _, _, found := k.GetKeyAssignmentReplacement(ctx, chainID, providerAddr); !found { - // store old key and current power for modifying the valset update in EndBlock; - // note: this state is deleted at the end of the block - k.SetKeyAssignmentReplacement( - ctx, - chainID, - providerAddr, - oldConsumerKey, - power, - ) - } - } - } else { - // if the consumer chain is not registered, then remove the mapping - // from the old consumer address to the provider address (if any) - // get the previous key assigned for this validator on this consumer chain - if oldConsumerKey, found := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr); found { - oldConsumerAddrTmp, err := ccvtypes.TMCryptoPublicKeyToConsAddr(oldConsumerKey) - if err != nil { - return err - } - oldConsumerAddr := types.NewConsumerConsAddress(oldConsumerAddrTmp) + // if the consumer chain is not registered, then remove the mapping + // from the old consumer address to the provider address k.DeleteValidatorByConsumerAddr(ctx, chainID, oldConsumerAddr) } } @@ -499,88 +371,6 @@ func (k Keeper) AssignConsumerKey( return nil } -// MustApplyKeyAssignmentToValUpdates applies the key assignment to the validator updates -// received from the staking module. -// The method panics if the key-assignment state is corrupted. -func (k Keeper) MustApplyKeyAssignmentToValUpdates( - ctx sdk.Context, - chainID string, - valUpdates []abci.ValidatorUpdate, -) (newUpdates []abci.ValidatorUpdate) { - for _, valUpdate := range valUpdates { - providerAddrTmp, err := ccvtypes.TMCryptoPublicKeyToConsAddr(valUpdate.PubKey) - if err != nil { - panic(fmt.Errorf("cannot get provider address from pub key: %s", err.Error())) - } - providerAddr := types.NewProviderConsAddress(providerAddrTmp) - - // If a key assignment replacement is found, we remove the valupdate with the old consumer key, - // create two new valupdates, - // - setting the old consumer key's power to 0 - // - and setting the new consumer key's power to the power in the update - prevConsumerKey, _, found := k.GetKeyAssignmentReplacement(ctx, chainID, providerAddr) - if found { - newUpdates = append(newUpdates, abci.ValidatorUpdate{ - PubKey: prevConsumerKey, - Power: 0, - }) - - newConsumerKey, found := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) - if !found { - // This should never happen as for every KeyAssignmentReplacement there should - // be a ValidatorConsumerPubKey that was stored when AssignConsumerKey() was called. - panic(fmt.Errorf("consumer key not found for provider addr %s stored in KeyAssignmentReplacement", providerAddr)) - } - newUpdates = append(newUpdates, abci.ValidatorUpdate{ - PubKey: newConsumerKey, - Power: valUpdate.Power, - }) - k.DeleteKeyAssignmentReplacement(ctx, chainID, providerAddr) - } else { - // If a key assignment replacement is not found, we check if the validator's key is assigned. - // If it is, we replace the update containing the provider key with an update containing - // the consumer key. - // Note that this will always be the branch taken when creating the genesis state - // of a newly registered consumer chain. - consumerKey, found := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) - if found { - newUpdates = append(newUpdates, abci.ValidatorUpdate{ - PubKey: consumerKey, - Power: valUpdate.Power, - }) - } else { - // keep the same update - newUpdates = append(newUpdates, valUpdate) - } - } - } - - // For any key assignment replacements that did not have a corresponding validator update already, - // set the old consumer key's power to 0 and the new consumer key's power to the - // power in the pending key assignment. - for _, replacement := range k.GetAllKeyAssignmentReplacements(ctx, chainID) { - providerAddr := types.NewProviderConsAddress(replacement.ProviderAddr) - k.DeleteKeyAssignmentReplacement(ctx, chainID, providerAddr) - newUpdates = append(newUpdates, abci.ValidatorUpdate{ - PubKey: *replacement.PrevCKey, - Power: 0, - }) - - newConsumerKey, found := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) - if !found { - // This should never happen as for every KeyAssignmentReplacement there should - // be a ValidatorConsumerPubKey that was stored when AssignConsumerKey() was called. - panic(fmt.Errorf("consumer key not found for provider addr %s stored in KeyAssignmentReplacement", replacement.ProviderAddr)) - } - newUpdates = append(newUpdates, abci.ValidatorUpdate{ - PubKey: newConsumerKey, - Power: replacement.Power, - }) - } - - return newUpdates -} - // GetProviderAddrFromConsumerAddr returns the consensus address of a validator with // consAddr set as the consensus address on a consumer chain func (k Keeper) GetProviderAddrFromConsumerAddr( @@ -627,12 +417,6 @@ func (k Keeper) DeleteKeyAssignments(ctx sdk.Context, chainID string) { k.DeleteValidatorByConsumerAddr(ctx, chainID, consumerAddr) } - // delete KeyAssignmentReplacements - for _, keyAssignmentReplacement := range k.GetAllKeyAssignmentReplacements(ctx, chainID) { - providerAddr := types.NewProviderConsAddress(keyAssignmentReplacement.ProviderAddr) - k.DeleteKeyAssignmentReplacement(ctx, chainID, providerAddr) - } - // delete ValidatorConsumerPubKey for _, consumerAddrsToPrune := range k.GetAllConsumerAddrsToPrune(ctx, chainID) { k.DeleteConsumerAddrsToPrune(ctx, chainID, consumerAddrsToPrune.VscId) diff --git a/x/ccv/provider/keeper/key_assignment_test.go b/x/ccv/provider/keeper/key_assignment_test.go index 4fab08c981..7cb222a3be 100644 --- a/x/ccv/provider/keeper/key_assignment_test.go +++ b/x/ccv/provider/keeper/key_assignment_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "bytes" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" "math/rand" "sort" "testing" @@ -184,67 +185,6 @@ func TestGetAllValidatorsByConsumerAddr(t *testing.T) { require.Len(t, result, len(testAssignments)) } -func TestKeyAssignmentReplacementCRUD(t *testing.T) { - chainID := consumer - providerAddr := types.NewProviderConsAddress([]byte("providerAddr")) - expCPubKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() - var expPower int64 = 100 - - keeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - keeper.SetKeyAssignmentReplacement(ctx, chainID, providerAddr, expCPubKey, expPower) - - cPubKey, power, found := keeper.GetKeyAssignmentReplacement(ctx, chainID, providerAddr) - require.True(t, found, "key assignment replacement not found") - require.Equal(t, expCPubKey, cPubKey, "previous consumer key not matching") - require.Equal(t, expPower, power, "power not matching") - - keeper.DeleteKeyAssignmentReplacement(ctx, chainID, providerAddr) - _, _, found = keeper.GetKeyAssignmentReplacement(ctx, chainID, providerAddr) - require.False(t, found, "key assignment replacement found") -} - -func TestGetAllKeyAssignmentReplacements(t *testing.T) { - pk, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - chainID := "consumer-1" - - seed := time.Now().UnixNano() - rng := rand.New(rand.NewSource(seed)) - - numAssignments := 10 - testAssignments := []types.KeyAssignmentReplacement{} - for i := 0; i < numAssignments; i++ { - consumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(i).TMProtoCryptoPublicKey() - providerAddr := cryptotestutil.NewCryptoIdentityFromIntSeed(numAssignments + i).ProviderConsAddress() - testAssignments = append(testAssignments, - types.KeyAssignmentReplacement{ - ProviderAddr: providerAddr.ToSdkConsAddr(), - PrevCKey: &consumerKey, - Power: rng.Int63(), - }, - ) - } - expectedGetAllOrder := testAssignments - // sorting by KeyAssignmentReplacement.ProviderAddr - sort.Slice(expectedGetAllOrder, func(i, j int) bool { - return bytes.Compare(expectedGetAllOrder[i].ProviderAddr, expectedGetAllOrder[j].ProviderAddr) == -1 - }) - - firstTestAssignmentProviderAddr := types.NewProviderConsAddress(testAssignments[0].ProviderAddr) - pk.SetKeyAssignmentReplacement(ctx, "consumer-2", firstTestAssignmentProviderAddr, *testAssignments[0].PrevCKey, testAssignments[0].Power) - for _, assignment := range testAssignments { - providerAddr := types.NewProviderConsAddress(assignment.ProviderAddr) - pk.SetKeyAssignmentReplacement(ctx, chainID, providerAddr, *assignment.PrevCKey, assignment.Power) - } - - result := pk.GetAllKeyAssignmentReplacements(ctx, chainID) - require.Len(t, result, len(testAssignments)) - require.Equal(t, expectedGetAllOrder, result) -} - func TestConsumerAddrsToPruneCRUD(t *testing.T) { chainID := consumer consumerAddr := types.NewConsumerConsAddress([]byte("consumerAddr1")) @@ -420,9 +360,6 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, consumerIdentities[0].SDKValConsAddress(), ).Return(stakingtypes.Validator{}, false), - mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower( - ctx, providerIdentities[0].SDKValOpAddress(), - ).Return(int64(0)), ) }, doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { @@ -445,15 +382,9 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, consumerIdentities[0].SDKValConsAddress(), ).Return(stakingtypes.Validator{}, false), - mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower( - ctx, providerIdentities[0].SDKValOpAddress(), - ).Return(int64(0)), mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, consumerIdentities[1].SDKValConsAddress(), ).Return(stakingtypes.Validator{}, false), - mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower( - ctx, providerIdentities[0].SDKValOpAddress(), - ).Return(int64(0)), ) }, doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { @@ -481,9 +412,6 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, consumerIdentities[0].SDKValConsAddress(), ).Return(stakingtypes.Validator{}, false), - mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower( - ctx, providerIdentities[0].SDKValOpAddress(), - ).Return(int64(0)), mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, consumerIdentities[0].SDKValConsAddress(), ).Return(stakingtypes.Validator{}, false), @@ -692,7 +620,6 @@ func (vs *ValSet) apply(updates []abci.ValidatorUpdate) { // note: an insertion index should always be found for _, u := range updates { for i, id := range vs.identities { // n2 looping but n is tiny - // cons := sdk.ConsAddress(utils.GetChangePubKeyAddress(u)) cons, _ := ccvtypes.TMCryptoPublicKeyToConsAddr(u.PubKey) if id.SDKValConsAddress().Equals(cons) { vs.power[i] = u.Power @@ -828,7 +755,21 @@ func TestSimulatedAssignmentsAndUpdateApplication(t *testing.T) { // and increment the provider vscid. applyUpdatesAndIncrementVSCID := func(updates []abci.ValidatorUpdate) { providerValset.apply(updates) - updates = k.MustApplyKeyAssignmentToValUpdates(ctx, CHAINID, updates) + + var bondedValidators []stakingtypes.Validator + for _, v := range providerValset.identities { + pkAny, _ := codectypes.NewAnyWithValue(v.ConsensusSDKPubKey()) + + bondedValidators = append(bondedValidators, stakingtypes.Validator{ + OperatorAddress: v.SDKValOpAddress().String(), + ConsensusPubkey: pkAny, + }) + } + + nextValidators := k.ComputeNextEpochConsumerValSet(ctx, CHAINID, bondedValidators) + updates = providerkeeper.DiffValidators(k.GetConsumerValSet(ctx, CHAINID), nextValidators) + k.SetConsumerValSet(ctx, CHAINID, nextValidators) + consumerValset.apply(updates) // Simulate the VSCID update in EndBlock k.IncrementValidatorSetUpdateId(ctx) @@ -844,10 +785,13 @@ func TestSimulatedAssignmentsAndUpdateApplication(t *testing.T) { // The consumer chain has not yet been registered // Apply some randomly generated key assignments - applyAssignments(getAssignments()) + assignments := getAssignments() + applyAssignments(assignments) // And generate a random provider valset which, in the real system, will // be put into the consumer genesis. - applyUpdatesAndIncrementVSCID(getStakingUpdates()) + stakingUpdates := getStakingUpdates() + + applyUpdatesAndIncrementVSCID(stakingUpdates) // Register the consumer chain k.SetConsumerClientId(ctx, CHAINID, "") @@ -861,9 +805,12 @@ func TestSimulatedAssignmentsAndUpdateApplication(t *testing.T) { // and a random set of validator power updates for block := 0; block < NUM_BLOCKS_PER_EXECUTION; block++ { + stakingUpdates = getStakingUpdates() + assignments = getAssignments() + // Generate and apply assignments and power updates - applyAssignments(getAssignments()) - applyUpdatesAndIncrementVSCID(getStakingUpdates()) + applyAssignments(assignments) + applyUpdatesAndIncrementVSCID(stakingUpdates) // Randomly fast forward the greatest pruned VSCID. This simulates // delivery of maturity packets from the consumer chain. diff --git a/x/ccv/provider/keeper/params.go b/x/ccv/provider/keeper/params.go index 209d0f0ddb..f74baf656e 100644 --- a/x/ccv/provider/keeper/params.go +++ b/x/ccv/provider/keeper/params.go @@ -78,6 +78,13 @@ func (k Keeper) GetConsumerRewardDenomRegistrationFee(ctx sdk.Context) sdk.Coin return c } +// GetBlocksPerEpoch returns the number of blocks that constitute an epoch +func (k Keeper) GetBlocksPerEpoch(ctx sdk.Context) int64 { + var b int64 + k.paramSpace.Get(ctx, types.KeyBlocksPerEpoch, &b) + return b +} + // GetParams returns the paramset for the provider module func (k Keeper) GetParams(ctx sdk.Context) types.Params { return types.NewParams( @@ -89,6 +96,7 @@ func (k Keeper) GetParams(ctx sdk.Context) types.Params { k.GetSlashMeterReplenishPeriod(ctx), k.GetSlashMeterReplenishFraction(ctx), k.GetConsumerRewardDenomRegistrationFee(ctx), + k.GetBlocksPerEpoch(ctx), ) } diff --git a/x/ccv/provider/keeper/params_test.go b/x/ccv/provider/keeper/params_test.go index a941523e87..88175431c0 100644 --- a/x/ccv/provider/keeper/params_test.go +++ b/x/ccv/provider/keeper/params_test.go @@ -48,6 +48,7 @@ func TestParams(t *testing.T) { Denom: "stake", Amount: sdk.NewInt(10000000), }, + 600, ) providerKeeper.SetParams(ctx, newParams) params = providerKeeper.GetParams(ctx) diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 89d71ff344..b0e2845502 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -253,6 +253,8 @@ func (k Keeper) MakeConsumerGenesis( return false }) + var bondedValidators []stakingtypes.Validator + initialUpdates := []abci.ValidatorUpdate{} for _, p := range lastPowers { addr, err := sdk.ValAddressFromBech32(p.Address) @@ -274,10 +276,16 @@ func (k Keeper) MakeConsumerGenesis( PubKey: tmProtoPk, Power: p.Power, }) + + // gather all the bonded validators in order to construct the consumer validator set for consumer chain `chainID` + bondedValidators = append(bondedValidators, val) } - // Apply key assignments to the initial valset. - initialUpdatesWithConsumerKeys := k.MustApplyKeyAssignmentToValUpdates(ctx, chainID, initialUpdates) + nextValidators := k.ComputeNextEpochConsumerValSet(ctx, chainID, bondedValidators) + k.SetConsumerValSet(ctx, chainID, nextValidators) + + // get the initial updates with the latest set consumer public keys + initialUpdatesWithConsumerKeys := DiffValidators([]types.ConsumerValidator{}, nextValidators) // Get a hash of the consumer validator set from the update with applied consumer assigned keys updatesAsValSet, err := tmtypes.PB2TM.ValidatorUpdates(initialUpdatesWithConsumerKeys) diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index e78823899a..fc1c7a4344 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -775,6 +775,7 @@ func TestMakeConsumerGenesis(t *testing.T) { Denom: "stake", Amount: sdk.NewInt(1000000), }, + BlocksPerEpoch: 600, } providerKeeper.SetParams(ctx, moduleParams) defer ctrl.Finish() diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 59fec69534..939f6d3995 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -148,13 +148,17 @@ func (k Keeper) EndBlockVSU(ctx sdk.Context) { // notify the staking module to complete all matured unbonding ops k.completeMaturedUnbondingOps(ctx) - // collect validator updates - k.QueueVSCPackets(ctx) + if ctx.BlockHeight()%k.GetBlocksPerEpoch(ctx) == 0 { + // only queue and send VSCPackets at the boundaries of an epoch - // try sending VSC packets to all registered consumer chains; - // if the CCV channel is not established for a consumer chain, - // the updates will remain queued until the channel is established - k.SendVSCPackets(ctx) + // collect validator updates + k.QueueVSCPackets(ctx) + + // try sending VSC packets to all registered consumer chains; + // if the CCV channel is not established for a consumer chain, + // the updates will remain queued until the channel is established + k.SendVSCPackets(ctx) + } } // SendVSCPackets iterates over all registered consumers and sends pending @@ -212,14 +216,15 @@ func (k Keeper) SendVSCPacketsToChain(ctx sdk.Context, chainID, channelID string // QueueVSCPackets queues latest validator updates for every registered consumer chain func (k Keeper) QueueVSCPackets(ctx sdk.Context) { valUpdateID := k.GetValidatorSetUpdateId(ctx) // current valset update ID - // Get the validator updates from the staking module. - // Note: GetValidatorUpdates panics if the updates provided by the x/staking module - // of cosmos-sdk is invalid. - stakingValUpdates := k.stakingKeeper.GetValidatorUpdates(ctx) + + // get the bonded validators from the staking module + bondedValidators := k.stakingKeeper.GetLastValidators(ctx) for _, chain := range k.GetAllConsumerChains(ctx) { - // Apply the key assignment to the validator updates. - valUpdates := k.MustApplyKeyAssignmentToValUpdates(ctx, chain.ChainId, stakingValUpdates) + currentValidators := k.GetConsumerValSet(ctx, chain.ChainId) + nextValidators := k.ComputeNextEpochConsumerValSet(ctx, chain.ChainId, bondedValidators) + valUpdates := DiffValidators(currentValidators, nextValidators) + k.SetConsumerValSet(ctx, chain.ChainId, nextValidators) // check whether there are changes in the validator set; // note that this also entails unbonding operations diff --git a/x/ccv/provider/keeper/relay_test.go b/x/ccv/provider/keeper/relay_test.go index 02df262d53..b7e89b3dc2 100644 --- a/x/ccv/provider/keeper/relay_test.go +++ b/x/ccv/provider/keeper/relay_test.go @@ -1,23 +1,24 @@ package keeper_test import ( + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + ibctesting "github.com/cosmos/ibc-go/v7/testing" "strings" "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" - ibctesting "github.com/cosmos/ibc-go/v7/testing" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" "cosmossdk.io/math" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/interchain-security/v4/testutil/crypto" 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/keeper" @@ -66,16 +67,7 @@ func TestQueueVSCPackets(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mocks := testkeeper.NewMockedKeepers(ctrl) - mockStakingKeeper := mocks.MockStakingKeeper - - mockUpdates := []abci.ValidatorUpdate{} - if len(tc.packets) != 0 { - mockUpdates = tc.packets[0].ValidatorUpdates - } - - gomock.InOrder( - mockStakingKeeper.EXPECT().GetValidatorUpdates(gomock.Eq(ctx)).Return(mockUpdates), - ) + mocks.MockStakingKeeper.EXPECT().GetLastValidators(ctx).Times(1) pk := testkeeper.NewInMemProviderKeeper(keeperParams, mocks) // no-op if tc.packets is empty @@ -661,3 +653,47 @@ func TestOnAcknowledgementPacketWithAckError(t *testing.T) { testkeeper.TestProviderStateIsCleanedAfterConsumerChainIsStopped(t, ctx, providerKeeper, "chainID", "channelID") require.NoError(t, err) } + +// TestEndBlockVSU tests that during `EndBlockVSU`, we only queue VSC packets at the boundaries of an epoch +func TestEndBlockVSU(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // 10 blocks constitute an epoch + params := providertypes.DefaultParams() + params.BlocksPerEpoch = 10 + providerKeeper.SetParams(ctx, params) + + // create 4 sample lastValidators + var lastValidators []stakingtypes.Validator + for i := 0; i < 4; i++ { + lastValidators = append(lastValidators, crypto.NewCryptoIdentityFromIntSeed(i).SDKStakingValidator()) + } + mocks.MockStakingKeeper.EXPECT().GetLastValidators(gomock.Any()).Return(lastValidators).AnyTimes() + mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), gomock.Any()).Return(int64(2)).AnyTimes() + + // set a sample client for a consumer chain so that `GetAllConsumerChains` in `QueueVSCPackets` iterates at least once + providerKeeper.SetConsumerClientId(ctx, "chainID", "clientID") + + // with block height of 1 we do not expect any queueing of VSC packets + ctx = ctx.WithBlockHeight(1) + providerKeeper.EndBlockVSU(ctx) + require.Equal(t, 0, len(providerKeeper.GetPendingVSCPackets(ctx, "chainID"))) + + // with block height of 5 we do not expect any queueing of VSC packets + ctx = ctx.WithBlockHeight(5) + providerKeeper.EndBlockVSU(ctx) + require.Equal(t, 0, len(providerKeeper.GetPendingVSCPackets(ctx, "chainID"))) + + // with block height of 10 we expect the queueing of one VSC packet + ctx = ctx.WithBlockHeight(10) + providerKeeper.EndBlockVSU(ctx) + require.Equal(t, 1, len(providerKeeper.GetPendingVSCPackets(ctx, "chainID"))) + + // With block height of 15 we expect no additional queueing of a VSC packet. + // Note that the pending VSC packet is still there because `SendVSCPackets` does not send the packet. We + // need to mock channels, etc. for this to work, and it's out of scope for this test. + ctx = ctx.WithBlockHeight(15) + providerKeeper.EndBlockVSU(ctx) + require.Equal(t, 1, len(providerKeeper.GetPendingVSCPackets(ctx, "chainID"))) +} diff --git a/x/ccv/provider/keeper/validator_set_update.go b/x/ccv/provider/keeper/validator_set_update.go new file mode 100644 index 0000000000..71238d210d --- /dev/null +++ b/x/ccv/provider/keeper/validator_set_update.go @@ -0,0 +1,178 @@ +package keeper + +import ( + "fmt" + abci "github.com/cometbft/cometbft/abci/types" + 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" +) + +// SetConsumerValidator sets provided consumer `validator` on the consumer chain with `chainID` +func (k Keeper) SetConsumerValidator( + ctx sdk.Context, + chainID string, + validator types.ConsumerValidator, +) { + store := ctx.KVStore(k.storeKey) + bz, err := validator.Marshal() + if err != nil { + panic(fmt.Errorf("failed to marshal ConsumerValidator: %w", err)) + } + + store.Set(types.ConsumerValidatorKey(chainID, validator.ProviderConsAddr), bz) +} + +// DeleteConsumerValidator removes consumer validator with `providerAddr` address +func (k Keeper) DeleteConsumerValidator( + ctx sdk.Context, + chainID string, + providerConsAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.ConsumerValidatorKey(chainID, providerConsAddr.ToSdkConsAddr())) +} + +// DeleteConsumerValSet deletes all the stored consumer validators for chain `chainID` +func (k Keeper) DeleteConsumerValSet( + ctx sdk.Context, + chainID string) { + store := ctx.KVStore(k.storeKey) + key := types.ChainIdWithLenKey(types.ConsumerValidatorBytePrefix, chainID) + iterator := sdk.KVStorePrefixIterator(store, key) + + var keysToDel [][]byte + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + keysToDel = append(keysToDel, iterator.Key()) + } + for _, delKey := range keysToDel { + store.Delete(delKey) + } +} + +// IsConsumerValidator returns `true` if the consumer validator with `providerAddr` exists for chain `chainID` +// and `false` otherwise +func (k Keeper) IsConsumerValidator( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) bool { + store := ctx.KVStore(k.storeKey) + return store.Get(types.ConsumerValidatorKey(chainID, providerAddr.ToSdkConsAddr())) != nil +} + +// GetConsumerValSet returns all the consumer validators for chain `chainID` +func (k Keeper) GetConsumerValSet( + ctx sdk.Context, + chainID string) (validators []types.ConsumerValidator) { + store := ctx.KVStore(k.storeKey) + key := types.ChainIdWithLenKey(types.ConsumerValidatorBytePrefix, chainID) + iterator := sdk.KVStorePrefixIterator(store, key) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + iterator.Value() + var validator types.ConsumerValidator + if err := validator.Unmarshal(iterator.Value()); err != nil { + panic(fmt.Errorf("failed to unmarshal ConsumerValidator: %w", err)) + } + validators = append(validators, validator) + } + + return validators +} + +// ComputeNextEpochConsumerValSet returns the next validator set that is responsible for validating consumer +// chain `chainID`, based on the bonded validators. +func (k Keeper) ComputeNextEpochConsumerValSet( + ctx sdk.Context, + chainID string, + bondedValidators []stakingtypes.Validator, +) []types.ConsumerValidator { + var nextValidators []types.ConsumerValidator + for _, val := range bondedValidators { + // get next voting power and the next consumer public key + nextPower := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()) + consAddr, err := val.GetConsAddr() + if err != nil { + // this should never happen but is recoverable if we exclude this validator from the `nextValidators` + k.Logger(ctx).Error("could not get consensus address of validator", + "validator", val.GetOperator().String(), + "error", err) + continue + } + nextConsumerPublicKey, foundConsumerPublicKey := k.GetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(consAddr)) + if !foundConsumerPublicKey { + // if no consumer key assigned then use the validator's key itself + k.Logger(ctx).Info("could not retrieve public key for validator on consumer chain because"+ + " the validator did not assign a new consumer key", + "validator", val.GetOperator().String(), + "chainID", chainID) + nextConsumerPublicKey, err = val.TmConsPublicKey() + if err != nil { + // this should never happen and might not be recoverable because without the public key + // we cannot generate a validator update + panic(fmt.Errorf("could not retrieve validator's (%+v) public key: %w", val, err)) + } + } + + nextValidator := types.ConsumerValidator{ + ProviderConsAddr: consAddr, + Power: nextPower, + ConsumerPublicKey: &nextConsumerPublicKey, + } + nextValidators = append(nextValidators, nextValidator) + } + + return nextValidators +} + +// DiffValidators compares the current and the next epoch's consumer validators and returns the `ValidatorUpdate` diff +// needed by CometBFT to update the validator set on a chain. +func DiffValidators( + currentValidators []types.ConsumerValidator, + nextValidators []types.ConsumerValidator) []abci.ValidatorUpdate { + var updates []abci.ValidatorUpdate + + isCurrentValidator := make(map[string]types.ConsumerValidator) + for _, val := range currentValidators { + isCurrentValidator[val.ConsumerPublicKey.String()] = val + } + + isNextValidator := make(map[string]types.ConsumerValidator) + for _, val := range nextValidators { + isNextValidator[val.ConsumerPublicKey.String()] = val + } + + for _, currentVal := range currentValidators { + if nextVal, found := isNextValidator[currentVal.ConsumerPublicKey.String()]; !found { + // this consumer public key does not appear in the next validators and hence we remove the validator + // with that consumer public key by creating an update with 0 power + updates = append(updates, abci.ValidatorUpdate{PubKey: *currentVal.ConsumerPublicKey, Power: 0}) + } else if currentVal.Power != nextVal.Power { + // validator did not modify its consumer public key but has changed its voting power, so we + // have to create an update with the new power + updates = append(updates, abci.ValidatorUpdate{PubKey: *nextVal.ConsumerPublicKey, Power: nextVal.Power}) + } + // else no update is needed because neither the consumer public key changed, nor the power of the validator + } + + for _, nextVal := range nextValidators { + if _, found := isCurrentValidator[nextVal.ConsumerPublicKey.String()]; !found { + // this consumer public key does not exist in the current validators and hence we introduce this validator + updates = append(updates, abci.ValidatorUpdate{PubKey: *nextVal.ConsumerPublicKey, Power: nextVal.Power}) + } + } + + return updates +} + +// SetConsumerValSet resets the current consumer validators with the `nextValidators` computed by +// `ComputeNextEpochConsumerValSet` and hence this method should only be called after `ComputeNextEpochConsumerValSet` has completed. +func (k Keeper) SetConsumerValSet(ctx sdk.Context, chainID string, nextValidators []types.ConsumerValidator) { + k.DeleteConsumerValSet(ctx, chainID) + for _, val := range nextValidators { + k.SetConsumerValidator(ctx, chainID, val) + } +} diff --git a/x/ccv/provider/keeper/validator_set_update_test.go b/x/ccv/provider/keeper/validator_set_update_test.go new file mode 100644 index 0000000000..8505158816 --- /dev/null +++ b/x/ccv/provider/keeper/validator_set_update_test.go @@ -0,0 +1,355 @@ +package keeper_test + +import ( + "bytes" + "sort" + "testing" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/crypto/ed25519" + "github.com/cometbft/cometbft/proto/tendermint/crypto" + codectypes "github.com/cosmos/cosmos-sdk/codec/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" + 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/keeper" + "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" + "github.com/stretchr/testify/require" +) + +// TestConsumerValidator tests the `SetConsumerValidator`, `IsConsumerValidator`, and `DeleteConsumerValidator` methods +func TestConsumerValidator(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + validator := types.ConsumerValidator{ + ProviderConsAddr: []byte("providerConsAddr"), + Power: 2, + ConsumerPublicKey: &crypto.PublicKey{}, + } + + require.False(t, providerKeeper.IsConsumerValidator(ctx, "chainID", types.NewProviderConsAddress(validator.ProviderConsAddr))) + providerKeeper.SetConsumerValidator(ctx, "chainID", validator) + require.True(t, providerKeeper.IsConsumerValidator(ctx, "chainID", types.NewProviderConsAddress(validator.ProviderConsAddr))) + providerKeeper.DeleteConsumerValidator(ctx, "chainID", types.NewProviderConsAddress(validator.ProviderConsAddr)) + require.False(t, providerKeeper.IsConsumerValidator(ctx, "chainID", types.NewProviderConsAddress(validator.ProviderConsAddr))) +} + +func TestGetConsumerValSet(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // create 3 validators and set them as current validators + expectedValidators := []types.ConsumerValidator{ + { + ProviderConsAddr: []byte("providerConsAddr1"), + Power: 1, + ConsumerPublicKey: &crypto.PublicKey{ + Sum: &crypto.PublicKey_Ed25519{ + Ed25519: []byte{1}, + }, + }, + }, + { + ProviderConsAddr: []byte("providerConsAddr2"), + Power: 2, + ConsumerPublicKey: &crypto.PublicKey{ + Sum: &crypto.PublicKey_Ed25519{ + Ed25519: []byte{2}, + }, + }, + }, + { + ProviderConsAddr: []byte("providerConsAddr3"), + Power: 3, + ConsumerPublicKey: &crypto.PublicKey{ + Sum: &crypto.PublicKey_Ed25519{ + Ed25519: []byte{3}, + }, + }, + }, + } + + for _, expectedValidator := range expectedValidators { + providerKeeper.SetConsumerValidator(ctx, "chainID", + types.ConsumerValidator{ + ProviderConsAddr: expectedValidator.ProviderConsAddr, + Power: expectedValidator.Power, + ConsumerPublicKey: expectedValidator.ConsumerPublicKey, + }) + } + + actualValidators := providerKeeper.GetConsumerValSet(ctx, "chainID") + + // sort validators first to be able to compare + sortValidators := func(validators []types.ConsumerValidator) { + sort.Slice(validators, func(i int, j int) bool { + return bytes.Compare(validators[i].ProviderConsAddr, validators[j].ProviderConsAddr) < 0 + }) + } + sortValidators(expectedValidators) + sortValidators(actualValidators) + require.Equal(t, expectedValidators, actualValidators) +} + +// createConsumerValidator is a helper function to create a consumer validator with the given `power`. It uses `index` as +// the `ProviderConsAddr` of the validator, and the `seed` to generate the consumer public key. Returns the validator +// and its consumer public key. +func createConsumerValidator(index int, power int64, seed int) (types.ConsumerValidator, crypto.PublicKey) { + publicKey := cryptotestutil.NewCryptoIdentityFromIntSeed(seed).TMProtoCryptoPublicKey() + + return types.ConsumerValidator{ + ProviderConsAddr: []byte{byte(index)}, + Power: power, + ConsumerPublicKey: &publicKey, + }, publicKey +} + +func TestComputeNextEpochConsumerValSet(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + chainID := "chainID" + + // helper function to generate a validator with the given power and with a provider address based on index + createStakingValidator := func(ctx sdk.Context, mocks testkeeper.MockedKeepers, index int, power int64) stakingtypes.Validator { + providerConsPubKey := ed25519.GenPrivKeyFromSecret([]byte{byte(index)}).PubKey() + consAddr := sdk.ConsAddress(providerConsPubKey.Address()) + providerAddr := types.NewProviderConsAddress(consAddr) + pk, _ := cryptocodec.FromTmPubKeyInterface(providerConsPubKey) + pkAny, _ := codectypes.NewAnyWithValue(pk) + + var providerValidatorAddr sdk.ValAddress + providerValidatorAddr = providerAddr.Address.Bytes() + + mocks.MockStakingKeeper.EXPECT(). + GetLastValidatorPower(ctx, providerValidatorAddr).Return(power).AnyTimes() + + return stakingtypes.Validator{ + OperatorAddress: providerValidatorAddr.String(), + ConsensusPubkey: pkAny, + } + } + + // no consumer validators returned if we have no bonded validators + require.Empty(t, providerKeeper.ComputeNextEpochConsumerValSet(ctx, chainID, []stakingtypes.Validator{})) + + var expectedValidators []types.ConsumerValidator + + // create a staking validator A that has not set a consumer public key + valA := createStakingValidator(ctx, mocks, 1, 1) + // because validator A has no consumer key set, the `ConsumerPublicKey` we expect is the key on the provider chain + valAConsAddr, _ := valA.GetConsAddr() + valAPublicKey, _ := valA.TmConsPublicKey() + expectedValidators = append(expectedValidators, types.ConsumerValidator{ + ProviderConsAddr: types.NewProviderConsAddress(valAConsAddr).Address.Bytes(), + Power: 1, + ConsumerPublicKey: &valAPublicKey, + }) + + // create a staking validator B that has set a consumer public key + valB := createStakingValidator(ctx, mocks, 2, 2) + // validator B has set a consumer key, the `ConsumerPublicKey` we expect is the key set by `SetValidatorConsumerPubKey` + valBConsumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() + valBConsAddr, _ := valB.GetConsAddr() + providerKeeper.SetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(valBConsAddr), valBConsumerKey) + expectedValidators = append(expectedValidators, types.ConsumerValidator{ + ProviderConsAddr: types.NewProviderConsAddress(valBConsAddr).Address.Bytes(), + Power: 2, + ConsumerPublicKey: &valBConsumerKey, + }) + + bondedValidators := []stakingtypes.Validator{valA, valB} + actualValidators := providerKeeper.ComputeNextEpochConsumerValSet(ctx, "chainID", bondedValidators) + require.Equal(t, expectedValidators, actualValidators) +} + +func TestDiff(t *testing.T) { + // In what follows we create 6 validators: A, B, C, D, E, and F where currentValidators = {A, B, C, D, E} + // and nextValidators = {B, C, D, E, F}. For the validators {B, C, D, E} in the intersection we have: + // - validator B has no power or consumer key change + // - validator C has changed its power + // - validator D has no power change but has changed its consumer key + // - validator E has both changed its power and its consumer key + + var expectedUpdates []abci.ValidatorUpdate + + // validator A only exists in `currentValidators` and hence an update with 0 power would be generated + // to remove this validator + currentA, currentPublicKeyA := createConsumerValidator(1, 1, 1) + expectedUpdates = append(expectedUpdates, abci.ValidatorUpdate{PubKey: currentPublicKeyA, Power: 0}) + + // validator B exists in both `currentValidators` and `nextValidators` but it did not change its + // power or consumer public key and hence no validator update is generated + currentB, _ := createConsumerValidator(2, 1, 2) + nextB, _ := createConsumerValidator(2, 1, 2) + + // validator C exists in both `currentValidators` and `nextValidators` and it changes its power, so + // a validator update is generated with the new power + currentC, currentPublicKeyC := createConsumerValidator(3, 1, 3) + nextC, _ := createConsumerValidator(3, 2, 3) + expectedUpdates = append(expectedUpdates, abci.ValidatorUpdate{PubKey: currentPublicKeyC, Power: 2}) + + // validator D exists in both `currentValidators` and `nextValidators` and it changes its consumer public key, so + // a validator update is generated to remove the old public key and another update to add the new public key + currentD, currentPublicKeyD := createConsumerValidator(4, 1, 4) + nextD, nextPublicKeyD := createConsumerValidator(4, 1, 5) + expectedUpdates = append(expectedUpdates, abci.ValidatorUpdate{PubKey: currentPublicKeyD, Power: 0}) + expectedUpdates = append(expectedUpdates, abci.ValidatorUpdate{PubKey: nextPublicKeyD, Power: 1}) + + // validator E exists in both `currentValidators` and `nextValidators` and it changes both its power and + // its consumer public key, so a validator update is generated to remove the old public key and another update to + // add the new public key with thew new power + currentE, currentPublicKeyE := createConsumerValidator(5, 1, 6) + nextE, nextPublicKeyE := createConsumerValidator(5, 2, 7) + expectedUpdates = append(expectedUpdates, abci.ValidatorUpdate{PubKey: currentPublicKeyE, Power: 0}) + expectedUpdates = append(expectedUpdates, abci.ValidatorUpdate{PubKey: nextPublicKeyE, Power: 2}) + + // validator F does not exist in `currentValidators` and hence an update is generated to add this new validator + nextF, nextPublicKeyF := createConsumerValidator(6, 1, 8) + expectedUpdates = append(expectedUpdates, abci.ValidatorUpdate{PubKey: nextPublicKeyF, Power: 1}) + + currentValidators := []types.ConsumerValidator{currentA, currentB, currentC, currentD, currentE} + nextValidators := []types.ConsumerValidator{nextB, nextC, nextD, nextE, nextF} + + actualUpdates := keeper.DiffValidators(currentValidators, nextValidators) + + // sort validators first to be able to compare + sortUpdates := func(updates []abci.ValidatorUpdate) { + sort.Slice(updates, func(i, j int) bool { + if updates[i].Power != updates[j].Power { + return updates[i].Power < updates[j].Power + } + return updates[i].PubKey.String() < updates[j].PubKey.String() + }) + } + + sortUpdates(expectedUpdates) + sortUpdates(actualUpdates) + require.Equal(t, expectedUpdates, actualUpdates) +} + +func TestDiffEdgeCases(t *testing.T) { + require.Empty(t, len(keeper.DiffValidators([]types.ConsumerValidator{}, []types.ConsumerValidator{}))) + + valA, publicKeyA := createConsumerValidator(1, 1, 1) + valB, publicKeyB := createConsumerValidator(2, 2, 2) + valC, publicKeyC := createConsumerValidator(3, 3, 3) + validators := []types.ConsumerValidator{valA, valB, valC} + + // we do not expect any validator updates if the `currentValidators` are the same with the `nextValidators` + require.Empty(t, len(keeper.DiffValidators(validators, validators))) + + // only have `nextValidators` that would generate validator updates for the validators to be added + expectedUpdates := []abci.ValidatorUpdate{{publicKeyA, 1}, {publicKeyB, 2}, {publicKeyC, 3}} + actualUpdates := keeper.DiffValidators([]types.ConsumerValidator{}, validators) + // sort validators first to be able to compare + sortUpdates := func(updates []abci.ValidatorUpdate) { + sort.Slice(updates, func(i, j int) bool { + if updates[i].Power != updates[j].Power { + return updates[i].Power < updates[j].Power + } + return updates[i].PubKey.String() < updates[j].PubKey.String() + }) + } + + sortUpdates(expectedUpdates) + sortUpdates(actualUpdates) + require.Equal(t, expectedUpdates, actualUpdates) + + // only have `currentValidators` that would generate validator updates for the validators to be removed + expectedUpdates = []abci.ValidatorUpdate{{publicKeyA, 0}, {publicKeyB, 0}, {publicKeyC, 0}} + actualUpdates = keeper.DiffValidators(validators, []types.ConsumerValidator{}) + sortUpdates(expectedUpdates) + sortUpdates(actualUpdates) + require.Equal(t, expectedUpdates, actualUpdates) + + // have nonempty `currentValidators` and `nextValidators`, but with empty intersection + // all old validators should be removed, all new validators should be added + expectedUpdates = []abci.ValidatorUpdate{{publicKeyA, 0}, {publicKeyB, 2}} + actualUpdates = keeper.DiffValidators(validators[0:1], validators[1:2]) + sortUpdates(expectedUpdates) + sortUpdates(actualUpdates) + require.Equal(t, expectedUpdates, actualUpdates) +} + +func TestSetConsumerValSet(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + chainID := "chainID" + + currentValidators := []types.ConsumerValidator{ + { + ProviderConsAddr: []byte("currentProviderConsAddr1"), + Power: 2, + ConsumerPublicKey: &crypto.PublicKey{ + Sum: &crypto.PublicKey_Ed25519{ + Ed25519: []byte{2}, + }, + }, + }, + { + ProviderConsAddr: []byte("currentProviderConsAddr2"), + Power: 3, + ConsumerPublicKey: &crypto.PublicKey{ + Sum: &crypto.PublicKey_Ed25519{ + Ed25519: []byte{3}, + }, + }, + }, + { + ProviderConsAddr: []byte("currentProviderConsAddr3"), + Power: 4, + ConsumerPublicKey: &crypto.PublicKey{ + Sum: &crypto.PublicKey_Ed25519{ + Ed25519: []byte{4}, + }, + }, + }, + } + + nextValidators := []types.ConsumerValidator{ + { + ProviderConsAddr: []byte("nextProviderConsAddr1"), + Power: 2, + ConsumerPublicKey: &crypto.PublicKey{ + Sum: &crypto.PublicKey_Ed25519{ + Ed25519: []byte{2}, + }, + }, + }, + { + ProviderConsAddr: []byte("nextProviderConsAddr2"), + Power: 3, + ConsumerPublicKey: &crypto.PublicKey{ + Sum: &crypto.PublicKey_Ed25519{ + Ed25519: []byte{3}, + }, + }, + }, + } + + // set the `currentValidators` for chain `chainID` + require.Empty(t, providerKeeper.GetConsumerValSet(ctx, chainID)) + for _, validator := range currentValidators { + providerKeeper.SetConsumerValidator(ctx, chainID, validator) + } + require.NotEmpty(t, providerKeeper.GetConsumerValSet(ctx, chainID)) + + providerKeeper.SetConsumerValSet(ctx, chainID, nextValidators) + nextCurrentValidators := providerKeeper.GetConsumerValSet(ctx, chainID) + + // sort validators first to be able to compare + sortValidators := func(validators []types.ConsumerValidator) { + sort.Slice(validators, func(i, j int) bool { + return bytes.Compare(validators[i].ProviderConsAddr, validators[j].ProviderConsAddr) < 0 + }) + } + + sortValidators(nextValidators) + sortValidators(nextCurrentValidators) + require.Equal(t, nextValidators, nextCurrentValidators) +} diff --git a/x/ccv/provider/types/genesis_test.go b/x/ccv/provider/types/genesis_test.go index 45d766fcfb..41a716757f 100644 --- a/x/ccv/provider/types/genesis_test.go +++ b/x/ccv/provider/types/genesis_test.go @@ -81,7 +81,7 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600), nil, nil, nil, @@ -102,7 +102,7 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600), nil, nil, nil, @@ -123,7 +123,7 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600), nil, nil, nil, @@ -144,7 +144,7 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600), nil, nil, nil, @@ -171,7 +171,7 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, types.DefaultSlashMeterReplenishPeriod, types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600), nil, nil, nil, @@ -198,7 +198,7 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, types.DefaultSlashMeterReplenishPeriod, types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600), nil, nil, nil, @@ -225,7 +225,7 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, types.DefaultSlashMeterReplenishPeriod, types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: sdk.NewInt(1000000)}), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(1000000)}, 600), nil, nil, nil, @@ -252,7 +252,7 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, types.DefaultSlashMeterReplenishPeriod, types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600), nil, nil, nil, @@ -279,7 +279,7 @@ func TestValidateGenesisState(t *testing.T) { 0, // 0 vsc timeout here types.DefaultSlashMeterReplenishPeriod, types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600), nil, nil, nil, @@ -306,7 +306,7 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, 0, // 0 slash meter replenish period here types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600), nil, nil, nil, @@ -333,7 +333,7 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, types.DefaultSlashMeterReplenishPeriod, "1.15", - sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600), nil, nil, nil, @@ -685,7 +685,7 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "st", Amount: sdk.NewInt(10000000)}), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "st", Amount: sdk.NewInt(10000000)}, 600), nil, nil, nil, @@ -706,7 +706,7 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(-1000000)}), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(-1000000)}, 600), nil, nil, nil, diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 615b901368..e9222ade98 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -120,7 +120,9 @@ const ( // on consumer chains to validator addresses on the provider chain ValidatorsByConsumerAddrBytePrefix - // KeyAssignmentReplacementsBytePrefix is the byte prefix that will store the key assignments that need to be replaced in the current block + // KeyAssignmentReplacementsBytePrefix was the byte prefix used to store the key assignments that needed to be replaced in the current block + // NOTE: This prefix is deprecated, but left in place to avoid consumer state migrations + // [DEPRECATED] KeyAssignmentReplacementsBytePrefix // ConsumerAddrsToPruneBytePrefix is the byte prefix that will store the mapping from VSC ids @@ -145,6 +147,9 @@ const ( // ProposedConsumerChainByteKey is the byte prefix storing the consumer chainId in consumerAddition gov proposal submitted before voting finishes ProposedConsumerChainByteKey + // ConsumerValidatorBytePrefix is the byte prefix used when storing for each consumer chain all the consumer validators in this epoch + ConsumerValidatorBytePrefix + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -362,12 +367,6 @@ func ValidatorsByConsumerAddrKey(chainID string, addr ConsumerConsAddress) []byt return ChainIdAndConsAddrKey(ValidatorsByConsumerAddrBytePrefix, chainID, addr.ToSdkConsAddr()) } -// KeyAssignmentReplacementsKey returns the key under which the -// key assignments that need to be replaced in the current block are stored -func KeyAssignmentReplacementsKey(chainID string, addr ProviderConsAddress) []byte { - return ChainIdAndConsAddrKey(KeyAssignmentReplacementsBytePrefix, chainID, addr.ToSdkConsAddr()) -} - // ConsumerAddrsToPruneKey returns the key under which the // mapping from VSC ids to consumer validators addresses is stored func ConsumerAddrsToPruneKey(chainID string, vscID uint64) []byte { @@ -517,6 +516,12 @@ func ParseProposedConsumerChainKey(prefix byte, bz []byte) (uint64, error) { return proposalID, nil } +// ConsumerValidatorKey returns the key of consumer chain `chainID` and validator with `providerAddr` +func ConsumerValidatorKey(chainID string, providerAddr []byte) []byte { + prefix := ChainIdWithLenKey(ConsumerValidatorBytePrefix, chainID) + return append(prefix, providerAddr...) +} + // // 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..02faa9a640 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.ConsumerValidatorBytePrefix, } } @@ -96,7 +97,6 @@ func getAllFullyDefinedKeys() [][]byte { providertypes.GlobalSlashEntryKey(providertypes.GlobalSlashEntry{}), providertypes.ConsumerValidatorsKey("chainID", providertypes.NewProviderConsAddress([]byte{0x05})), providertypes.ValidatorsByConsumerAddrKey("chainID", providertypes.NewConsumerConsAddress([]byte{0x05})), - providertypes.KeyAssignmentReplacementsKey("chainID", providertypes.NewProviderConsAddress([]byte{0x05})), providertypes.ConsumerAddrsToPruneKey("chainID", 88), providertypes.SlashLogKey(providertypes.NewProviderConsAddress([]byte{0x05})), providertypes.VSCMaturedHandledThisBlockKey(), diff --git a/x/ccv/provider/types/params.go b/x/ccv/provider/types/params.go index a580e60f41..a90468b04f 100644 --- a/x/ccv/provider/types/params.go +++ b/x/ccv/provider/types/params.go @@ -36,6 +36,14 @@ const ( // that is replenished to the slash meter every replenish period. This param also serves as a maximum // fraction of total voting power that the slash meter can hold. DefaultSlashMeterReplenishFraction = "0.05" + + // DefaultBlocksPerEpoch defines the default blocks that constitute an epoch. Assuming we need 6 seconds per block, + // an epoch corresponds to 1 hour (6 * 600 = 3600 seconds). + DefaultBlocksPerEpoch = 600 + + // MaxBlocksPerEpoch defines the maximum blocks that constitute an epoch. Assuming we need 6 seconds per block, + // the maximum epoch corresponds to 2 hours (6 * 1200 = 7200 seconds). + MaxBlocksPerEpoch = 1200 ) // Reflection based keys for params subspace @@ -47,6 +55,7 @@ var ( KeySlashMeterReplenishPeriod = []byte("SlashMeterReplenishPeriod") KeySlashMeterReplenishFraction = []byte("SlashMeterReplenishFraction") KeyConsumerRewardDenomRegistrationFee = []byte("ConsumerRewardDenomRegistrationFee") + KeyBlocksPerEpoch = []byte("BlocksPerEpoch") ) // ParamKeyTable returns a key table with the necessary registered provider params @@ -64,6 +73,7 @@ func NewParams( slashMeterReplenishPeriod time.Duration, slashMeterReplenishFraction string, consumerRewardDenomRegistrationFee sdk.Coin, + blocksPerEpoch int64, ) Params { return Params{ TemplateClient: cs, @@ -74,6 +84,7 @@ func NewParams( SlashMeterReplenishPeriod: slashMeterReplenishPeriod, SlashMeterReplenishFraction: slashMeterReplenishFraction, ConsumerRewardDenomRegistrationFee: consumerRewardDenomRegistrationFee, + BlocksPerEpoch: blocksPerEpoch, } } @@ -104,6 +115,7 @@ func DefaultParams() Params { Denom: sdk.DefaultBondDenom, Amount: sdk.NewInt(10000000), }, + DefaultBlocksPerEpoch, ) } @@ -136,6 +148,9 @@ func (p Params) Validate() error { if err := ValidateCoin(p.ConsumerRewardDenomRegistrationFee); err != nil { return fmt.Errorf("consumer reward denom registration fee is invalid: %s", err) } + if err := ValidateBlocksPerEpoch(p.BlocksPerEpoch); err != nil { + return fmt.Errorf("blocks per epoch is invalid: %s", err) + } return nil } @@ -150,6 +165,7 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(KeySlashMeterReplenishPeriod, p.SlashMeterReplenishPeriod, ccvtypes.ValidateDuration), paramtypes.NewParamSetPair(KeySlashMeterReplenishFraction, p.SlashMeterReplenishFraction, ccvtypes.ValidateStringFraction), paramtypes.NewParamSetPair(KeyConsumerRewardDenomRegistrationFee, p.ConsumerRewardDenomRegistrationFee, ValidateCoin), + paramtypes.NewParamSetPair(KeyBlocksPerEpoch, p.BlocksPerEpoch, ccvtypes.ValidatePositiveInt64), } } @@ -192,3 +208,17 @@ func ValidateCoin(i interface{}) error { return nil } + +// ValidateBlocksPerEpoch validates the BlocksPerEpoch param is in [1, MaxBlocksPerEpoch] +func ValidateBlocksPerEpoch(i interface{}) error { + if _, ok := i.(int64); !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + if i.(int64) <= int64(0) { + return fmt.Errorf("blocks per epoch must be positive") + } + if i.(int64) > MaxBlocksPerEpoch { + return fmt.Errorf("blocks per epoch have to be at most %d", MaxBlocksPerEpoch) + } + return nil +} diff --git a/x/ccv/provider/types/params_test.go b/x/ccv/provider/types/params_test.go index 1de6b6fe54..cc5810a7ce 100644 --- a/x/ccv/provider/types/params_test.go +++ b/x/ccv/provider/types/params_test.go @@ -24,39 +24,48 @@ func TestValidateParams(t *testing.T) { {"custom valid params", types.NewParams( ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), true}, + "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000), true}, {"custom invalid params", types.NewParams( ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, 0, clienttypes.Height{}, nil, []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), false}, + "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000), false}, {"blank client", types.NewParams(&ibctmtypes.ClientState{}, - "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), false}, - {"nil client", types.NewParams(nil, "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), false}, + "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000), false}, + {"nil client", types.NewParams(nil, "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000), false}, // Check if "0.00" is valid or if a zero dec TrustFraction needs to return an error {"0 trusting period fraction", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.00", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), true}, + "0.00", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000), true}, {"0 ccv timeout period", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", 0, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), false}, + "0.33", 0, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000), false}, {"0 init timeout period", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, 0, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), false}, + "0.33", time.Hour, 0, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000), false}, {"0 vsc timeout period", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, 0, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), false}, + "0.33", time.Hour, time.Hour, 0, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000), false}, {"0 slash meter replenish period", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, 24*time.Hour, 0, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, 0, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000), false}, {"slash meter replenish fraction over 1", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "1.5", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "1.5", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000), false}, {"invalid consumer reward denom registration fee denom", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "0.1", sdk.Coin{Denom: "st", Amount: sdk.NewInt(10000000)}), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "0.1", sdk.Coin{Denom: "st", Amount: sdk.NewInt(10000000)}, 1000), false}, {"invalid consumer reward denom registration fee amount", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(-10000000)}), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(-10000000)}, 1000), false}, + {"0 blocks per epoch", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, + time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), + "0.00", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 0), false}, + {"exceeding max blocks per epoch", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, + time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), + "0.00", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, types.MaxBlocksPerEpoch+1), false}, + {"valid blocks per epoch", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, + time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), + "0.00", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, types.MaxBlocksPerEpoch), true}, } for _, tc := range testCases { diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index 41a87e69f4..819154b75d 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -451,6 +451,8 @@ type Params struct { SlashMeterReplenishFraction string `protobuf:"bytes,7,opt,name=slash_meter_replenish_fraction,json=slashMeterReplenishFraction,proto3" json:"slash_meter_replenish_fraction,omitempty"` // The fee required to be paid to add a reward denom ConsumerRewardDenomRegistrationFee types2.Coin `protobuf:"bytes,9,opt,name=consumer_reward_denom_registration_fee,json=consumerRewardDenomRegistrationFee,proto3" json:"consumer_reward_denom_registration_fee"` + // The number of blocks that comprise an epoch. + BlocksPerEpoch int64 `protobuf:"varint,10,opt,name=blocks_per_epoch,json=blocksPerEpoch,proto3" json:"blocks_per_epoch,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -542,6 +544,13 @@ func (m *Params) GetConsumerRewardDenomRegistrationFee() types2.Coin { return types2.Coin{} } +func (m *Params) GetBlocksPerEpoch() int64 { + if m != nil { + return m.BlocksPerEpoch + } + return 0 +} + // SlashAcks contains cons addresses of consumer chain validators // successfully slashed on the provider chain. type SlashAcks struct { @@ -1385,6 +1394,71 @@ func (m *ConsumerAddrsToPrune) GetConsumerAddrs() *AddressList { return nil } +// ConsumerValidator is used to facilitate epoch-based transitions. It contains relevant info for +// a validator that is opted in, in an epoch, on a consumer chain. +type ConsumerValidator struct { + // validator's consensus address on the provider chain + ProviderConsAddr []byte `protobuf:"bytes,1,opt,name=provider_cons_addr,json=providerConsAddr,proto3" json:"provider_cons_addr,omitempty"` + // voting power the validator has during this epoch + Power int64 `protobuf:"varint,2,opt,name=power,proto3" json:"power,omitempty"` + // public key the validator uses on the consumer chain during this epoch + ConsumerPublicKey *crypto.PublicKey `protobuf:"bytes,3,opt,name=consumer_public_key,json=consumerPublicKey,proto3" json:"consumer_public_key,omitempty"` +} + +func (m *ConsumerValidator) Reset() { *m = ConsumerValidator{} } +func (m *ConsumerValidator) String() string { return proto.CompactTextString(m) } +func (*ConsumerValidator) ProtoMessage() {} +func (*ConsumerValidator) Descriptor() ([]byte, []int) { + return fileDescriptor_f22ec409a72b7b72, []int{22} +} +func (m *ConsumerValidator) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConsumerValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConsumerValidator.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 *ConsumerValidator) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConsumerValidator.Merge(m, src) +} +func (m *ConsumerValidator) XXX_Size() int { + return m.Size() +} +func (m *ConsumerValidator) XXX_DiscardUnknown() { + xxx_messageInfo_ConsumerValidator.DiscardUnknown(m) +} + +var xxx_messageInfo_ConsumerValidator proto.InternalMessageInfo + +func (m *ConsumerValidator) GetProviderConsAddr() []byte { + if m != nil { + return m.ProviderConsAddr + } + return nil +} + +func (m *ConsumerValidator) GetPower() int64 { + if m != nil { + return m.Power + } + return 0 +} + +func (m *ConsumerValidator) GetConsumerPublicKey() *crypto.PublicKey { + if m != nil { + return m.ConsumerPublicKey + } + return nil +} + func init() { proto.RegisterType((*ConsumerAdditionProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerAdditionProposal") proto.RegisterType((*ConsumerRemovalProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerRemovalProposal") @@ -1408,6 +1482,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((*ConsumerValidator)(nil), "interchain_security.ccv.provider.v1.ConsumerValidator") } func init() { @@ -1415,113 +1490,117 @@ 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, + // 1755 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4f, 0x6f, 0x1b, 0xc7, + 0x15, 0xd7, 0x92, 0x94, 0x2c, 0x3e, 0x4a, 0x94, 0xb4, 0x52, 0xe2, 0x95, 0xab, 0x52, 0xf2, 0xa6, + 0x49, 0x55, 0xa4, 0x59, 0x56, 0x4a, 0x0b, 0x04, 0x46, 0x83, 0x40, 0xa2, 0x9c, 0x58, 0x56, 0x12, + 0x2b, 0x2b, 0x55, 0x46, 0xdb, 0xc3, 0x62, 0x38, 0x3b, 0x26, 0x07, 0x5a, 0xee, 0xac, 0x67, 0x66, + 0xd7, 0xe1, 0xa5, 0xe7, 0x1e, 0xd3, 0x5b, 0xd0, 0x4b, 0xd3, 0x02, 0x3d, 0xf7, 0x6b, 0xe4, 0x98, + 0x63, 0x4f, 0x49, 0x61, 0x1f, 0xfb, 0x25, 0x8a, 0x99, 0xfd, 0x4b, 0x4a, 0x72, 0x69, 0xb8, 0xbd, + 0xcd, 0xbe, 0x79, 0xef, 0xf7, 0xfe, 0xbf, 0x37, 0x24, 0xec, 0xd3, 0x50, 0x12, 0x8e, 0x87, 0x88, + 0x86, 0x9e, 0x20, 0x38, 0xe6, 0x54, 0x8e, 0xbb, 0x18, 0x27, 0xdd, 0x88, 0xb3, 0x84, 0xfa, 0x84, + 0x77, 0x93, 0xbd, 0xe2, 0xec, 0x44, 0x9c, 0x49, 0x66, 0xbe, 0x75, 0x8d, 0x8c, 0x83, 0x71, 0xe2, + 0x14, 0x7c, 0xc9, 0xde, 0x9d, 0xb7, 0x6f, 0x02, 0x4e, 0xf6, 0xba, 0xcf, 0x28, 0x27, 0x29, 0xd6, + 0x9d, 0x8d, 0x01, 0x1b, 0x30, 0x7d, 0xec, 0xaa, 0x53, 0x46, 0xdd, 0x1e, 0x30, 0x36, 0x08, 0x48, + 0x57, 0x7f, 0xf5, 0xe3, 0x27, 0x5d, 0x49, 0x47, 0x44, 0x48, 0x34, 0x8a, 0x32, 0x86, 0xce, 0x34, + 0x83, 0x1f, 0x73, 0x24, 0x29, 0x0b, 0x73, 0x00, 0xda, 0xc7, 0x5d, 0xcc, 0x38, 0xe9, 0xe2, 0x80, + 0x92, 0x50, 0x2a, 0xad, 0xe9, 0x29, 0x63, 0xe8, 0x2a, 0x86, 0x80, 0x0e, 0x86, 0x32, 0x25, 0x8b, + 0xae, 0x24, 0xa1, 0x4f, 0xf8, 0x88, 0xa6, 0xcc, 0xe5, 0x57, 0x26, 0xb0, 0x55, 0xb9, 0xc7, 0x7c, + 0x1c, 0x49, 0xd6, 0xbd, 0x24, 0x63, 0x91, 0xdd, 0xbe, 0x83, 0x99, 0x18, 0x31, 0xd1, 0x25, 0xca, + 0xff, 0x10, 0x93, 0x6e, 0xb2, 0xd7, 0x27, 0x12, 0xed, 0x15, 0x84, 0xdc, 0xee, 0x8c, 0xaf, 0x8f, + 0x44, 0xc9, 0x83, 0x19, 0xcd, 0xec, 0xb6, 0x7f, 0x58, 0x00, 0xab, 0xc7, 0x42, 0x11, 0x8f, 0x08, + 0x3f, 0xf0, 0x7d, 0xaa, 0x5c, 0x3a, 0xe5, 0x2c, 0x62, 0x02, 0x05, 0xe6, 0x06, 0xcc, 0x4b, 0x2a, + 0x03, 0x62, 0x19, 0x3b, 0xc6, 0x6e, 0xd3, 0x4d, 0x3f, 0xcc, 0x1d, 0x68, 0xf9, 0x44, 0x60, 0x4e, + 0x23, 0xc5, 0x6c, 0xd5, 0xf4, 0x5d, 0x95, 0x64, 0x6e, 0xc2, 0x62, 0x9a, 0x07, 0xea, 0x5b, 0x75, + 0x7d, 0x7d, 0x4b, 0x7f, 0x1f, 0xfb, 0xe6, 0x27, 0xd0, 0xa6, 0x21, 0x95, 0x14, 0x05, 0xde, 0x90, + 0xa8, 0x68, 0x58, 0x8d, 0x1d, 0x63, 0xb7, 0xb5, 0x7f, 0xc7, 0xa1, 0x7d, 0xec, 0xa8, 0x00, 0x3a, + 0x59, 0xd8, 0x92, 0x3d, 0xe7, 0x81, 0xe6, 0x38, 0x6c, 0x7c, 0xfb, 0xfd, 0xf6, 0x9c, 0xbb, 0x9c, + 0xc9, 0xa5, 0x44, 0xf3, 0x2e, 0x2c, 0x0d, 0x48, 0x48, 0x04, 0x15, 0xde, 0x10, 0x89, 0xa1, 0x35, + 0xbf, 0x63, 0xec, 0x2e, 0xb9, 0xad, 0x8c, 0xf6, 0x00, 0x89, 0xa1, 0xb9, 0x0d, 0xad, 0x3e, 0x0d, + 0x11, 0x1f, 0xa7, 0x1c, 0x0b, 0x9a, 0x03, 0x52, 0x92, 0x66, 0xe8, 0x01, 0x88, 0x08, 0x3d, 0x0b, + 0x3d, 0x95, 0x6d, 0xeb, 0x56, 0x66, 0x48, 0x9a, 0x69, 0x27, 0xcf, 0xb4, 0x73, 0x9e, 0x97, 0xc2, + 0xe1, 0xa2, 0x32, 0xe4, 0xab, 0x1f, 0xb6, 0x0d, 0xb7, 0xa9, 0xe5, 0xd4, 0x8d, 0xf9, 0x39, 0xac, + 0xc6, 0x61, 0x9f, 0x85, 0x3e, 0x0d, 0x07, 0x5e, 0x44, 0x38, 0x65, 0xbe, 0xb5, 0xa8, 0xa1, 0x36, + 0xaf, 0x40, 0x1d, 0x65, 0x45, 0x93, 0x22, 0x7d, 0xad, 0x90, 0x56, 0x0a, 0xe1, 0x53, 0x2d, 0x6b, + 0x7e, 0x01, 0x26, 0xc6, 0x89, 0x36, 0x89, 0xc5, 0x32, 0x47, 0x6c, 0xce, 0x8e, 0xb8, 0x8a, 0x71, + 0x72, 0x9e, 0x4a, 0x67, 0x90, 0xbf, 0x87, 0xdb, 0x92, 0xa3, 0x50, 0x3c, 0x21, 0x7c, 0x1a, 0x17, + 0x66, 0xc7, 0x7d, 0x23, 0xc7, 0x98, 0x04, 0x7f, 0x00, 0x3b, 0x38, 0x2b, 0x20, 0x8f, 0x13, 0x9f, + 0x0a, 0xc9, 0x69, 0x3f, 0x56, 0xb2, 0xde, 0x13, 0x8e, 0xb0, 0xae, 0x91, 0x96, 0x2e, 0x82, 0x4e, + 0xce, 0xe7, 0x4e, 0xb0, 0x7d, 0x9c, 0x71, 0x99, 0x8f, 0xe0, 0x27, 0xfd, 0x80, 0xe1, 0x4b, 0xa1, + 0x8c, 0xf3, 0x26, 0x90, 0xb4, 0xea, 0x11, 0x15, 0x42, 0xa1, 0x2d, 0xed, 0x18, 0xbb, 0x75, 0xf7, + 0x6e, 0xca, 0x7b, 0x4a, 0xf8, 0x51, 0x85, 0xf3, 0xbc, 0xc2, 0x68, 0xbe, 0x07, 0xe6, 0x90, 0x0a, + 0xc9, 0x38, 0xc5, 0x28, 0xf0, 0x48, 0x28, 0x39, 0x25, 0xc2, 0x5a, 0xd6, 0xe2, 0x6b, 0xe5, 0xcd, + 0xfd, 0xf4, 0xc2, 0x7c, 0x08, 0x77, 0x6f, 0x54, 0xea, 0xe1, 0x21, 0x0a, 0x43, 0x12, 0x58, 0x6d, + 0xed, 0xca, 0xb6, 0x7f, 0x83, 0xce, 0x5e, 0xca, 0x76, 0x6f, 0xf1, 0x8f, 0xdf, 0x6c, 0xcf, 0x7d, + 0xfd, 0xcd, 0xf6, 0x9c, 0xfd, 0x0f, 0x03, 0x6e, 0xf7, 0x0a, 0xc7, 0x47, 0x2c, 0x41, 0xc1, 0xff, + 0xb3, 0xc1, 0x0e, 0xa0, 0x29, 0x24, 0x8b, 0xd2, 0x92, 0x6e, 0xbc, 0x42, 0x49, 0x2f, 0x2a, 0x31, + 0x75, 0x61, 0xff, 0xc5, 0x80, 0x8d, 0xfb, 0x4f, 0x63, 0x9a, 0x30, 0x8c, 0xfe, 0x27, 0xf3, 0xe0, + 0x04, 0x96, 0x49, 0x05, 0x4f, 0x58, 0xf5, 0x9d, 0xfa, 0x6e, 0x6b, 0xff, 0x6d, 0x27, 0x1d, 0x4e, + 0x4e, 0x31, 0xb3, 0xb2, 0x01, 0xe5, 0x54, 0xb5, 0xbb, 0x93, 0xb2, 0xf7, 0x6a, 0x96, 0x61, 0xff, + 0xcd, 0x80, 0x3b, 0x2a, 0xd2, 0x03, 0xe2, 0x92, 0x67, 0x88, 0xfb, 0x47, 0x24, 0x64, 0x23, 0xf1, + 0xda, 0x76, 0xda, 0xb0, 0xec, 0x6b, 0x24, 0x4f, 0x32, 0x0f, 0xf9, 0xbe, 0xb6, 0x53, 0xf3, 0x28, + 0xe2, 0x39, 0x3b, 0xf0, 0x7d, 0x73, 0x17, 0x56, 0x4b, 0x1e, 0xae, 0xf2, 0xa9, 0xc2, 0xac, 0xd8, + 0xda, 0x39, 0x9b, 0xce, 0x32, 0xb1, 0xff, 0x6d, 0xc0, 0xea, 0x27, 0x01, 0xeb, 0xa3, 0xe0, 0x2c, + 0x40, 0x62, 0xa8, 0xaa, 0x6c, 0xac, 0xd2, 0xc3, 0x49, 0xd6, 0xde, 0xda, 0xbc, 0x99, 0xd3, 0xa3, + 0xc4, 0xf4, 0xc0, 0xf9, 0x08, 0xd6, 0x8a, 0x86, 0x2b, 0xaa, 0x40, 0x7b, 0x73, 0xb8, 0xfe, 0xfc, + 0xfb, 0xed, 0x95, 0xbc, 0xd8, 0x7a, 0xba, 0x22, 0x8e, 0xdc, 0x15, 0x3c, 0x41, 0xf0, 0xcd, 0x0e, + 0xb4, 0x68, 0x1f, 0x7b, 0x82, 0x3c, 0xf5, 0xc2, 0x78, 0xa4, 0x0b, 0xa8, 0xe1, 0x36, 0x69, 0x1f, + 0x9f, 0x91, 0xa7, 0x9f, 0xc7, 0x23, 0xf3, 0x7d, 0x78, 0x33, 0x5f, 0xac, 0x5e, 0x82, 0x02, 0x4f, + 0xc9, 0xab, 0x70, 0x70, 0x5d, 0x4f, 0x4b, 0xee, 0x7a, 0x7e, 0x7b, 0x81, 0x02, 0xa5, 0xec, 0xc0, + 0xf7, 0xb9, 0xfd, 0x62, 0x1e, 0x16, 0x4e, 0x11, 0x47, 0x23, 0x61, 0x9e, 0xc3, 0x8a, 0x24, 0xa3, + 0x28, 0x40, 0x92, 0x78, 0xe9, 0x30, 0xcf, 0x3c, 0x7d, 0x57, 0x0f, 0xf9, 0xea, 0x12, 0x74, 0x2a, + 0x6b, 0x2f, 0xd9, 0x73, 0x7a, 0x9a, 0x7a, 0x26, 0x91, 0x24, 0x6e, 0x3b, 0xc7, 0x48, 0x89, 0xe6, + 0x07, 0x60, 0x49, 0x1e, 0x0b, 0x59, 0x8e, 0xd9, 0x72, 0xbe, 0xa4, 0xb9, 0x7c, 0x33, 0xbf, 0x4f, + 0x27, 0x53, 0x31, 0x57, 0xae, 0x9f, 0xa8, 0xf5, 0xd7, 0x99, 0xa8, 0x67, 0xb0, 0xae, 0xd6, 0xd1, + 0x34, 0x66, 0x63, 0x76, 0xcc, 0x35, 0x25, 0x3f, 0x09, 0xfa, 0x05, 0x98, 0x89, 0xc0, 0xd3, 0x98, + 0xf3, 0xaf, 0x60, 0x67, 0x22, 0xf0, 0x24, 0xa4, 0x0f, 0x5b, 0x42, 0x15, 0x9f, 0x37, 0x22, 0x52, + 0xcf, 0xe7, 0x28, 0x20, 0x21, 0x15, 0xc3, 0x1c, 0x7c, 0x61, 0x76, 0xf0, 0x4d, 0x0d, 0xf4, 0x99, + 0xc2, 0x71, 0x73, 0x98, 0x4c, 0x4b, 0x0f, 0x3a, 0xd7, 0x6b, 0x29, 0x12, 0x74, 0x4b, 0x27, 0xe8, + 0x47, 0xd7, 0x40, 0x14, 0x59, 0x12, 0xf0, 0x4e, 0x65, 0x8f, 0xa8, 0xae, 0xf6, 0x74, 0x43, 0x79, + 0x9c, 0x0c, 0xd4, 0xb0, 0x45, 0xe9, 0x4a, 0x21, 0xa4, 0xd8, 0x85, 0xd9, 0xf4, 0x50, 0x4f, 0x9b, + 0x62, 0x72, 0xf4, 0x18, 0x0d, 0xb3, 0x07, 0x83, 0x5d, 0xae, 0x9b, 0x62, 0x46, 0xb8, 0x15, 0xac, + 0x8f, 0x09, 0x51, 0xdd, 0x5c, 0x59, 0x39, 0x24, 0x62, 0x78, 0xa8, 0x57, 0x62, 0xdd, 0x6d, 0x17, + 0xeb, 0xe5, 0xbe, 0xa2, 0x3e, 0x6c, 0x2c, 0x2e, 0xae, 0x36, 0xed, 0x9f, 0x41, 0x53, 0x37, 0xf3, + 0x01, 0xbe, 0x14, 0xe6, 0x16, 0x34, 0x55, 0x57, 0x10, 0x21, 0x88, 0xb0, 0x0c, 0x3d, 0x03, 0x4a, + 0x82, 0x2d, 0x61, 0xf3, 0xa6, 0x87, 0x95, 0x30, 0x1f, 0xc3, 0xad, 0x88, 0xe8, 0xad, 0xaf, 0x05, + 0x5b, 0xfb, 0x1f, 0x3a, 0x33, 0xbc, 0x71, 0x9d, 0x9b, 0x00, 0xdd, 0x1c, 0xcd, 0xe6, 0xe5, 0x73, + 0x6e, 0x6a, 0xd9, 0x08, 0xf3, 0x62, 0x5a, 0xe9, 0xaf, 0x5f, 0x49, 0xe9, 0x14, 0x5e, 0xa9, 0xf3, + 0x5d, 0x68, 0x1d, 0xa4, 0x6e, 0x7f, 0x4a, 0x85, 0xbc, 0x1a, 0x96, 0xa5, 0x6a, 0x58, 0x1e, 0x42, + 0x3b, 0xdb, 0x91, 0xe7, 0x4c, 0x0f, 0x24, 0xf3, 0xc7, 0x00, 0xd9, 0x72, 0x55, 0x83, 0x2c, 0x1d, + 0xd9, 0xcd, 0x8c, 0x72, 0xec, 0x4f, 0xec, 0xba, 0xda, 0xc4, 0xae, 0xb3, 0x5d, 0x58, 0xb9, 0x10, + 0xf8, 0x37, 0xf9, 0x03, 0xea, 0x51, 0x24, 0xcc, 0x37, 0x60, 0x41, 0xf5, 0x50, 0x06, 0xd4, 0x70, + 0xe7, 0x13, 0x81, 0x8f, 0xf5, 0xd4, 0x2e, 0x1f, 0x69, 0x2c, 0xf2, 0xa8, 0x2f, 0xac, 0xda, 0x4e, + 0x7d, 0xb7, 0xe1, 0xb6, 0xe3, 0x52, 0xfc, 0xd8, 0x17, 0xf6, 0x6f, 0xa1, 0x55, 0x01, 0x34, 0xdb, + 0x50, 0x2b, 0xb0, 0x6a, 0xd4, 0x37, 0xef, 0xc1, 0x66, 0x09, 0x34, 0x39, 0x86, 0x53, 0xc4, 0xa6, + 0x7b, 0xbb, 0x60, 0x98, 0x98, 0xc4, 0xc2, 0x7e, 0x04, 0x1b, 0xc7, 0x65, 0xd3, 0x17, 0x43, 0x7e, + 0xc2, 0x43, 0x63, 0x72, 0x9b, 0x6f, 0x41, 0xb3, 0xf8, 0x25, 0xa2, 0xbd, 0x6f, 0xb8, 0x25, 0xc1, + 0x1e, 0xc1, 0xea, 0x85, 0xc0, 0x67, 0x24, 0xf4, 0x4b, 0xb0, 0x1b, 0x02, 0x70, 0x38, 0x0d, 0x34, + 0xf3, 0x4b, 0xb7, 0x54, 0xc7, 0x60, 0xf3, 0x02, 0x05, 0xd4, 0x47, 0x92, 0xf1, 0x33, 0x22, 0xd3, + 0x05, 0x7c, 0x8a, 0xf0, 0x25, 0x91, 0xc2, 0x74, 0xa1, 0x11, 0x50, 0x21, 0xb3, 0xca, 0xfa, 0xe0, + 0xc6, 0xca, 0x4a, 0xf6, 0x9c, 0x9b, 0x40, 0x8e, 0x90, 0x44, 0x59, 0xef, 0x6a, 0x2c, 0xfb, 0xa7, + 0xb0, 0xfe, 0x19, 0x92, 0x31, 0x27, 0xfe, 0x44, 0x8e, 0x57, 0xa1, 0xae, 0xf2, 0x67, 0xe8, 0xfc, + 0xa9, 0xa3, 0x7a, 0x0f, 0x58, 0xf7, 0xbf, 0x8c, 0x18, 0x97, 0xc4, 0xbf, 0x12, 0x91, 0x97, 0x84, + 0xf7, 0x12, 0xd6, 0x55, 0xb0, 0x04, 0x09, 0x7d, 0xaf, 0xf0, 0x33, 0xcd, 0x63, 0x6b, 0xff, 0x57, + 0x33, 0x75, 0xc7, 0xb4, 0xba, 0xcc, 0x81, 0xb5, 0x64, 0x8a, 0x2e, 0xec, 0x3f, 0x19, 0x60, 0x9d, + 0x90, 0xf1, 0x81, 0x10, 0x74, 0x10, 0x8e, 0x48, 0x28, 0xd5, 0x0c, 0x44, 0x98, 0xa8, 0xa3, 0xf9, + 0x16, 0x2c, 0x17, 0x3b, 0x57, 0xaf, 0x5a, 0x43, 0xaf, 0xda, 0xa5, 0x9c, 0xa8, 0x1a, 0xcc, 0xbc, + 0x07, 0x10, 0x71, 0x92, 0x78, 0xd8, 0xbb, 0x24, 0xe3, 0x2c, 0x8b, 0x5b, 0xd5, 0x15, 0x9a, 0xfe, + 0x4e, 0x74, 0x4e, 0xe3, 0x7e, 0x40, 0xf1, 0x09, 0x19, 0xbb, 0x8b, 0x8a, 0xbf, 0x77, 0x42, 0xc6, + 0xea, 0x4d, 0x14, 0xb1, 0x67, 0x84, 0xeb, 0xbd, 0x57, 0x77, 0xd3, 0x0f, 0xfb, 0xcf, 0x06, 0xdc, + 0x2e, 0xd2, 0x91, 0x97, 0xeb, 0x69, 0xdc, 0x57, 0x12, 0x2f, 0x89, 0xdb, 0x15, 0x6b, 0x6b, 0xd7, + 0x58, 0xfb, 0x11, 0x2c, 0x15, 0x0d, 0xa2, 0xec, 0xad, 0xcf, 0x60, 0x6f, 0x2b, 0x97, 0x38, 0x21, + 0x63, 0xfb, 0x0f, 0x15, 0xdb, 0x0e, 0xc7, 0x95, 0xd9, 0xc7, 0xff, 0x8b, 0x6d, 0x85, 0xda, 0xaa, + 0x6d, 0xb8, 0x2a, 0x7f, 0xc5, 0x81, 0xfa, 0x55, 0x07, 0xec, 0xbf, 0x1a, 0xb0, 0x51, 0xd5, 0x2a, + 0xce, 0xd9, 0x29, 0x8f, 0x43, 0xf2, 0x32, 0xed, 0x65, 0xfb, 0xd5, 0xaa, 0xed, 0xf7, 0x18, 0xda, + 0x13, 0x46, 0x89, 0x2c, 0x1a, 0xbf, 0x98, 0xa9, 0xc6, 0x2a, 0xd3, 0xd5, 0x5d, 0xae, 0xfa, 0x21, + 0xec, 0xbf, 0x1b, 0xb0, 0x96, 0xdb, 0x58, 0x04, 0xcb, 0xfc, 0x39, 0x98, 0x85, 0x7b, 0xe5, 0xeb, + 0x2d, 0x2d, 0xa9, 0xd5, 0xfc, 0x26, 0x7f, 0xba, 0x95, 0xa5, 0x51, 0xab, 0x94, 0x86, 0xf9, 0x29, + 0xac, 0x17, 0x26, 0x47, 0x3a, 0x41, 0x33, 0x67, 0xb1, 0x78, 0x9f, 0x16, 0xa4, 0xc3, 0xc7, 0xdf, + 0x3e, 0xef, 0x18, 0xdf, 0x3d, 0xef, 0x18, 0xff, 0x7a, 0xde, 0x31, 0xbe, 0x7a, 0xd1, 0x99, 0xfb, + 0xee, 0x45, 0x67, 0xee, 0x9f, 0x2f, 0x3a, 0x73, 0xbf, 0xfb, 0x70, 0x40, 0xe5, 0x30, 0xee, 0x3b, + 0x98, 0x8d, 0xba, 0xd9, 0x9f, 0x15, 0x65, 0x4c, 0xde, 0x2b, 0xfe, 0xc9, 0x49, 0x7e, 0xd9, 0xfd, + 0x72, 0xf2, 0x7f, 0x22, 0x39, 0x8e, 0x88, 0xe8, 0x2f, 0xe8, 0xe9, 0xf5, 0xfe, 0x7f, 0x02, 0x00, + 0x00, 0xff, 0xff, 0x0a, 0xef, 0x81, 0x2b, 0x58, 0x12, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -1876,6 +1955,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.BlocksPerEpoch != 0 { + i = encodeVarintProvider(dAtA, i, uint64(m.BlocksPerEpoch)) + i-- + dAtA[i] = 0x50 + } { size, err := m.ConsumerRewardDenomRegistrationFee.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -2585,6 +2669,53 @@ func (m *ConsumerAddrsToPrune) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ConsumerValidator) 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 *ConsumerValidator) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConsumerValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ConsumerPublicKey != nil { + { + size, err := m.ConsumerPublicKey.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintProvider(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.Power != 0 { + i = encodeVarintProvider(dAtA, i, uint64(m.Power)) + i-- + dAtA[i] = 0x10 + } + if len(m.ProviderConsAddr) > 0 { + i -= len(m.ProviderConsAddr) + copy(dAtA[i:], m.ProviderConsAddr) + i = encodeVarintProvider(dAtA, i, uint64(len(m.ProviderConsAddr))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintProvider(dAtA []byte, offset int, v uint64) int { offset -= sovProvider(v) base := offset @@ -2774,6 +2905,9 @@ func (m *Params) Size() (n int) { } l = m.ConsumerRewardDenomRegistrationFee.Size() n += 1 + l + sovProvider(uint64(l)) + if m.BlocksPerEpoch != 0 { + n += 1 + sovProvider(uint64(m.BlocksPerEpoch)) + } return n } @@ -3053,6 +3187,26 @@ func (m *ConsumerAddrsToPrune) Size() (n int) { return n } +func (m *ConsumerValidator) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ProviderConsAddr) + if l > 0 { + n += 1 + l + sovProvider(uint64(l)) + } + if m.Power != 0 { + n += 1 + sovProvider(uint64(m.Power)) + } + if m.ConsumerPublicKey != nil { + l = m.ConsumerPublicKey.Size() + n += 1 + l + sovProvider(uint64(l)) + } + return n +} + func sovProvider(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -4507,6 +4661,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlocksPerEpoch", wireType) + } + m.BlocksPerEpoch = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BlocksPerEpoch |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipProvider(dAtA[iNdEx:]) @@ -6327,6 +6500,145 @@ func (m *ConsumerAddrsToPrune) Unmarshal(dAtA []byte) error { } return nil } +func (m *ConsumerValidator) 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: ConsumerValidator: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConsumerValidator: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProviderConsAddr", 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.ProviderConsAddr = append(m.ProviderConsAddr[:0], dAtA[iNdEx:postIndex]...) + if m.ProviderConsAddr == nil { + m.ProviderConsAddr = []byte{} + } + iNdEx = postIndex + case 2: + 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 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsumerPublicKey", 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 + } + if m.ConsumerPublicKey == nil { + m.ConsumerPublicKey = &crypto.PublicKey{} + } + if err := m.ConsumerPublicKey.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 From ae68d99accf451df5787cf8ee035214f5030a194 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:52:57 +0100 Subject: [PATCH 3/9] test: Add epochs to MBT (#1676) * cleanup ./changelog entries * rebase * 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) Co-authored-by: Marius Poke * docs: update changelog for v4.0.0 (#1578) update changelog * docs: prepare for v4.0.0 (#1581) * unclog build * update release notes * update release date * added proto declaration * temp commit * temp commit * more changes * first commit * add param and fix tests * reduce epoch size for e2e * clean up * mbt fix * fix diff bug * cleaning up * cleaning up * cleaning up * cleaning up * cleaning up * cleaning up * added more tests * more fixes * nit fixes * cleaning up * increase downtime by one block * fix logs * took into account Marius' comments * tiny fixes * Update x/ccv/provider/keeper/params.go Co-authored-by: Simon Noetzlin * use Bech32 addresses as keys for maps * refactor nextBlocks(epoch) to nextEpoch * Start adding epochs * Adjust tests for epochs * Use invariant script instead of handwriting Makefile * Fix key assignment valset invariant * Add better run_invariants script * Start adding epochs from trace into driver * Remove new block creation during consumer chain setup * Adjust model for epochs * Take into account comments * Revert changes to actions.go * Revert changes to x/ * Remove unused listMul * Advance time by epochLength instead of 1 second * Indent condition and clarify EndProviderEpoch --------- Co-authored-by: mpoke Co-authored-by: insumity Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Simon Noetzlin --- Makefile | 6 ++-- tests/mbt/driver/core.go | 4 +++ tests/mbt/driver/mbt_test.go | 61 +++++++++++++++++++++++++------- tests/mbt/driver/model_viewer.go | 4 +++ tests/mbt/driver/setup.go | 16 --------- tests/mbt/model/ccv.qnt | 46 ++++++++++++++---------- tests/mbt/model/ccv_model.qnt | 55 ++++++++++++++++++++-------- tests/mbt/model/ccv_test.qnt | 9 +++-- tests/mbt/model/ccv_utils.qnt | 17 ++++++--- tests/mbt/run_invariants.sh | 37 ++++++++++++++++++- 10 files changed, 181 insertions(+), 74 deletions(-) diff --git a/Makefile b/Makefile index 71866d8b24..1b3fa87e7f 100644 --- a/Makefile +++ b/Makefile @@ -140,10 +140,8 @@ test-trace: # Note: this is *not* using the Quint models to test the system, # this tests/verifies the Quint models *themselves*. 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 run --invariant "all{ValidatorUpdatesArePropagatedKeyAssignmentInv,ValidatorSetHasExistedKeyAssignmentInv,SameVscPacketsKeyAssignmentInv,MatureOnTimeInv,EventuallyMatureOnProviderInv,KeyAssignmentRulesInv}" tests/mbt/model/ccv_model.qnt --step stepKeyAssignment --max-steps 200 --max-samples 200 + cd tests/mbt/model;\ + ../run_invariants.sh diff --git a/tests/mbt/driver/core.go b/tests/mbt/driver/core.go index b9a4293df1..803cd486f1 100644 --- a/tests/mbt/driver/core.go +++ b/tests/mbt/driver/core.go @@ -76,6 +76,10 @@ func (s *Driver) providerChain() *ibctesting.TestChain { return s.chain("provider") } +func (s *Driver) providerHeight() int64 { + return s.providerChain().CurrentHeader.Height +} + func (s *Driver) providerCtx() sdk.Context { return s.providerChain().GetContext() } diff --git a/tests/mbt/driver/mbt_test.go b/tests/mbt/driver/mbt_test.go index 183839dc9a..df748ea11f 100644 --- a/tests/mbt/driver/mbt_test.go +++ b/tests/mbt/driver/mbt_test.go @@ -179,17 +179,25 @@ func RunItfTrace(t *testing.T, path string) { nodes[i] = addressMap[valName] } + // very hacky: the system produces a lot of extra blocks, e.g. when setting up consumer chains, when updating clients, etc. + // to be able to compare the model to the system, we make the blocks per epoch a very large number (such that an epoch never ends naturally in the system while running the trace) + // When an epoch in the model ends (which we can detect by the height modulo the epoch length), we produce many, many blocks in the system, such that an epoch actually ends. + blocksPerEpoch := int64(200) + modelBlocksPerEpoch := params["BlocksPerEpoch"].Value.(int64) + driver := newDriver(t, nodes, valNames) driver.DriverStats = &stats driver.setupProvider(modelParams, valSet, signers, nodes, valNames) - - // set `BlocksPerEpoch` to 10: a reasonable small value greater than 1 that prevents waiting for too - // many blocks and slowing down the tests providerParams := driver.providerKeeper().GetParams(driver.providerCtx()) - providerParams.BlocksPerEpoch = 10 + providerParams.BlocksPerEpoch = blocksPerEpoch driver.providerKeeper().SetParams(driver.providerCtx(), providerParams) + // begin enough blocks to end the first epoch + for i := int64(1); i < blocksPerEpoch; i++ { + driver.endAndBeginBlock("provider", 1*time.Nanosecond) + } + // remember the time offsets to be able to compare times to the model // this is necessary because the system needs to do many steps to initialize the chains, // which is abstracted away in the model @@ -200,8 +208,16 @@ func RunItfTrace(t *testing.T, path string) { t.Log("Reading the trace...") for index, state := range trace.States { + t.Log("Height modulo epoch length:", driver.providerChain().CurrentHeader.Height%blocksPerEpoch) + t.Log("Model height modulo epoch length:", ProviderHeight(state.VarValues["currentState"].Value.(itf.MapExprType))%modelBlocksPerEpoch) t.Logf("Reading state %v of trace %v", index, path) + // store the height of the provider state before each step. + // The height should only pass an epoch when it passes an epoch in the model, too, + // otherwise there is an error, and blocksPerEpoch needs to be increased. + // See the comment for blocksPerEpoch above. + heightBeforeStep := driver.providerHeight() + trace := state.VarValues["trace"].Value.(itf.ListExprType) // lastAction will get the last action that was executed so far along the model trace, // i.e. the action we should perform before checking model vs actual system equivalence @@ -239,22 +255,33 @@ func RunItfTrace(t *testing.T, path string) { stats.numStartedChains += len(consumersToStart) stats.numStops += len(consumersToStop) + // get the block height in the model + modelHeight := ProviderHeight(currentModelState) + + if modelHeight%modelBlocksPerEpoch == 0 { + // in the model, an epoch ends, so we need to produce blocks in the system to get the actual height + // to end an epoch with the first of the two subsequent calls to endAndBeginBlock below + actualHeight := driver.providerHeight() + + heightInEpoch := actualHeight % blocksPerEpoch + + // produce blocks until the next block ends the epoch + for i := heightInEpoch; i < blocksPerEpoch; i++ { + driver.endAndBeginBlock("provider", 1*time.Nanosecond) + } + } + // we need at least 2 blocks, because for a packet sent at height H, the receiving chain // needs a header of height H+1 to accept the packet - // so, we do `blocksPerEpoch` time advancements with a very small increment, - // and then increment the rest of the time + // so, we do two blocks, one with a very small increment, + // and then another to increment the rest of the time runningConsumersBefore := driver.runningConsumers() - // going through `blocksPerEpoch` blocks to take into account an epoch - blocksPerEpoch := driver.providerKeeper().GetBlocksPerEpoch(driver.providerCtx()) - for i := int64(0); i < blocksPerEpoch; i = i + 1 { - driver.endAndBeginBlock("provider", 1*time.Nanosecond) - } + driver.endAndBeginBlock("provider", 1*time.Nanosecond) for _, consumer := range driver.runningConsumers() { UpdateProviderClientOnConsumer(t, driver, consumer.ChainId) } - - driver.endAndBeginBlock("provider", time.Duration(timeAdvancement)*time.Second-time.Nanosecond*time.Duration(blocksPerEpoch)) + driver.endAndBeginBlock("provider", time.Duration(timeAdvancement)*time.Second-1*time.Nanosecond) runningConsumersAfter := driver.runningConsumers() @@ -436,6 +463,14 @@ func RunItfTrace(t *testing.T, path string) { stats.EnterStats(driver) + // should not have ended an epoch, unless we also ended an epoch in the model + heightAfterStep := driver.providerHeight() + + if heightBeforeStep/blocksPerEpoch != heightAfterStep/blocksPerEpoch { + // we changed epoch during this step, so ensure that the model also changed epochs + require.True(t, ProviderHeight(state.VarValues["currentState"].Value.(itf.MapExprType))%modelBlocksPerEpoch == 0, "Height in model did not change epoch, but did in system. increase blocksPerEpoch in the system") + } + t.Logf("State %v of trace %v is ok!", index, path) } t.Log("🟢 Trace is ok!") diff --git a/tests/mbt/driver/model_viewer.go b/tests/mbt/driver/model_viewer.go index ed090b2b86..f1f786c4f0 100644 --- a/tests/mbt/driver/model_viewer.go +++ b/tests/mbt/driver/model_viewer.go @@ -40,6 +40,10 @@ func RunningTime(curStateExpr itf.MapExprType, chain string) int64 { return ChainState(curStateExpr, chain)["runningTimestamp"].Value.(int64) } +func ProviderHeight(curStateExpr itf.MapExprType) int64 { + return ProviderState(curStateExpr)["chainState"].Value.(itf.MapExprType)["currentBlockHeight"].Value.(int64) +} + // PacketQueue returns the queued packets between sender and receiver. // Either sender or receiver need to be the provider. func PacketQueue(curStateExpr itf.MapExprType, sender, receiver string) itf.ListExprType { diff --git a/tests/mbt/driver/setup.go b/tests/mbt/driver/setup.go index 69b385cb77..c0020a4095 100644 --- a/tests/mbt/driver/setup.go +++ b/tests/mbt/driver/setup.go @@ -397,22 +397,6 @@ func (s *Driver) ConfigureNewPath(consumerChain, providerChain *ibctesting.TestC // their channel, and are ready for anything to happen. s.consumerKeeper(consumerChainId).SetProviderChannel(s.ctx(consumerChainId), consumerEndPoint.ChannelID) - // Commit a block on both chains, giving us two committed headers from - // the same time and height. This is the starting point for all our - // data driven testing. - lastConsumerHeader, _ := simibc.EndBlock(consumerChain, func() {}) - lastProviderHeader, _ := simibc.EndBlock(providerChain, func() {}) - - // Get ready to update clients. - simibc.BeginBlock(providerChain, 5) - simibc.BeginBlock(consumerChain, 5) - - // Update clients to the latest header. - err = simibc.UpdateReceiverClient(consumerEndPoint, providerEndPoint, lastConsumerHeader, false) - require.NoError(s.t, err, "Error updating client on consumer for chain %v", consumerChain.ChainID) - err = simibc.UpdateReceiverClient(providerEndPoint, consumerEndPoint, lastProviderHeader, false) - require.NoError(s.t, err, "Error updating client on provider for chain %v", consumerChain.ChainID) - // path is ready to go return path } diff --git a/tests/mbt/model/ccv.qnt b/tests/mbt/model/ccv.qnt index 0e42436c50..2a81fe3a2d 100644 --- a/tests/mbt/model/ccv.qnt +++ b/tests/mbt/model/ccv.qnt @@ -58,6 +58,8 @@ module ccv_types { // the running timestamp of the current block (that will be put on chain when the block is ended) runningTimestamp: Time, + + currentBlockHeight: int, } // utility function: returns a chain state that is initialized minimally. @@ -67,6 +69,7 @@ module ccv_types { currentValidatorSet: Map(), lastTimestamp: -1, // last timestamp -1 means that in the model, there was no block committed on chain yet. runningTimestamp: 0, + currentBlockHeight: 0 } // Defines the current state of the provider chain. Essentially, all information here is stored by the provider on-chain (or could be derived purely by information that is on-chain). @@ -86,11 +89,8 @@ module ccv_types { // Stores VscPackets which have been sent but where the provider has *not received a response yet*. sentVscPacketsToConsumer: Chain -> List[VscPacket], - // stores whether, in this block, the validator set has changed. - // this is needed because the validator set might be considered to have changed, even though - // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider - // might leave the validator set the same because a delegation and undelegation cancel each other out. - providerValidatorSetChangedInThisBlock: bool, + // stores for which consumer chains, in this epoch, the validator set is considered to have changed and we thus need to send a VscPacket to the consumer chains. + consumersWithPowerChangesInThisEpoch: Set[Chain], // stores, for each consumer chain, its current status - // not consumer, running, or stopped @@ -110,7 +110,7 @@ module ccv_types { 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], + consumersWithAddrAssignmentChangesInThisEpoch: 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. @@ -130,7 +130,7 @@ module ccv_types { outstandingPacketsToConsumer: Map(), receivedMaturations: Set(), sentVscPacketsToConsumer: Map(), - providerValidatorSetChangedInThisBlock: false, + consumersWithPowerChangesInThisEpoch: Set(), consumerStatus: Map(), runningVscId: 0, validatorToConsumerAddr: Map(), @@ -138,7 +138,7 @@ module ccv_types { consumerAddrToValidator: Map(), consumerAddrsToPrune: Map(), keyAssignmentsForVSCPackets: Map(), - consumersWithAddrAssignmentChangesInThisBlock: Set() + consumersWithAddrAssignmentChangesInThisEpoch: Set() } @@ -271,6 +271,10 @@ module ccv { // they expire and the channel will be closed. const TrustingPeriodPerChain: Chain -> int + // The number of blocks in an epoch. + // VscPackets are only sent to consumer chains at the end of every epoch. + const BlocksPerEpoch: int + // =================== // PROTOCOL LOGIC contains the meat of the protocol // functions here roughly correspond to API calls that can be triggered from external sources @@ -294,7 +298,7 @@ module ccv { } else { // set the validator set changed flag val newProviderState = currentState.providerState.with( - "providerValidatorSetChangedInThisBlock", true + "consumersWithPowerChangesInThisEpoch", getRunningConsumers(currentState.providerState) ) pure val tmpState = currentState.with( "providerState", newProviderState @@ -455,10 +459,16 @@ module ccv { // send vsc packets (will be a noop if no sends are necessary) val providerStateAfterSending = - providerStateAfterTimeAdvancement.sendVscPackets( - currentProviderState.chainState.runningTimestamp, - CcvTimeout.get(PROVIDER_CHAIN) - ) + // if currentBlockHeight is a multiple of BlocksPerEpoch, send VscPackets + if (providerStateAfterTimeAdvancement.chainState.currentBlockHeight % BlocksPerEpoch == 0) { + providerStateAfterTimeAdvancement.sendVscPackets( + currentProviderState.chainState.runningTimestamp, + CcvTimeout.get(PROVIDER_CHAIN) + ) + } else { + // otherwise, just do a noop + providerStateAfterTimeAdvancement + } // start/stop chains @@ -471,8 +481,6 @@ module ccv { val err = res._2 val providerStateAfterConsumerAdvancement = providerStateAfterSending.with( "consumerStatus", newConsumerStatus - ).with( - "providerValidatorSetChangedInThisBlock", false ) if (err != "") { @@ -609,17 +617,17 @@ module ccv { // 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 = + pure val consumersWithAddrAssignmentChangesInThisEpoch = if (provValPower > 0) { // if the consumer has positive power, the relevant key assignment for the consumer changed - currentState.providerState.consumersWithAddrAssignmentChangesInThisBlock.union(Set(consumer)) + currentState.providerState.consumersWithAddrAssignmentChangesInThisEpoch.union(Set(consumer)) } else { // otherwise, the consumer doesn't need to know about the change, so no change - currentState.providerState.consumersWithAddrAssignmentChangesInThisBlock + currentState.providerState.consumersWithAddrAssignmentChangesInThisEpoch } pure val tmpStateAfterKeyAssignmentReplacement = tmpState.with( "providerState", tmpState.providerState.with( - "consumersWithAddrAssignmentChangesInThisBlock", consumersWithAddrAssignmentChangesInThisBlock + "consumersWithAddrAssignmentChangesInThisEpoch", consumersWithAddrAssignmentChangesInThisEpoch ) ) diff --git a/tests/mbt/model/ccv_model.qnt b/tests/mbt/model/ccv_model.qnt index f997198b6e..1509ad439f 100644 --- a/tests/mbt/model/ccv_model.qnt +++ b/tests/mbt/model/ccv_model.qnt @@ -13,6 +13,7 @@ module ccv_model { pure val unbondingPeriods = chains.mapBy(chain => defUnbondingPeriod) pure val trustingPeriods = chains.mapBy(chain => defUnbondingPeriod - 1 * Hour) pure val ccvTimeouts = chains.mapBy(chain => 3 * Week) + pure val epochLength = 3 pure val nodes = Set("node1", "node2", "node3", "node4", "node5", "node6", "node7", "node8", "node9", "node10") // possible consumer addresses that nodes can assign their key to @@ -24,7 +25,8 @@ module ccv_model { CcvTimeout = ccvTimeouts, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = consumerChains, - TrustingPeriodPerChain = trustingPeriods + TrustingPeriodPerChain = trustingPeriods, + BlocksPerEpoch = epochLength ).* from "./ccv" type Parameters = { @@ -36,6 +38,7 @@ module ccv_model { Nodes: Set[Node], ConsumerAddresses: Set[ConsumerAddr], InitialValidatorSet: Node -> int, + BlocksPerEpoch: int, } // The params variable is never actually changed, and @@ -129,6 +132,7 @@ module ccv_model { InitialValidatorSet: InitialValidatorSet, TrustingPeriodPerChain: TrustingPeriodPerChain, ConsumerAddresses: consumerAddresses, + BlocksPerEpoch: epochLength, } } @@ -235,6 +239,25 @@ module ccv_model { stepCommon } + // Runs epochLength many blocks on the provider. + // The first block will start all consumers in consumersToStart and stop all consumers in consumersToStop, + // and advance time by timeAdvancement - ((epochLength-1) * Seconds). + // The rest of the blocks will not start or stop any consumers, and will advance time by 1 second each. + // As a a `Run`, it is only used in tests, not during simulation or verification. + run EndProviderEpoch( + timeAdvancement: Time, + consumersToStart: Set[Chain], + consumersToStop: Set[Chain] + ): bool = + epochLength.reps( + i => + if (i == 0) { + EndAndBeginBlockForProvider(timeAdvancement-((epochLength-1)*Second), consumersToStart, consumersToStop) + } else { + EndAndBeginBlockForProvider(1 * Second, Set(), Set()) + } + ) + // ================== // UTILITY FUNCTIONS // ================== @@ -319,16 +342,19 @@ module ccv_model { ) ) + + val CurrentBlockEndsEpoch = currentState.providerState.chainState.currentBlockHeight % epochLength == 0 + // 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 val ValUpdatePrecondition = trace[trace.length()-1].kind == "EndAndBeginBlockForProvider" val ValidatorUpdatesArePropagatedInv = - // when the provider has just entered a validator set into a block... - ValUpdatePrecondition and currentState.providerState.providerValidatorSetChangedInThisBlock + // when the an epoch ends and the provider has just entered a validator set into a block... + ValUpdatePrecondition and CurrentBlockEndsEpoch implies val providerValSetInCurBlock = providerValidatorHistory.head() - // ... for each consumer that is running then ... - runningConsumers.forall( + // ... for each consumer for which we need to send a vsc packet ... + currentState.providerState.consumersWithPowerChangesInThisEpoch.forall( // ...the validator set is in a sent packet... consumer => currentState.providerState.sentVscPacketsToConsumer.get(consumer).toSet().exists( packet => packet.validatorSet == providerValSetInCurBlock @@ -517,9 +543,10 @@ module ccv_model { assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet.put("node1", 150), InitialValidatorSet)), // change voting power on provider again VotingPowerChange("node1", 50).then( - // end another block - EndAndBeginBlockForProvider(1 * Second, Set(), Set()) - ).then( + // end the epoch + EndProviderEpoch(epochLength * Second, Set(), Set()) + ) + .then( // deliver packet to consumer1 DeliverVscPacket("consumer1") ) @@ -588,7 +615,7 @@ module ccv_model { VotingPowerChange("node1", 50) ).then( // send packet to consumer1 and consumer2 - EndAndBeginBlockForProvider(1 * Second, Set(), Set()) + EndProviderEpoch(epochLength * Second, Set(), Set()) ).then( // deliver the packets DeliverVscPacket("consumer1") @@ -603,7 +630,7 @@ module ccv_model { VotingPowerChange("node2", 50) ).then( // send packets - EndAndBeginBlockForProvider(1 * Second, Set(), Set()) + EndProviderEpoch(epochLength * Second, Set(), Set()) ).then( //deliver to consumer1 DeliverVscPacket("consumer1") @@ -636,7 +663,7 @@ module ccv_model { ) .then( // send packets - EndAndBeginBlockForProvider(1 * Second, Set(), Set()) + EndProviderEpoch(epochLength * Second, Set(), Set()) ) .then( // advance time on provider by VscTimeout + 1 Second @@ -715,11 +742,11 @@ module ccv_model { // 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 + ValUpdatePrecondition and CurrentBlockEndsEpoch implies val providerValSetInCurBlock = providerValidatorHistory.head() // ... for each consumer that is running then ... - runningConsumers.forall( + currentState.providerState.consumersWithPowerChangesInThisEpoch.forall( // ...the validator set under key assignment is in a sent packet... val providerState = currentState.providerState consumer => providerState.sentVscPacketsToConsumer.get(consumer).toSet().exists( @@ -849,7 +876,7 @@ module ccv_model { ) .then( // end and begin block to make sure the key assignment is processed and the packet is sent - EndAndBeginBlockForProvider(1 * Second, Set(), Set()) + EndProviderEpoch(epochLength * Second, Set(), Set()) ) .then( // receive the packet on the consumer diff --git a/tests/mbt/model/ccv_test.qnt b/tests/mbt/model/ccv_test.qnt index 62ab03b5e0..ab47df50b7 100644 --- a/tests/mbt/model/ccv_test.qnt +++ b/tests/mbt/model/ccv_test.qnt @@ -12,8 +12,9 @@ module ccv_test { pure val unbondingPeriods = chains.mapBy(chain => 2 * Week) pure val ccvTimeouts = chains.mapBy(chain => 3 * Week) pure val trustingPeriods = chains.mapBy(chain => 2 * Week - 1 * Hour) + pure val epochLength = 3 - import ccv(VscTimeout = 5 * Week, CcvTimeout = ccvTimeouts, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = consumerChains, TrustingPeriodPerChain=trustingPeriods).* from "./ccv" + import ccv(VscTimeout = 5 * Week, CcvTimeout = ccvTimeouts, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = consumerChains, TrustingPeriodPerChain=trustingPeriods, BlocksPerEpoch=epochLength).* from "./ccv" val votingPowerTestInitState = GetEmptyProtocolState.with( @@ -25,6 +26,10 @@ module ccv_test { "validator2", 5 ) ) + ).with( + "consumerStatus", Map( + "consumer" -> RUNNING + ) ) ) @@ -78,7 +83,7 @@ module ccv_test { ) // still should set the flag not(finalResult.hasError()) and - finalResult.newState.providerState.providerValidatorSetChangedInThisBlock + finalResult.newState.providerState.consumersWithPowerChangesInThisEpoch.contains("consumer") } diff --git a/tests/mbt/model/ccv_utils.qnt b/tests/mbt/model/ccv_utils.qnt index 4d881828ef..476ef923dc 100644 --- a/tests/mbt/model/ccv_utils.qnt +++ b/tests/mbt/model/ccv_utils.qnt @@ -183,9 +183,16 @@ module ccv_utils { runningTimestamp: chainState.runningTimestamp + timeAdvancement, } + pure def incrementBlockHeight(chainState: ChainState): ChainState = + { + chainState.with( + "currentBlockHeight", chainState.currentBlockHeight + 1 + ) + } + // 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) + chainState.enterCurValSetIntoBlock().advanceTime(timeAdvancement).incrementBlockHeight() } // returns the providerState with the following modifications: @@ -201,8 +208,8 @@ module ccv_utils { 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)) + if ((providerState.consumersWithPowerChangesInThisEpoch.contains(consumer) or + providerState.consumersWithAddrAssignmentChangesInThisEpoch.contains(consumer)) and isRunningConsumer(consumer, providerState)) { // send a packet, i.e. use a list with one element (the packet to be sent) @@ -240,8 +247,8 @@ module ccv_utils { 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(), + consumersWithPowerChangesInThisEpoch: Set(), + consumersWithAddrAssignmentChangesInThisEpoch: Set(), // remember the key assignments that were applied to send the packets keyAssignmentsForVSCPackets: providerState.keyAssignmentsForVSCPackets.put( providerState.runningVscId, diff --git a/tests/mbt/run_invariants.sh b/tests/mbt/run_invariants.sh index df22b97702..afe079387e 100755 --- a/tests/mbt/run_invariants.sh +++ b/tests/mbt/run_invariants.sh @@ -1,6 +1,41 @@ #!/bin/bash +# to stop on any errors +set -e + 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 +quint run --invariant "all{ValidatorUpdatesArePropagatedKeyAssignmentInv,ValidatorSetHasExistedKeyAssignmentInv,SameVscPacketsKeyAssignmentInv,MatureOnTimeInv,EventuallyMatureOnProviderInv,KeyAssignmentRulesInv}" ccv_model.qnt --step stepKeyAssignment --max-steps 200 --max-samples 200 + + +# do not stop on errors anymore, so we can give better output if we error +set +e + +run_invariant() { + local invariant=$1 + local step=$2 + local match=$3 + + if [[ -z "$step" ]]; then + quint run --invariant $invariant ccv_model.qnt | grep -q $match + else + quint run --invariant $invariant --step $step ccv_model.qnt | grep -q $match + fi + + if [[ $? -eq 0 ]]; then + echo "sanity check $invariant ok" + else + echo "sanity check $invariant not ok" + exit 1 + fi +} + +run_invariant "CanRunConsumer" "" '[violation]' +run_invariant "CanStopConsumer" "" '[violation]' +run_invariant "CanTimeoutConsumer" "" '[violation]' +run_invariant "CanSendVscPackets" "" '[violation]' +run_invariant "CanSendVscMaturedPackets" "" '[violation]' +run_invariant "CanAssignConsumerKey" "stepKeyAssignment" '[violation]' +run_invariant "CanHaveConsumerAddresses" "stepKeyAssignment" '[violation]' +run_invariant "CanReceiveMaturations" "stepKeyAssignment" '[violation]' \ No newline at end of file From f2764a2432a2e239137bce48a3691f7aadfeebb9 Mon Sep 17 00:00:00 2001 From: insumity Date: Mon, 11 Mar 2024 09:50:24 +0100 Subject: [PATCH 4/9] added changelogs --- .../unreleased/features/provider/1516-introduce-epochs.md | 3 +++ .../state-breaking/provider/1516-introduce-epochs.md | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 .changelog/unreleased/features/provider/1516-introduce-epochs.md create mode 100644 .changelog/unreleased/state-breaking/provider/1516-introduce-epochs.md diff --git a/.changelog/unreleased/features/provider/1516-introduce-epochs.md b/.changelog/unreleased/features/provider/1516-introduce-epochs.md new file mode 100644 index 0000000000..1ebce4000b --- /dev/null +++ b/.changelog/unreleased/features/provider/1516-introduce-epochs.md @@ -0,0 +1,3 @@ +- Introduce epochs (i.e., send a VSCPacket every X blocks instead of in every + block) so that we reduce the cost of relaying IBC packets needed for ICS. + ([\#1516](https://github.com/cosmos/interchain-security/pull/1516)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/provider/1516-introduce-epochs.md b/.changelog/unreleased/state-breaking/provider/1516-introduce-epochs.md new file mode 100644 index 0000000000..1ebce4000b --- /dev/null +++ b/.changelog/unreleased/state-breaking/provider/1516-introduce-epochs.md @@ -0,0 +1,3 @@ +- Introduce epochs (i.e., send a VSCPacket every X blocks instead of in every + block) so that we reduce the cost of relaying IBC packets needed for ICS. + ([\#1516](https://github.com/cosmos/interchain-security/pull/1516)) \ No newline at end of file From 192af119746bc0cf2ad7c024ff6165bf62025129 Mon Sep 17 00:00:00 2001 From: insumity Date: Mon, 11 Mar 2024 13:39:11 +0100 Subject: [PATCH 5/9] rebase and fix compatibility test --- tests/e2e/config.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/e2e/config.go b/tests/e2e/config.go index e71f3d5b4c..e530ca3a9d 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -466,7 +466,7 @@ func CompatibilityTestConfig(providerVersion, consumerVersion string) TestConfig ".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling ".app_state.provider.params.slash_meter_replenish_period = \"3s\"", } - } else if semver.Compare(providerVersion, "v4.0.0") < 0 { + } else if semver.Compare(providerVersion, "v4.0.0") <= 0 { fmt.Println("Using provider chain config for v3.x.x") providerConfig = ChainConfig{ ChainId: ChainID("provi"), @@ -553,9 +553,7 @@ func DemocracyTestConfig(allowReward bool) TestConfig { ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + ".app_state.slashing.params.downtime_jail_duration = \"60s\" | " + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + - ".app_state.transfer.params.send_enabled = false | " + - ".app_state.provider.params.blocks_per_epoch = 3" - + ".app_state.transfer.params.send_enabled = false" name := string(DemocracyTestCfg) if allowReward { // This allows the consumer chain to send rewards in the stake denom From c859295e0e3ba0b6061c80ca92c1590df4d1aa36 Mon Sep 17 00:00:00 2001 From: insumity Date: Mon, 11 Mar 2024 16:47:44 +0100 Subject: [PATCH 6/9] Update docs/docs/adrs/adr-014-epochs.md Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> --- docs/docs/adrs/adr-014-epochs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/adrs/adr-014-epochs.md b/docs/docs/adrs/adr-014-epochs.md index 2f4f5df087..9bcf241917 100644 --- a/docs/docs/adrs/adr-014-epochs.md +++ b/docs/docs/adrs/adr-014-epochs.md @@ -52,7 +52,7 @@ valUpdates := DiffValidators(currentValidators, stakingmodule.GetBondedValidator SetConsumerValSet(stakingmodule.GetBondedValidators()) ``` Note that a validator can change its consumer public key for a specific consumer chain an arbitrary amount of times during -a block and during an epoch. Then, when we generate the validator updates in `DiffValidators`, we have to check on whether +a block and during an epoch. Then, when we generate the validator updates in `DiffValidators`, we have to check whether the current consumer public key (retrieved by calling `GetValidatorConsumerPubKey`) is different from the consumer public key the validator was using in the current epoch. From 800c1aedc5371d400bf57bdf0babf02278a73b53 Mon Sep 17 00:00:00 2001 From: insumity Date: Mon, 11 Mar 2024 16:48:09 +0100 Subject: [PATCH 7/9] Update docs/docs/adrs/adr-014-epochs.md Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> --- docs/docs/adrs/adr-014-epochs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/adrs/adr-014-epochs.md b/docs/docs/adrs/adr-014-epochs.md index 9bcf241917..37a1bd62f0 100644 --- a/docs/docs/adrs/adr-014-epochs.md +++ b/docs/docs/adrs/adr-014-epochs.md @@ -23,7 +23,7 @@ As a matter of fact, this already happens due to relaying delays. As a solution, this ADR introduces the concept of _epochs_. An epoch consists of multiple blocks. The provider sends `VSCPacket`s once per epoch. -A `VSCPacket` contains all the validator updates that are needed by consumer chains. +A `VSCPacket` contains all the validator updates that are needed by a consumer chain. ## Decision From b628eea725bd46a59a365dc9dda3144bb0d3e24b Mon Sep 17 00:00:00 2001 From: insumity Date: Mon, 11 Mar 2024 17:00:07 +0100 Subject: [PATCH 8/9] nit change in test --- tests/integration/expired_client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/expired_client.go b/tests/integration/expired_client.go index cfba19803e..a46df32d8e 100644 --- a/tests/integration/expired_client.go +++ b/tests/integration/expired_client.go @@ -96,7 +96,7 @@ func (s *CCVTestSuite) TestConsumerPacketSendExpiredClient() { delegate(s, delAddr, bondAmt) // send CCV packet to consumer - s.providerChain.NextBlock() + s.nextEpoch() // bond more tokens on provider to change validator powers delegate(s, delAddr, bondAmt) From 0a2546f0c44ce6aa61cd7a5380c4980e1f32dc76 Mon Sep 17 00:00:00 2001 From: insumity Date: Tue, 12 Mar 2024 10:12:11 +0100 Subject: [PATCH 9/9] removed blocks per epoch upper limit --- docs/docs/adrs/adr-014-epochs.md | 3 +-- docs/docs/introduction/params.md | 9 +++++++-- x/ccv/provider/types/params.go | 20 +------------------- x/ccv/provider/types/params_test.go | 9 --------- 4 files changed, 9 insertions(+), 32 deletions(-) diff --git a/docs/docs/adrs/adr-014-epochs.md b/docs/docs/adrs/adr-014-epochs.md index 37a1bd62f0..fc669e9b36 100644 --- a/docs/docs/adrs/adr-014-epochs.md +++ b/docs/docs/adrs/adr-014-epochs.md @@ -35,8 +35,7 @@ The implementation of epochs requires the following changes: The initial consumer validator set for a chain is set during the creation of the consumer genesis. - We introduce the `BlocksPerEpoch` param that sets the number of blocks in an epoch. By default, `BlocksPerEpoch` is set to be 600 which corresponds to 1 hour, assuming 6 seconds per block. This param can be changed through - a _governance proposal_ to be anywhere between `[1, MaxBlocksPerEpoch]` where `MaxBlocksPerEpoch` can be up to 1200 - (2 hours if we assume 6 seconds per block). In the provider `EndBlock` we check `BlockHeight() % BlocksPerEpoch() == 0` + a _governance proposal_. In the provider `EndBlock` we check `BlockHeight() % BlocksPerEpoch() == 0` to decide when an epoch has ended. - At the end of every epoch, if there were validator set changes on the provider, then for every consumer chain, we construct a `VSCPacket` with all the validator updates and add it to the list of `PendingVSCPackets`. We compute the diff --git a/docs/docs/introduction/params.md b/docs/docs/introduction/params.md index 69994f261c..8d917b3070 100644 --- a/docs/docs/introduction/params.md +++ b/docs/docs/introduction/params.md @@ -154,5 +154,10 @@ This param would allow provider binaries to panic deterministically in the event ### BlocksPerEpoch `BlocksPerEpoch` exists on the provider for **ICS versions >= 3.3.0** (introduced by the implementation of [ADR-014](../adrs/adr-014-epochs.md)) -and corresponds to the number of blocks that constitute an epoch. This param is set to 600 by default and cannot exceed 1200. -Assuming we need 6 seconds per block, the default value corresponds to 1 hour and the maximum to 2 hours. \ No newline at end of file +and corresponds to the number of blocks that constitute an epoch. This param is set to 600 by default. Assuming we need 6 seconds to +commit a block, the duration of an epoch corresponds to 1 hour. This means that a `VSCPacket` would be sent to a consumer +chain once at the end of every epoch, so once every 600 blocks. This parameter can be adjusted via a governance proposal, +however careful consideration is needed so that `BlocksPerEpoch` is not too large. A large `BlocksPerEpoch` could lead to a delay +of `VSCPacket`s and hence potentially lead to [unbonding pausing](https://informal.systems/blog/learning-to-live-with-unbonding-pausing). +For setting `BlocksPerEpoch`, we also need to consider potential slow chain upgrades that could delay the sending of a +`VSCPacket`, as well as potential increases in the time it takes to commit a block (e.g., from 6 seconds to 30 seconds). \ No newline at end of file diff --git a/x/ccv/provider/types/params.go b/x/ccv/provider/types/params.go index a90468b04f..30bce2d17f 100644 --- a/x/ccv/provider/types/params.go +++ b/x/ccv/provider/types/params.go @@ -40,10 +40,6 @@ const ( // DefaultBlocksPerEpoch defines the default blocks that constitute an epoch. Assuming we need 6 seconds per block, // an epoch corresponds to 1 hour (6 * 600 = 3600 seconds). DefaultBlocksPerEpoch = 600 - - // MaxBlocksPerEpoch defines the maximum blocks that constitute an epoch. Assuming we need 6 seconds per block, - // the maximum epoch corresponds to 2 hours (6 * 1200 = 7200 seconds). - MaxBlocksPerEpoch = 1200 ) // Reflection based keys for params subspace @@ -148,7 +144,7 @@ func (p Params) Validate() error { if err := ValidateCoin(p.ConsumerRewardDenomRegistrationFee); err != nil { return fmt.Errorf("consumer reward denom registration fee is invalid: %s", err) } - if err := ValidateBlocksPerEpoch(p.BlocksPerEpoch); err != nil { + if err := ccvtypes.ValidateInt64(p.BlocksPerEpoch); err != nil { return fmt.Errorf("blocks per epoch is invalid: %s", err) } return nil @@ -208,17 +204,3 @@ func ValidateCoin(i interface{}) error { return nil } - -// ValidateBlocksPerEpoch validates the BlocksPerEpoch param is in [1, MaxBlocksPerEpoch] -func ValidateBlocksPerEpoch(i interface{}) error { - if _, ok := i.(int64); !ok { - return fmt.Errorf("invalid parameter type: %T", i) - } - if i.(int64) <= int64(0) { - return fmt.Errorf("blocks per epoch must be positive") - } - if i.(int64) > MaxBlocksPerEpoch { - return fmt.Errorf("blocks per epoch have to be at most %d", MaxBlocksPerEpoch) - } - return nil -} diff --git a/x/ccv/provider/types/params_test.go b/x/ccv/provider/types/params_test.go index cc5810a7ce..4e72c233af 100644 --- a/x/ccv/provider/types/params_test.go +++ b/x/ccv/provider/types/params_test.go @@ -57,15 +57,6 @@ func TestValidateParams(t *testing.T) { {"invalid consumer reward denom registration fee amount", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(-10000000)}, 1000), false}, - {"0 blocks per epoch", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, - time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.00", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 0), false}, - {"exceeding max blocks per epoch", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, - time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.00", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, types.MaxBlocksPerEpoch+1), false}, - {"valid blocks per epoch", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, - time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.00", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, types.MaxBlocksPerEpoch), true}, } for _, tc := range testCases {