From 0fcb57bffd03e0f598d3fd5e5bf860dcf4fc0e96 Mon Sep 17 00:00:00 2001 From: Jehan Tremback Date: Sun, 12 May 2024 21:55:54 -0700 Subject: [PATCH 01/28] rough draft of one method of sending a reduced active set to the provider's consensus --- x/ccv/provider/keeper/relay.go | 33 +++++++++++++++++++++++++++++++++ x/ccv/provider/module.go | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 230ed0a96a..518d7dcc1b 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -12,6 +12,8 @@ import ( 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/x/ccv/provider/types" providertypes "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ccv "github.com/cosmos/interchain-security/v4/x/ccv/types" ) @@ -256,6 +258,37 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { k.IncrementValidatorSetUpdateId(ctx) } +func (k Keeper) ProviderValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate { + // get the bonded validators from the staking module + bondedValidators := k.stakingKeeper.GetLastValidators(ctx) + + // get the last validator set sent to consensus + currentValidators := k.GetConsumerValSet(ctx, "cosmoshub-4") // TODO: This is DEFINITELY not safe for production, need a dedicated function for the provider valset + + MAX_CONSENSUS_VALIDATORS := 180 // TODO: make this a parameter + + nextValidators := []types.ConsumerValidator{} + for _, val := range bondedValidators[:MAX_CONSENSUS_VALIDATORS] { + nextValidator, err := k.CreateConsumerValidator(ctx, "cosmoshub-4", val) + if err != nil { + // this should never happen but is recoverable if we exclude this validator from the next validator set + k.Logger(ctx).Error("could not create consumer validator", + "validator", val.GetOperator().String(), + "error", err) + continue + } + + nextValidators = append(nextValidators, nextValidator) + } + + // save the next validator set + k.SetConsumerValSet(ctx, "cosmoshub-4", nextValidators) + + valUpdates := DiffValidators(currentValidators, nextValidators) + + return valUpdates +} + // BeginBlockCIS contains the BeginBlock logic needed for the Consumer Initiated Slashing sub-protocol. func (k Keeper) BeginBlockCIS(ctx sdk.Context) { // Replenish slash meter if necessary. This ensures the meter value is replenished before handling any slash packets, diff --git a/x/ccv/provider/module.go b/x/ccv/provider/module.go index 6b616f0de8..723d1080e8 100644 --- a/x/ccv/provider/module.go +++ b/x/ccv/provider/module.go @@ -164,7 +164,7 @@ func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.V // EndBlock logic needed for the Validator Set Update sub-protocol am.keeper.EndBlockVSU(ctx) - return []abci.ValidatorUpdate{} + return am.keeper.ProviderValidatorUpdates(ctx) } // AppModuleSimulation functions From c0d8581b522a7b5ef69f0e81de218490c3bba1f8 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Tue, 14 May 2024 09:55:24 +0200 Subject: [PATCH 02/28] Add key for last consensus validator set on the provider --- x/ccv/provider/keeper/provider_consensus.go | 84 +++++++++++++++++++++ x/ccv/provider/keeper/relay.go | 2 +- x/ccv/provider/types/keys.go | 10 +++ x/ccv/provider/types/keys_test.go | 1 + 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 x/ccv/provider/keeper/provider_consensus.go diff --git a/x/ccv/provider/keeper/provider_consensus.go b/x/ccv/provider/keeper/provider_consensus.go new file mode 100644 index 0000000000..4ef7ccbc09 --- /dev/null +++ b/x/ccv/provider/keeper/provider_consensus.go @@ -0,0 +1,84 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" +) + +// SetLastProviderConsensusValidator sets the given validator to be stored +// as part of the last provider consensus validator set +func (k Keeper) SetLastProviderConsensusValidator( + ctx sdk.Context, + 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.LastProviderConsensusValidatorKey(validator.ProviderConsAddr), bz) +} + +// SetLastProviderConsensusValSet resets the stored last validator set sent to the consensus engine on the provider +// to the provided nextValidators. +func (k Keeper) SetLastProviderConsensusValSet(ctx sdk.Context, nextValidators []types.ConsumerValidator) { + k.DeleteLastProviderConsensusValSet(ctx) + for _, val := range nextValidators { + k.SetLastProviderConsensusValidator(ctx, val) + } +} + +// DeleteLastProviderConsensusValidator removes the validator with `providerConsAddr` address +// from the stored last provider consensus validator set +func (k Keeper) DeleteLastProviderConsensusValidator( + ctx sdk.Context, + providerConsAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.LastProviderConsensusValidatorKey(providerConsAddr.ToSdkConsAddr())) +} + +// DeleteLastProviderConsensusValSet deletes all the stored validators from the +// last provider consensus validator set +func (k Keeper) DeleteLastProviderConsensusValSet( + ctx sdk.Context, +) { + store := ctx.KVStore(k.storeKey) + key := []byte{types.LastProviderConsensusValsPrefix} + 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) + } +} + +// GetLastProviderConsensusValSet returns the last stored +// validator set sent to the consensus engine on the provider +func (k Keeper) GetLastProviderConsensusValSet( + ctx sdk.Context, +) (validators []types.ConsumerValidator) { + store := ctx.KVStore(k.storeKey) + key := []byte{types.LastProviderConsensusValsPrefix} + 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 +} diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 518d7dcc1b..b29a90413b 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -281,7 +281,7 @@ func (k Keeper) ProviderValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate nextValidators = append(nextValidators, nextValidator) } - // save the next validator set + // store the validator set we will send to consensus k.SetConsumerValSet(ctx, "cosmoshub-4", nextValidators) valUpdates := DiffValidators(currentValidators, nextValidators) diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 14521b9289..ac0729a669 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -183,6 +183,10 @@ const ( // per validator per consumer chain ConsumerCommissionRatePrefix + // LastProviderConsensusValsPrefix is byte prefix for storing the last validator set + // sent to the consensus engine of the provider chain + LastProviderConsensusValsPrefix + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -601,6 +605,12 @@ func ConsumerCommissionRateKey(chainID string, providerAddr ProviderConsAddress) ) } +// LastProviderConsensusValidatorKey returns the key of the validator with `providerAddr` +// in the last validator set sent to the consensus engine of the provider chain +func LastProviderConsensusValidatorKey(providerAddr []byte) []byte { + return append([]byte{LastProviderConsensusValsPrefix}, 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 eac11a0993..7f6e90a809 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -61,6 +61,7 @@ func getAllKeyPrefixes() []byte { providertypes.TopNBytePrefix, providertypes.ConsumerRewardsAllocationBytePrefix, providertypes.ConsumerCommissionRatePrefix, + providertypes.LastProviderConsensusValsPrefix, } } From b88cfb5885ffa960d7fbdd3d0475ae89f9050827 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Tue, 14 May 2024 10:47:22 +0200 Subject: [PATCH 03/28] Refactor validator set storage to reduce duplication --- x/ccv/provider/keeper/provider_consensus.go | 49 ++------- .../keeper/provider_consensus_test.go | 102 ++++++++++++++++++ .../provider/keeper/validator_set_storage.go | 94 ++++++++++++++++ x/ccv/provider/keeper/validator_set_update.go | 54 ++-------- 4 files changed, 213 insertions(+), 86 deletions(-) create mode 100644 x/ccv/provider/keeper/provider_consensus_test.go create mode 100644 x/ccv/provider/keeper/validator_set_storage.go diff --git a/x/ccv/provider/keeper/provider_consensus.go b/x/ccv/provider/keeper/provider_consensus.go index 4ef7ccbc09..8f2f494975 100644 --- a/x/ccv/provider/keeper/provider_consensus.go +++ b/x/ccv/provider/keeper/provider_consensus.go @@ -1,8 +1,6 @@ package keeper import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" @@ -14,22 +12,13 @@ func (k Keeper) SetLastProviderConsensusValidator( ctx sdk.Context, 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.LastProviderConsensusValidatorKey(validator.ProviderConsAddr), bz) + k.setValidator(ctx, []byte{types.LastProviderConsensusValsPrefix}, validator) } // SetLastProviderConsensusValSet resets the stored last validator set sent to the consensus engine on the provider // to the provided nextValidators. func (k Keeper) SetLastProviderConsensusValSet(ctx sdk.Context, nextValidators []types.ConsumerValidator) { - k.DeleteLastProviderConsensusValSet(ctx) - for _, val := range nextValidators { - k.SetLastProviderConsensusValidator(ctx, val) - } + k.setValSet(ctx, []byte{types.LastProviderConsensusValsPrefix}, nextValidators) } // DeleteLastProviderConsensusValidator removes the validator with `providerConsAddr` address @@ -38,8 +27,7 @@ func (k Keeper) DeleteLastProviderConsensusValidator( ctx sdk.Context, providerConsAddr types.ProviderConsAddress, ) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.LastProviderConsensusValidatorKey(providerConsAddr.ToSdkConsAddr())) + k.deleteValidator(ctx, []byte{types.LastProviderConsensusValsPrefix}, providerConsAddr) } // DeleteLastProviderConsensusValSet deletes all the stored validators from the @@ -47,38 +35,13 @@ func (k Keeper) DeleteLastProviderConsensusValidator( func (k Keeper) DeleteLastProviderConsensusValSet( ctx sdk.Context, ) { - store := ctx.KVStore(k.storeKey) - key := []byte{types.LastProviderConsensusValsPrefix} - 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) - } + k.deleteValSet(ctx, []byte{types.LastProviderConsensusValsPrefix}) } // GetLastProviderConsensusValSet returns the last stored // validator set sent to the consensus engine on the provider func (k Keeper) GetLastProviderConsensusValSet( ctx sdk.Context, -) (validators []types.ConsumerValidator) { - store := ctx.KVStore(k.storeKey) - key := []byte{types.LastProviderConsensusValsPrefix} - 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 +) []types.ConsumerValidator { + return k.getValSet(ctx, []byte{types.LastProviderConsensusValsPrefix}) } diff --git a/x/ccv/provider/keeper/provider_consensus_test.go b/x/ccv/provider/keeper/provider_consensus_test.go new file mode 100644 index 0000000000..9f9cd4e3f4 --- /dev/null +++ b/x/ccv/provider/keeper/provider_consensus_test.go @@ -0,0 +1,102 @@ +package keeper_test + +import ( + "testing" + + "github.com/cometbft/cometbft/proto/tendermint/crypto" + "github.com/stretchr/testify/require" + + testkeeper "github.com/cosmos/interchain-security/v4/testutil/keeper" + "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" +) + +func TestSetLastProviderConsensusValidator(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{}, + } + + providerKeeper.SetLastProviderConsensusValidator(ctx, validator) + + // Retrieve the stored validator + storedValidator := providerKeeper.GetLastProviderConsensusValSet(ctx)[0] + + require.Equal(t, validator, storedValidator, "stored validator does not match") +} + +func TestSetLastProviderConsensusValSet(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + validator1 := types.ConsumerValidator{ + ProviderConsAddr: []byte("providerConsAddr1"), + Power: 2, + ConsumerPublicKey: &crypto.PublicKey{}, + } + + validator2 := types.ConsumerValidator{ + ProviderConsAddr: []byte("providerConsAddr2"), + Power: 3, + ConsumerPublicKey: &crypto.PublicKey{}, + } + + nextValidators := []types.ConsumerValidator{validator1, validator2} + + providerKeeper.SetLastProviderConsensusValSet(ctx, nextValidators) + + // Retrieve the stored validator set + storedValidators := providerKeeper.GetLastProviderConsensusValSet(ctx) + require.Equal(t, nextValidators, storedValidators, "stored validator set does not match") +} + +func TestDeleteLastProviderConsensusValidator(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{}, + } + + providerKeeper.SetLastProviderConsensusValidator(ctx, validator) + + // Delete the stored validator + providerKeeper.DeleteLastProviderConsensusValidator(ctx, types.NewProviderConsAddress(validator.ProviderConsAddr)) + + // Ensure the validator is deleted + storedValidators := providerKeeper.GetLastProviderConsensusValSet(ctx) + require.Empty(t, storedValidators, "validator set should be empty") +} + +func TestDeleteLastProviderConsensusValSet(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + validator1 := types.ConsumerValidator{ + ProviderConsAddr: []byte("providerConsAddr1"), + Power: 2, + ConsumerPublicKey: &crypto.PublicKey{}, + } + + validator2 := types.ConsumerValidator{ + ProviderConsAddr: []byte("providerConsAddr2"), + Power: 3, + ConsumerPublicKey: &crypto.PublicKey{}, + } + + nextValidators := []types.ConsumerValidator{validator1, validator2} + + providerKeeper.SetLastProviderConsensusValSet(ctx, nextValidators) + + // Delete the stored validator set + providerKeeper.DeleteLastProviderConsensusValSet(ctx) + + // Ensure the validator set is empty + storedValidators := providerKeeper.GetLastProviderConsensusValSet(ctx) + require.Empty(t, storedValidators, "validator set should be empty") +} diff --git a/x/ccv/provider/keeper/validator_set_storage.go b/x/ccv/provider/keeper/validator_set_storage.go new file mode 100644 index 0000000000..65f86a23c5 --- /dev/null +++ b/x/ccv/provider/keeper/validator_set_storage.go @@ -0,0 +1,94 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" +) + +// getValidatorKey constructs the key to access a given validator, stored under a given prefix. +func (k Keeper) getValidatorKey(prefix []byte, providerAddr types.ProviderConsAddress) []byte { + return append(prefix, providerAddr.ToSdkConsAddr()...) +} + +// setValidator stores the given `validator` in the validator set stored under the given prefix. +func (k Keeper) setValidator( + ctx sdk.Context, + prefix []byte, + 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(k.getValidatorKey(prefix, types.NewProviderConsAddress(validator.ProviderConsAddr)), bz) +} + +// setValSet resets the validator set stored under the given prefix to the provided `nextValidators`. +func (k Keeper) setValSet(ctx sdk.Context, prefix []byte, nextValidators []types.ConsumerValidator) { + k.deleteValSet(ctx, prefix) + for _, val := range nextValidators { + k.setValidator(ctx, prefix, val) + } +} + +// deleteValidator removes validator with `providerAddr` address from the +// validator set stored under the given prefix. +func (k Keeper) deleteValidator( + ctx sdk.Context, + prefix []byte, + providerConsAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(k.getValidatorKey(prefix, providerConsAddr)) +} + +// deleteValSet deletes all the stored consumer validators under the given prefix. +func (k Keeper) deleteValSet( + ctx sdk.Context, + prefix []byte, +) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, prefix) + + var keysToDel [][]byte + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + keysToDel = append(keysToDel, iterator.Key()) + } + for _, delKey := range keysToDel { + store.Delete(delKey) + } +} + +// isValidator returns `true` if the validator with `providerAddr` exists +// in the validator set stored under the given prefix. +func (k Keeper) isValidator(ctx sdk.Context, prefix []byte, providerAddr types.ProviderConsAddress) bool { + store := ctx.KVStore(k.storeKey) + return store.Get(k.getValidatorKey(prefix, providerAddr)) != nil +} + +// getValSet returns all the validators stored under the given prefix. +func (k Keeper) getValSet( + ctx sdk.Context, + key []byte, +) (validators []types.ConsumerValidator) { + store := ctx.KVStore(k.storeKey) + 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 +} diff --git a/x/ccv/provider/keeper/validator_set_update.go b/x/ccv/provider/keeper/validator_set_update.go index 264fdd67f2..3311856b65 100644 --- a/x/ccv/provider/keeper/validator_set_update.go +++ b/x/ccv/provider/keeper/validator_set_update.go @@ -11,28 +11,23 @@ import ( "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ) +func (k Keeper) GetConsumerChainKey(ctx sdk.Context, chainID string) []byte { + return types.ChainIdWithLenKey(types.ConsumerValidatorBytePrefix, chainID) +} + // 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) + k.setValidator(ctx, k.GetConsumerChainKey(ctx, chainID), validator) } // SetConsumerValSet resets the current consumer validators with the `nextValidators` computed by // `FilterValidators` and hence this method should only be called after `FilterValidators` 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) - } + k.setValSet(ctx, k.GetConsumerChainKey(ctx, chainID), nextValidators) } // DeleteConsumerValidator removes consumer validator with `providerAddr` address @@ -41,8 +36,7 @@ func (k Keeper) DeleteConsumerValidator( chainID string, providerConsAddr types.ProviderConsAddress, ) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.ConsumerValidatorKey(chainID, providerConsAddr.ToSdkConsAddr())) + k.deleteValidator(ctx, k.GetConsumerChainKey(ctx, chainID), providerConsAddr) } // DeleteConsumerValSet deletes all the stored consumer validators for chain `chainID` @@ -50,47 +44,21 @@ 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) - } + k.deleteValSet(ctx, k.GetConsumerChainKey(ctx, chainID)) } // 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 + return k.isValidator(ctx, k.GetConsumerChainKey(ctx, chainID), providerAddr) } // 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 +) []types.ConsumerValidator { + return k.getValSet(ctx, k.GetConsumerChainKey(ctx, chainID)) } // DiffValidators compares the current and the next epoch's consumer validators and returns the `ValidatorUpdate` diff From 2e5a91a3b4f93e8a2eee80b711be2b570b968d4b Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Tue, 14 May 2024 13:40:39 +0200 Subject: [PATCH 04/28] Add draft ADR for active set validators --- .../adr-017-allowing-inactive-validators.md | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 docs/docs/adrs/adr-017-allowing-inactive-validators.md diff --git a/docs/docs/adrs/adr-017-allowing-inactive-validators.md b/docs/docs/adrs/adr-017-allowing-inactive-validators.md new file mode 100644 index 0000000000..53cd69f859 --- /dev/null +++ b/docs/docs/adrs/adr-017-allowing-inactive-validators.md @@ -0,0 +1,67 @@ +--- +sidebar_position: 18 +title: ADR Template +--- +# ADR [17]: [Allowing validators outside the active set to validate on consumer chains] + +## Changelog +* [date]: [changelog] + +## Status + +Proposed + +## Context + +Currently, only validators in the active set on the provider can validate on consumer chains. +This limits the number of validators that can validate on consumer chains. Validators outside of the active set might be willing +to validate on consumer chains, but we might not want to make the provider validator set larger, e.g. to not put more strain on the consensus engine. +This runs the risk of leaving consumer chains with too few validators. + +The purpose of this ADR is to allow validators that are *not* part of the consensus process on the provider chain (because they are inactive) +to validate on consumer chains. + +## Decision + +The proposed solution is to: +* Increase the validator set size in the staking module to a very large number (e.g. 500) +* Introduce an additional parameter in the provider module, called MaxProviderConsensusValidators +* When the validator updates are passed to the consensus engine of the provider during EndBlock, the provider module will simply only pass the first MaxProviderConsensusValidators validators by voting power to the consensus engine + +To facilitate this, the provider module will need to: +* keep the last validator set sent to the consensus engine +* load the current validator set from the staking module +* diff the two sets, and send the diff to the consensus engine + +Extra considerations: +* Migration: In the migration, the last consensus validator set would just be sent to the last active validator set from the view of the staking module. Existing consumer chains need to be migrated to have a validator set size cap (otherwise, they could end up with a huge validator set including all the staking-but-not-consensus-active validators from the provider chain) +* Slashing: Validators that are not part of the active set on the provider chain can still be slashed on the consumer chain, but they *should not* be slashed for downtime on the provider chain. Will those validators accrue missed blocks? If yes, we probably need to make changes in the slashing module to not continuously slash them for downtime on the provider +* Rewards: Validators that are not part of the active set on the provider chain can still receive rewards on the consumer chain, but they *should not* receive rewards on the provider chain. +* Where else might the staking module validators be used? We need to carefully assess whether we need to change these references and direct them to the "actual active set" of the provider chain instead, or whether they can still go to the staking module + +Comms: +* This change needs to be communicated, in particular to frontends. They need to be aware that there is a difference between "active validator on the provider chain", and "part of the consensus validator set on the provider chain". + +## Consequences + +### Positive + +* Validators outside of the active set can validate on consumer chains without having an impact on the consensus engine of the provider chain +* We can do this change without further forking from the standard Cosmos SDK modules + +### Negative + +* We need to be very careful in making sure all existing references to the validator set are updated to refer to the "actual active set" of the provider chain if necessary +* In the worst case, this might mean we need many changes to existing Cosmos SDK modules (and this would negate one of the positives of this solution) + +### Neutral + +## Alternative considerations + +We could instead adapt the *staking module* with a similar change. +This might be better if it turns out that the staking module active set is used in many other places. + +## References + +* [adr-016-securityaggregation.md] has similar concerns where the staking validator set will differ from the consensus validator set +* From 2cddff5d7852b013b9d22670113664eb0bc98e16 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Tue, 14 May 2024 13:44:32 +0200 Subject: [PATCH 05/28] Revert "Add draft ADR for active set validators" This reverts commit 2e5a91a3b4f93e8a2eee80b711be2b570b968d4b. --- .../adr-017-allowing-inactive-validators.md | 67 ------------------- 1 file changed, 67 deletions(-) delete mode 100644 docs/docs/adrs/adr-017-allowing-inactive-validators.md diff --git a/docs/docs/adrs/adr-017-allowing-inactive-validators.md b/docs/docs/adrs/adr-017-allowing-inactive-validators.md deleted file mode 100644 index 53cd69f859..0000000000 --- a/docs/docs/adrs/adr-017-allowing-inactive-validators.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -sidebar_position: 18 -title: ADR Template ---- -# ADR [17]: [Allowing validators outside the active set to validate on consumer chains] - -## Changelog -* [date]: [changelog] - -## Status - -Proposed - -## Context - -Currently, only validators in the active set on the provider can validate on consumer chains. -This limits the number of validators that can validate on consumer chains. Validators outside of the active set might be willing -to validate on consumer chains, but we might not want to make the provider validator set larger, e.g. to not put more strain on the consensus engine. -This runs the risk of leaving consumer chains with too few validators. - -The purpose of this ADR is to allow validators that are *not* part of the consensus process on the provider chain (because they are inactive) -to validate on consumer chains. - -## Decision - -The proposed solution is to: -* Increase the validator set size in the staking module to a very large number (e.g. 500) -* Introduce an additional parameter in the provider module, called MaxProviderConsensusValidators -* When the validator updates are passed to the consensus engine of the provider during EndBlock, the provider module will simply only pass the first MaxProviderConsensusValidators validators by voting power to the consensus engine - -To facilitate this, the provider module will need to: -* keep the last validator set sent to the consensus engine -* load the current validator set from the staking module -* diff the two sets, and send the diff to the consensus engine - -Extra considerations: -* Migration: In the migration, the last consensus validator set would just be sent to the last active validator set from the view of the staking module. Existing consumer chains need to be migrated to have a validator set size cap (otherwise, they could end up with a huge validator set including all the staking-but-not-consensus-active validators from the provider chain) -* Slashing: Validators that are not part of the active set on the provider chain can still be slashed on the consumer chain, but they *should not* be slashed for downtime on the provider chain. Will those validators accrue missed blocks? If yes, we probably need to make changes in the slashing module to not continuously slash them for downtime on the provider -* Rewards: Validators that are not part of the active set on the provider chain can still receive rewards on the consumer chain, but they *should not* receive rewards on the provider chain. -* Where else might the staking module validators be used? We need to carefully assess whether we need to change these references and direct them to the "actual active set" of the provider chain instead, or whether they can still go to the staking module - -Comms: -* This change needs to be communicated, in particular to frontends. They need to be aware that there is a difference between "active validator on the provider chain", and "part of the consensus validator set on the provider chain". - -## Consequences - -### Positive - -* Validators outside of the active set can validate on consumer chains without having an impact on the consensus engine of the provider chain -* We can do this change without further forking from the standard Cosmos SDK modules - -### Negative - -* We need to be very careful in making sure all existing references to the validator set are updated to refer to the "actual active set" of the provider chain if necessary -* In the worst case, this might mean we need many changes to existing Cosmos SDK modules (and this would negate one of the positives of this solution) - -### Neutral - -## Alternative considerations - -We could instead adapt the *staking module* with a similar change. -This might be better if it turns out that the staking module active set is used in many other places. - -## References - -* [adr-016-securityaggregation.md] has similar concerns where the staking validator set will differ from the consensus validator set -* From acdf06a30f1e50852b8bcb755c7fff2cac3ba791 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Tue, 14 May 2024 13:45:09 +0200 Subject: [PATCH 06/28] Add MaxProviderConsensusValidators param --- .../ccv/provider/v1/provider.proto | 4 + x/ccv/provider/keeper/params.go | 7 + x/ccv/provider/keeper/relay.go | 20 +- x/ccv/provider/types/params.go | 9 + x/ccv/provider/types/provider.pb.go | 279 ++++++++++-------- 5 files changed, 195 insertions(+), 124 deletions(-) diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index 139cc9d25f..3e83948fc4 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -212,6 +212,10 @@ message Params { // The number of blocks that comprise an epoch. int64 blocks_per_epoch = 10; + + // The maximal number of validators that will be passed + // to the consensus engine on the provider. + int64 max_provider_consensus_validators = 11; } // SlashAcks contains cons addresses of consumer chain validators diff --git a/x/ccv/provider/keeper/params.go b/x/ccv/provider/keeper/params.go index f74baf656e..610561413d 100644 --- a/x/ccv/provider/keeper/params.go +++ b/x/ccv/provider/keeper/params.go @@ -85,6 +85,12 @@ func (k Keeper) GetBlocksPerEpoch(ctx sdk.Context) int64 { return b } +func (k Keeper) GetMaxProviderConsensusValidators(ctx sdk.Context) int64 { + var m int64 + k.paramSpace.Get(ctx, types.KeyMaxProviderConsensusValidators, &m) + return m +} + // GetParams returns the paramset for the provider module func (k Keeper) GetParams(ctx sdk.Context) types.Params { return types.NewParams( @@ -97,6 +103,7 @@ func (k Keeper) GetParams(ctx sdk.Context) types.Params { k.GetSlashMeterReplenishFraction(ctx), k.GetConsumerRewardDenomRegistrationFee(ctx), k.GetBlocksPerEpoch(ctx), + k.GetMaxProviderConsensusValidators(ctx), ) } diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index b29a90413b..cab1c1cbb7 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -263,26 +263,38 @@ func (k Keeper) ProviderValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate bondedValidators := k.stakingKeeper.GetLastValidators(ctx) // get the last validator set sent to consensus - currentValidators := k.GetConsumerValSet(ctx, "cosmoshub-4") // TODO: This is DEFINITELY not safe for production, need a dedicated function for the provider valset + currentValidators := k.GetLastProviderConsensusValSet(ctx) MAX_CONSENSUS_VALIDATORS := 180 // TODO: make this a parameter nextValidators := []types.ConsumerValidator{} for _, val := range bondedValidators[:MAX_CONSENSUS_VALIDATORS] { - nextValidator, err := k.CreateConsumerValidator(ctx, "cosmoshub-4", val) + // create the validator from the staking validator + consAddr, err := val.GetConsAddr() if err != nil { - // this should never happen but is recoverable if we exclude this validator from the next validator set k.Logger(ctx).Error("could not create consumer validator", "validator", val.GetOperator().String(), "error", err) continue } + pubKey, err := val.TmConsPublicKey() + if err != nil { + k.Logger(ctx).Error("could not create consumer validator", + "validator", val.GetOperator().String(), + "error", err) + continue + } + nextValidator := types.ConsumerValidator{ + ProviderConsAddr: consAddr, + ConsumerPublicKey: &pubKey, + Power: k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()), + } nextValidators = append(nextValidators, nextValidator) } // store the validator set we will send to consensus - k.SetConsumerValSet(ctx, "cosmoshub-4", nextValidators) + k.SetLastProviderConsensusValSet(ctx, nextValidators) valUpdates := DiffValidators(currentValidators, nextValidators) diff --git a/x/ccv/provider/types/params.go b/x/ccv/provider/types/params.go index a0a7a5ed7a..664cf3db94 100644 --- a/x/ccv/provider/types/params.go +++ b/x/ccv/provider/types/params.go @@ -41,6 +41,10 @@ const ( // an epoch corresponds to 1 hour (6 * 600 = 3600 seconds). // forcing int64 as the Params KeyTable expects an int64 and not int. DefaultBlocksPerEpoch = int64(600) + + // DefaultMaxProviderConsensusValidators is the default maximum number of validators that will + // be passed on from the staking module to the consensus engine on the provider. + DefaultMaxProviderConsensusValidators = 180 ) // Reflection based keys for params subspace @@ -53,6 +57,7 @@ var ( KeySlashMeterReplenishFraction = []byte("SlashMeterReplenishFraction") KeyConsumerRewardDenomRegistrationFee = []byte("ConsumerRewardDenomRegistrationFee") KeyBlocksPerEpoch = []byte("BlocksPerEpoch") + KeyMaxProviderConsensusValidators = []byte("MaxProviderConsensusValidators") ) // ParamKeyTable returns a key table with the necessary registered provider params @@ -71,6 +76,7 @@ func NewParams( slashMeterReplenishFraction string, consumerRewardDenomRegistrationFee sdk.Coin, blocksPerEpoch int64, + maxProviderConsensusValidators int64, ) Params { return Params{ TemplateClient: cs, @@ -82,6 +88,7 @@ func NewParams( SlashMeterReplenishFraction: slashMeterReplenishFraction, ConsumerRewardDenomRegistrationFee: consumerRewardDenomRegistrationFee, BlocksPerEpoch: blocksPerEpoch, + MaxProviderConsensusValidators: maxProviderConsensusValidators, } } @@ -113,6 +120,7 @@ func DefaultParams() Params { Amount: sdk.NewInt(10000000), }, DefaultBlocksPerEpoch, + DefaultMaxProviderConsensusValidators, ) } @@ -163,6 +171,7 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(KeySlashMeterReplenishFraction, p.SlashMeterReplenishFraction, ccvtypes.ValidateStringFraction), paramtypes.NewParamSetPair(KeyConsumerRewardDenomRegistrationFee, p.ConsumerRewardDenomRegistrationFee, ValidateCoin), paramtypes.NewParamSetPair(KeyBlocksPerEpoch, p.BlocksPerEpoch, ccvtypes.ValidatePositiveInt64), + paramtypes.NewParamSetPair(KeyMaxProviderConsensusValidators, p.MaxProviderConsensusValidators, ccvtypes.ValidatePositiveInt64), } } diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index 4f0a2fd605..ee69d3d52a 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -474,6 +474,9 @@ type Params struct { 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"` + // The maximal number of validators that will be passed + // to the consensus engine on the provider. + MaxProviderConsensusValidators int64 `protobuf:"varint,11,opt,name=max_provider_consensus_validators,json=maxProviderConsensusValidators,proto3" json:"max_provider_consensus_validators,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -572,6 +575,13 @@ func (m *Params) GetBlocksPerEpoch() int64 { return 0 } +func (m *Params) GetMaxProviderConsensusValidators() int64 { + if m != nil { + return m.MaxProviderConsensusValidators + } + return 0 +} + // SlashAcks contains cons addresses of consumer chain validators // successfully slashed on the provider chain. type SlashAcks struct { @@ -1559,127 +1569,129 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1919 bytes of a gzipped FileDescriptorProto + // 1952 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4f, 0x6f, 0x1b, 0xc7, - 0x15, 0xd7, 0x8a, 0x94, 0x45, 0x0e, 0xf5, 0x77, 0xa4, 0xc4, 0x2b, 0x55, 0xa5, 0xe8, 0x4d, 0x93, - 0xaa, 0x71, 0xbd, 0x1b, 0x29, 0x2d, 0x60, 0x18, 0x0d, 0x02, 0x89, 0x72, 0x62, 0x59, 0x89, 0xcd, - 0xac, 0x54, 0x19, 0x6d, 0x0f, 0x8b, 0xe1, 0xec, 0x98, 0x1c, 0x68, 0xb9, 0xb3, 0x9e, 0x19, 0xae, - 0xc2, 0x4b, 0xcf, 0x3d, 0xb4, 0x40, 0x7a, 0x0b, 0x7a, 0x69, 0x5a, 0xa0, 0x40, 0xd1, 0x4b, 0xfb, - 0x31, 0x72, 0xcc, 0xb1, 0xa7, 0xa4, 0xb0, 0x0f, 0x3d, 0xf4, 0x4b, 0x14, 0x33, 0xfb, 0x97, 0x94, - 0xe4, 0xd2, 0x48, 0x73, 0x91, 0x76, 0xdf, 0xbc, 0xf7, 0x7b, 0x6f, 0xe6, 0xbd, 0x79, 0xbf, 0xc7, - 0x05, 0x7b, 0x34, 0x94, 0x84, 0xe3, 0x3e, 0xa2, 0xa1, 0x27, 0x08, 0x1e, 0x72, 0x2a, 0x47, 0x0e, - 0xc6, 0xb1, 0x13, 0x71, 0x16, 0x53, 0x9f, 0x70, 0x27, 0xde, 0xcd, 0x9f, 0xed, 0x88, 0x33, 0xc9, - 0xe0, 0x1b, 0x57, 0xd8, 0xd8, 0x18, 0xc7, 0x76, 0xae, 0x17, 0xef, 0x6e, 0xbe, 0x79, 0x1d, 0x70, - 0xbc, 0xeb, 0x5c, 0x50, 0x4e, 0x12, 0xac, 0xcd, 0xf5, 0x1e, 0xeb, 0x31, 0xfd, 0xe8, 0xa8, 0xa7, - 0x54, 0xba, 0xdd, 0x63, 0xac, 0x17, 0x10, 0x47, 0xbf, 0x75, 0x87, 0x4f, 0x1d, 0x49, 0x07, 0x44, - 0x48, 0x34, 0x88, 0x52, 0x85, 0xe6, 0xa4, 0x82, 0x3f, 0xe4, 0x48, 0x52, 0x16, 0x66, 0x00, 0xb4, - 0x8b, 0x1d, 0xcc, 0x38, 0x71, 0x70, 0x40, 0x49, 0x28, 0x95, 0xd7, 0xe4, 0x29, 0x55, 0x70, 0x94, - 0x42, 0x40, 0x7b, 0x7d, 0x99, 0x88, 0x85, 0x23, 0x49, 0xe8, 0x13, 0x3e, 0xa0, 0x89, 0x72, 0xf1, - 0x96, 0x1a, 0x6c, 0x95, 0xd6, 0x31, 0x1f, 0x45, 0x92, 0x39, 0xe7, 0x64, 0x24, 0xd2, 0xd5, 0xb7, - 0x30, 0x13, 0x03, 0x26, 0x1c, 0xa2, 0xf6, 0x1f, 0x62, 0xe2, 0xc4, 0xbb, 0x5d, 0x22, 0xd1, 0x6e, - 0x2e, 0xc8, 0xe2, 0x4e, 0xf5, 0xba, 0x48, 0x14, 0x3a, 0x98, 0xd1, 0x2c, 0xee, 0x55, 0x34, 0xa0, - 0x21, 0x73, 0xf4, 0xdf, 0x44, 0x64, 0xfd, 0xb6, 0x06, 0xcc, 0x36, 0x0b, 0xc5, 0x70, 0x40, 0xf8, - 0xbe, 0xef, 0x53, 0xb5, 0xcb, 0x0e, 0x67, 0x11, 0x13, 0x28, 0x80, 0xeb, 0x60, 0x4e, 0x52, 0x19, - 0x10, 0xd3, 0x68, 0x19, 0x3b, 0x75, 0x37, 0x79, 0x81, 0x2d, 0xd0, 0xf0, 0x89, 0xc0, 0x9c, 0x46, - 0x4a, 0xd9, 0x9c, 0xd5, 0x6b, 0x65, 0x11, 0xdc, 0x00, 0xb5, 0x24, 0x35, 0xd4, 0x37, 0x2b, 0x7a, - 0x79, 0x5e, 0xbf, 0x1f, 0xf9, 0xf0, 0x43, 0xb0, 0x44, 0x43, 0x2a, 0x29, 0x0a, 0xbc, 0x3e, 0x51, - 0x07, 0x64, 0x56, 0x5b, 0xc6, 0x4e, 0x63, 0x6f, 0xd3, 0xa6, 0x5d, 0x6c, 0xab, 0x33, 0xb5, 0xd3, - 0x93, 0x8c, 0x77, 0xed, 0x07, 0x5a, 0xe3, 0xa0, 0xfa, 0xe5, 0xd7, 0xdb, 0x33, 0xee, 0x62, 0x6a, - 0x97, 0x08, 0xe1, 0x2d, 0xb0, 0xd0, 0x23, 0x21, 0x11, 0x54, 0x78, 0x7d, 0x24, 0xfa, 0xe6, 0x5c, - 0xcb, 0xd8, 0x59, 0x70, 0x1b, 0xa9, 0xec, 0x01, 0x12, 0x7d, 0xb8, 0x0d, 0x1a, 0x5d, 0x1a, 0x22, - 0x3e, 0x4a, 0x34, 0x6e, 0x68, 0x0d, 0x90, 0x88, 0xb4, 0x42, 0x1b, 0x00, 0x11, 0xa1, 0x8b, 0xd0, - 0x53, 0x05, 0x60, 0xce, 0xa7, 0x81, 0x24, 0xc9, 0xb7, 0xb3, 0xe4, 0xdb, 0xa7, 0x59, 0x75, 0x1c, - 0xd4, 0x54, 0x20, 0x9f, 0x7d, 0xb3, 0x6d, 0xb8, 0x75, 0x6d, 0xa7, 0x56, 0xe0, 0x23, 0xb0, 0x32, - 0x0c, 0xbb, 0x2c, 0xf4, 0x69, 0xd8, 0xf3, 0x22, 0xc2, 0x29, 0xf3, 0xcd, 0x9a, 0x86, 0xda, 0xb8, - 0x04, 0x75, 0x98, 0xd6, 0x51, 0x82, 0xf4, 0xb9, 0x42, 0x5a, 0xce, 0x8d, 0x3b, 0xda, 0x16, 0x7e, - 0x02, 0x20, 0xc6, 0xb1, 0x0e, 0x89, 0x0d, 0x65, 0x86, 0x58, 0x9f, 0x1e, 0x71, 0x05, 0xe3, 0xf8, - 0x34, 0xb1, 0x4e, 0x21, 0x7f, 0x05, 0x6e, 0x4a, 0x8e, 0x42, 0xf1, 0x94, 0xf0, 0x49, 0x5c, 0x30, - 0x3d, 0xee, 0x6b, 0x19, 0xc6, 0x38, 0xf8, 0x03, 0xd0, 0xc2, 0x69, 0x01, 0x79, 0x9c, 0xf8, 0x54, - 0x48, 0x4e, 0xbb, 0x43, 0x65, 0xeb, 0x3d, 0xe5, 0x08, 0xeb, 0x1a, 0x69, 0xe8, 0x22, 0x68, 0x66, - 0x7a, 0xee, 0x98, 0xda, 0x07, 0xa9, 0x16, 0x7c, 0x0c, 0x7e, 0xd0, 0x0d, 0x18, 0x3e, 0x17, 0x2a, - 0x38, 0x6f, 0x0c, 0x49, 0xbb, 0x1e, 0x50, 0x21, 0x14, 0xda, 0x42, 0xcb, 0xd8, 0xa9, 0xb8, 0xb7, - 0x12, 0xdd, 0x0e, 0xe1, 0x87, 0x25, 0xcd, 0xd3, 0x92, 0x22, 0xbc, 0x03, 0x60, 0x9f, 0x0a, 0xc9, - 0x38, 0xc5, 0x28, 0xf0, 0x48, 0x28, 0x39, 0x25, 0xc2, 0x5c, 0xd4, 0xe6, 0xab, 0xc5, 0xca, 0xfd, - 0x64, 0x01, 0x3e, 0x04, 0xb7, 0xae, 0x75, 0xea, 0xe1, 0x3e, 0x0a, 0x43, 0x12, 0x98, 0x4b, 0x7a, - 0x2b, 0xdb, 0xfe, 0x35, 0x3e, 0xdb, 0x89, 0x1a, 0x5c, 0x03, 0x73, 0x92, 0x45, 0xde, 0x23, 0x73, - 0xb9, 0x65, 0xec, 0x2c, 0xba, 0x55, 0xc9, 0xa2, 0x47, 0xf0, 0x1d, 0xb0, 0x1e, 0xa3, 0x80, 0xfa, - 0x48, 0x32, 0x2e, 0xbc, 0x88, 0x5d, 0x10, 0xee, 0x61, 0x14, 0x99, 0x2b, 0x5a, 0x07, 0x16, 0x6b, - 0x1d, 0xb5, 0xd4, 0x46, 0x11, 0x7c, 0x1b, 0xac, 0xe6, 0x52, 0x4f, 0x10, 0xa9, 0xd5, 0x57, 0xb5, - 0xfa, 0x72, 0xbe, 0x70, 0x42, 0xa4, 0xd2, 0xdd, 0x02, 0x75, 0x14, 0x04, 0xec, 0x22, 0xa0, 0x42, - 0x9a, 0xb0, 0x55, 0xd9, 0xa9, 0xbb, 0x85, 0x00, 0x6e, 0x82, 0x9a, 0x4f, 0xc2, 0x91, 0x5e, 0x5c, - 0xd3, 0x8b, 0xf9, 0xfb, 0xbd, 0xda, 0x6f, 0xbe, 0xd8, 0x9e, 0xf9, 0xfc, 0x8b, 0xed, 0x19, 0xeb, - 0xef, 0x06, 0xb8, 0xd9, 0xce, 0xb3, 0x34, 0x60, 0x31, 0x0a, 0xbe, 0xcb, 0x6e, 0xb0, 0x0f, 0xea, - 0x42, 0x1d, 0x93, 0xbe, 0x7f, 0xd5, 0x57, 0xb8, 0x7f, 0x35, 0x65, 0xa6, 0x16, 0xac, 0x3f, 0x1a, - 0x60, 0xfd, 0xfe, 0xb3, 0x21, 0x8d, 0x19, 0x46, 0xff, 0x97, 0xe6, 0x75, 0x0c, 0x16, 0x49, 0x09, - 0x4f, 0x98, 0x95, 0x56, 0x65, 0xa7, 0xb1, 0xf7, 0xa6, 0x9d, 0x34, 0x57, 0x3b, 0xef, 0xb9, 0x69, - 0x83, 0xb5, 0xcb, 0xde, 0xdd, 0x71, 0xdb, 0x7b, 0xb3, 0xa6, 0x61, 0xfd, 0xd9, 0x00, 0x9b, 0xaa, - 0x2c, 0x7a, 0xc4, 0x25, 0x17, 0x88, 0xfb, 0x87, 0x24, 0x64, 0x03, 0xf1, 0xad, 0xe3, 0xb4, 0xc0, - 0xa2, 0xaf, 0x91, 0x3c, 0xc9, 0x3c, 0xe4, 0xfb, 0x3a, 0x4e, 0xad, 0xa3, 0x84, 0xa7, 0x6c, 0xdf, - 0xf7, 0xe1, 0x0e, 0x58, 0x29, 0x74, 0xb8, 0xca, 0xa7, 0x3a, 0x66, 0xa5, 0xb6, 0x94, 0xa9, 0xe9, - 0x2c, 0x13, 0xeb, 0x3f, 0x06, 0x58, 0xf9, 0x30, 0x60, 0x5d, 0x14, 0x9c, 0x04, 0x48, 0xf4, 0xd5, - 0x95, 0x18, 0xa9, 0xf4, 0x70, 0x92, 0xf6, 0x22, 0x1d, 0xde, 0xd4, 0xe9, 0x51, 0x66, 0xba, 0x3b, - 0xbe, 0x0f, 0x56, 0xf3, 0xee, 0x90, 0x57, 0x81, 0xde, 0xcd, 0xc1, 0xda, 0xf3, 0xaf, 0xb7, 0x97, - 0xb3, 0x62, 0x6b, 0xeb, 0x8a, 0x38, 0x74, 0x97, 0xf1, 0x98, 0xc0, 0x87, 0x4d, 0xd0, 0xa0, 0x5d, - 0xec, 0x09, 0xf2, 0xcc, 0x0b, 0x87, 0x03, 0x5d, 0x40, 0x55, 0xb7, 0x4e, 0xbb, 0xf8, 0x84, 0x3c, - 0x7b, 0x34, 0x1c, 0xc0, 0x77, 0xc1, 0xeb, 0xd9, 0x60, 0xe0, 0xc5, 0x28, 0xf0, 0x94, 0xbd, 0x3a, - 0x0e, 0xae, 0xeb, 0x69, 0xc1, 0x5d, 0xcb, 0x56, 0xcf, 0x50, 0xa0, 0x9c, 0xed, 0xfb, 0x3e, 0xb7, - 0x5e, 0xcc, 0x81, 0x1b, 0x1d, 0xc4, 0xd1, 0x40, 0xc0, 0x53, 0xb0, 0x2c, 0xc9, 0x20, 0x0a, 0x90, - 0x24, 0x5e, 0xc2, 0x3c, 0xe9, 0x4e, 0x6f, 0x6b, 0x46, 0x2a, 0x93, 0xb8, 0x5d, 0xa2, 0xed, 0x78, - 0xd7, 0x6e, 0x6b, 0xe9, 0x89, 0x44, 0x92, 0xb8, 0x4b, 0x19, 0x46, 0x22, 0x84, 0x77, 0x81, 0x29, - 0xf9, 0x50, 0xc8, 0x82, 0x13, 0x8a, 0x66, 0x98, 0xe4, 0xf2, 0xf5, 0x6c, 0x3d, 0x69, 0xa3, 0x79, - 0x13, 0xbc, 0xba, 0xfd, 0x57, 0xbe, 0x4d, 0xfb, 0x3f, 0x01, 0x6b, 0x8a, 0x3b, 0x27, 0x31, 0xab, - 0xd3, 0x63, 0xae, 0x2a, 0xfb, 0x71, 0xd0, 0x4f, 0x00, 0x8c, 0x05, 0x9e, 0xc4, 0x9c, 0x7b, 0x85, - 0x38, 0x63, 0x81, 0xc7, 0x21, 0x7d, 0xb0, 0x25, 0x54, 0xf1, 0x79, 0x03, 0x22, 0x35, 0x99, 0x44, - 0x01, 0x09, 0xa9, 0xe8, 0x67, 0xe0, 0x37, 0xa6, 0x07, 0xdf, 0xd0, 0x40, 0x1f, 0x2b, 0x1c, 0x37, - 0x83, 0x49, 0xbd, 0xb4, 0x41, 0xf3, 0x6a, 0x2f, 0x79, 0x82, 0xe6, 0x75, 0x82, 0xbe, 0x77, 0x05, - 0x44, 0x9e, 0x25, 0x01, 0xde, 0x2a, 0x91, 0x9e, 0xba, 0xd5, 0x9e, 0xbe, 0x50, 0x1e, 0x27, 0x3d, - 0xc5, 0x0c, 0x28, 0xe1, 0x3f, 0x42, 0x72, 0xe2, 0x4e, 0xbb, 0x87, 0x1a, 0xcd, 0xf2, 0xce, 0xd1, - 0x66, 0x34, 0x4c, 0xa7, 0x1b, 0xab, 0xe0, 0xc6, 0xbc, 0x47, 0xb8, 0x25, 0xac, 0x0f, 0x08, 0x51, - 0xb7, 0xb9, 0xc4, 0x8f, 0x24, 0x62, 0xb8, 0xaf, 0xf9, 0xbb, 0xe2, 0x2e, 0xe5, 0x5c, 0x78, 0x5f, - 0x49, 0x1f, 0x56, 0x6b, 0xb5, 0x95, 0xba, 0xf5, 0x23, 0x50, 0xd7, 0x97, 0x79, 0x1f, 0x9f, 0x0b, - 0xcd, 0x0e, 0xbe, 0xcf, 0x89, 0x10, 0x44, 0x98, 0x46, 0xca, 0x0e, 0x99, 0xc0, 0x92, 0x60, 0xe3, - 0xba, 0x29, 0x50, 0xc0, 0x27, 0x60, 0x3e, 0x22, 0x7a, 0x44, 0xd1, 0x86, 0x8d, 0xbd, 0xf7, 0xec, - 0x29, 0x66, 0x74, 0xfb, 0x3a, 0x40, 0x37, 0x43, 0xb3, 0x78, 0x31, 0x7b, 0x4e, 0x90, 0x8d, 0x80, - 0x67, 0x93, 0x4e, 0x7f, 0xf6, 0x4a, 0x4e, 0x27, 0xf0, 0x0a, 0x9f, 0xb7, 0x41, 0x63, 0x3f, 0xd9, - 0xf6, 0x47, 0x8a, 0x16, 0x2f, 0x1d, 0xcb, 0x42, 0xf9, 0x58, 0x1e, 0x82, 0xa5, 0x94, 0xd0, 0x4f, - 0x99, 0x6e, 0x48, 0xf0, 0xfb, 0x00, 0xa4, 0x93, 0x80, 0x6a, 0x64, 0x49, 0xcb, 0xae, 0xa7, 0x92, - 0x23, 0x7f, 0x8c, 0xeb, 0x66, 0xc7, 0xb8, 0xce, 0x72, 0xc1, 0xf2, 0x99, 0xc0, 0x3f, 0xcf, 0xa6, - 0xbd, 0xc7, 0x91, 0x80, 0xaf, 0x81, 0x1b, 0xea, 0x0e, 0xa5, 0x40, 0x55, 0x77, 0x2e, 0x16, 0xf8, - 0x48, 0x77, 0xed, 0x62, 0xa2, 0x64, 0x91, 0x47, 0x7d, 0x61, 0xce, 0xb6, 0x2a, 0x3b, 0x55, 0x77, - 0x69, 0x58, 0x98, 0x1f, 0xf9, 0xc2, 0xfa, 0x05, 0x68, 0x94, 0x00, 0xe1, 0x12, 0x98, 0xcd, 0xb1, - 0x66, 0xa9, 0x0f, 0xef, 0x81, 0x8d, 0x02, 0x68, 0xbc, 0x0d, 0x27, 0x88, 0x75, 0xf7, 0x66, 0xae, - 0x30, 0xd6, 0x89, 0x85, 0xf5, 0x18, 0xac, 0x1f, 0x15, 0x97, 0x3e, 0x6f, 0xf2, 0x63, 0x3b, 0x34, - 0xc6, 0xd9, 0x7c, 0x0b, 0xd4, 0xf3, 0x5f, 0x52, 0x7a, 0xf7, 0x55, 0xb7, 0x10, 0x58, 0x03, 0xb0, - 0x72, 0x26, 0xf0, 0x09, 0x09, 0xfd, 0x02, 0xec, 0x9a, 0x03, 0x38, 0x98, 0x04, 0x9a, 0x7a, 0x2c, - 0x2f, 0xdc, 0x31, 0xb0, 0x71, 0x56, 0x1e, 0x90, 0x34, 0x01, 0x77, 0x10, 0x3e, 0x27, 0x52, 0x40, - 0x17, 0x54, 0xf5, 0x20, 0x94, 0x54, 0xd6, 0xdd, 0x6b, 0x2b, 0x2b, 0xde, 0xb5, 0xaf, 0x03, 0x39, - 0x44, 0x12, 0xa5, 0x77, 0x57, 0x63, 0x59, 0x3f, 0x04, 0x6b, 0x1f, 0x23, 0x39, 0xe4, 0xc4, 0x1f, - 0xcb, 0xf1, 0x0a, 0xa8, 0xa8, 0xfc, 0x19, 0x3a, 0x7f, 0xea, 0x51, 0xcd, 0x03, 0xe6, 0xfd, 0x4f, - 0x23, 0xc6, 0x25, 0xf1, 0x2f, 0x9d, 0xc8, 0x4b, 0x8e, 0xf7, 0x1c, 0xac, 0xa9, 0xc3, 0x12, 0x24, - 0xf4, 0xbd, 0x7c, 0x9f, 0x49, 0x1e, 0x1b, 0x7b, 0x3f, 0x9d, 0xea, 0x76, 0x4c, 0xba, 0x4b, 0x37, - 0xb0, 0x1a, 0x4f, 0xc8, 0x85, 0xf5, 0x7b, 0x03, 0x98, 0xc7, 0x64, 0xb4, 0x2f, 0x04, 0xed, 0x85, - 0x03, 0x12, 0x4a, 0xd5, 0x03, 0x11, 0x26, 0xea, 0x11, 0xbe, 0x01, 0x16, 0x73, 0xce, 0xd5, 0x54, - 0x6b, 0x68, 0xaa, 0x5d, 0xc8, 0x84, 0xea, 0x82, 0xc1, 0x7b, 0x00, 0x44, 0x9c, 0xc4, 0x1e, 0xf6, - 0xce, 0xc9, 0x28, 0xcd, 0xe2, 0x56, 0x99, 0x42, 0x93, 0xdf, 0xb9, 0x76, 0x67, 0xd8, 0x0d, 0x28, - 0x3e, 0x26, 0x23, 0xb7, 0xa6, 0xf4, 0xdb, 0xc7, 0x64, 0xa4, 0x66, 0x22, 0x3d, 0x1d, 0x6b, 0xde, - 0xab, 0xb8, 0xc9, 0x8b, 0xf5, 0x07, 0x03, 0xdc, 0xcc, 0xd3, 0x91, 0x95, 0x6b, 0x67, 0xd8, 0x55, - 0x16, 0x2f, 0x39, 0xb7, 0x4b, 0xd1, 0xce, 0x5e, 0x11, 0xed, 0xfb, 0x60, 0x21, 0xbf, 0x20, 0x2a, - 0xde, 0xca, 0x14, 0xf1, 0x36, 0x32, 0x8b, 0x63, 0x32, 0xb2, 0x7e, 0x5d, 0x8a, 0xed, 0x60, 0x54, - 0xea, 0x7d, 0xfc, 0x7f, 0xc4, 0x96, 0xbb, 0x2d, 0xc7, 0x86, 0xcb, 0xf6, 0x97, 0x36, 0x50, 0xb9, - 0xbc, 0x01, 0xeb, 0x4f, 0x06, 0x58, 0x2f, 0x7b, 0x15, 0xa7, 0xac, 0xc3, 0x87, 0x21, 0x79, 0x99, - 0xf7, 0xe2, 0xfa, 0xcd, 0x96, 0xaf, 0xdf, 0x13, 0xb0, 0x34, 0x16, 0x94, 0x48, 0x4f, 0xe3, 0x9d, - 0xa9, 0x6a, 0xac, 0xd4, 0x5d, 0xdd, 0xc5, 0xf2, 0x3e, 0x84, 0xf5, 0x17, 0x03, 0xac, 0x66, 0x31, - 0xe6, 0x87, 0x05, 0x7f, 0x0c, 0x60, 0xbe, 0xbd, 0x62, 0x7a, 0x4b, 0x4a, 0x6a, 0x25, 0x5b, 0xc9, - 0x46, 0xb7, 0xa2, 0x34, 0x66, 0x4b, 0xa5, 0x01, 0x3f, 0x02, 0x6b, 0x79, 0xc8, 0x91, 0x4e, 0xd0, - 0xd4, 0x59, 0xcc, 0xe7, 0xd3, 0x5c, 0x64, 0xfd, 0xce, 0x28, 0xe8, 0x30, 0xe1, 0x63, 0xb1, 0x1f, - 0x04, 0xe9, 0x50, 0x0f, 0x23, 0x30, 0x9f, 0x50, 0xbe, 0x48, 0xfb, 0xc7, 0xd6, 0x95, 0xe4, 0x7e, - 0x48, 0xb0, 0xe6, 0xf7, 0xbb, 0xea, 0x8a, 0xfd, 0xed, 0x9b, 0xed, 0xdb, 0x3d, 0x2a, 0xfb, 0xc3, - 0xae, 0x8d, 0xd9, 0xc0, 0x49, 0xbf, 0xd3, 0x24, 0xff, 0xee, 0x08, 0xff, 0xdc, 0x91, 0xa3, 0x88, - 0x88, 0xcc, 0x46, 0xfc, 0xf5, 0xdf, 0xff, 0x78, 0xdb, 0x70, 0x33, 0x37, 0x07, 0x4f, 0xbe, 0x7c, - 0xde, 0x34, 0xbe, 0x7a, 0xde, 0x34, 0xfe, 0xf5, 0xbc, 0x69, 0x7c, 0xf6, 0xa2, 0x39, 0xf3, 0xd5, - 0x8b, 0xe6, 0xcc, 0x3f, 0x5f, 0x34, 0x67, 0x7e, 0xf9, 0xde, 0x65, 0xd0, 0x22, 0x47, 0x77, 0xf2, - 0x2f, 0x63, 0xf1, 0x4f, 0x9c, 0x4f, 0xc7, 0xbf, 0xbb, 0x69, 0x7f, 0xdd, 0x1b, 0xba, 0x9b, 0xbe, - 0xfb, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6d, 0xb7, 0x45, 0x0f, 0xa8, 0x13, 0x00, 0x00, + 0x15, 0xd7, 0x8a, 0xb4, 0x44, 0x0e, 0xf5, 0x77, 0xa4, 0xc4, 0x2b, 0x55, 0xa5, 0xe8, 0x4d, 0x93, + 0xaa, 0x49, 0xbd, 0x8c, 0x94, 0x16, 0x30, 0x8c, 0x06, 0x81, 0x44, 0x39, 0xb1, 0xac, 0xc4, 0x66, + 0x56, 0xaa, 0x8c, 0xb6, 0x87, 0xc5, 0x70, 0x76, 0x4c, 0x0e, 0xb4, 0xbb, 0xb3, 0x9e, 0x19, 0xae, + 0xcc, 0x4b, 0xcf, 0x3d, 0xb4, 0x40, 0x7a, 0x0b, 0x7a, 0x69, 0x5a, 0xa0, 0x40, 0xd1, 0x43, 0xdb, + 0x8f, 0x91, 0x63, 0x8e, 0x3d, 0x25, 0x85, 0x7d, 0xe8, 0xa1, 0x5f, 0xa2, 0x98, 0xd9, 0xbf, 0xa4, + 0x24, 0x97, 0x46, 0xda, 0x8b, 0xb4, 0xfb, 0xe6, 0xbd, 0xdf, 0x7b, 0x33, 0xef, 0xcd, 0xfb, 0x3d, + 0x2e, 0xd8, 0xa3, 0xa1, 0x24, 0x1c, 0x0f, 0x10, 0x0d, 0x5d, 0x41, 0xf0, 0x90, 0x53, 0x39, 0x6a, + 0x63, 0x1c, 0xb7, 0x23, 0xce, 0x62, 0xea, 0x11, 0xde, 0x8e, 0x77, 0xf3, 0x67, 0x3b, 0xe2, 0x4c, + 0x32, 0xf8, 0xc6, 0x15, 0x36, 0x36, 0xc6, 0xb1, 0x9d, 0xeb, 0xc5, 0xbb, 0x9b, 0x6f, 0x5e, 0x07, + 0x1c, 0xef, 0xb6, 0x2f, 0x28, 0x27, 0x09, 0xd6, 0xe6, 0x7a, 0x9f, 0xf5, 0x99, 0x7e, 0x6c, 0xab, + 0xa7, 0x54, 0xba, 0xdd, 0x67, 0xac, 0xef, 0x93, 0xb6, 0x7e, 0xeb, 0x0d, 0x9f, 0xb4, 0x25, 0x0d, + 0x88, 0x90, 0x28, 0x88, 0x52, 0x85, 0xe6, 0xa4, 0x82, 0x37, 0xe4, 0x48, 0x52, 0x16, 0x66, 0x00, + 0xb4, 0x87, 0xdb, 0x98, 0x71, 0xd2, 0xc6, 0x3e, 0x25, 0xa1, 0x54, 0x5e, 0x93, 0xa7, 0x54, 0xa1, + 0xad, 0x14, 0x7c, 0xda, 0x1f, 0xc8, 0x44, 0x2c, 0xda, 0x92, 0x84, 0x1e, 0xe1, 0x01, 0x4d, 0x94, + 0x8b, 0xb7, 0xd4, 0x60, 0xab, 0xb4, 0x8e, 0xf9, 0x28, 0x92, 0xac, 0x7d, 0x4e, 0x46, 0x22, 0x5d, + 0x7d, 0x0b, 0x33, 0x11, 0x30, 0xd1, 0x26, 0x6a, 0xff, 0x21, 0x26, 0xed, 0x78, 0xb7, 0x47, 0x24, + 0xda, 0xcd, 0x05, 0x59, 0xdc, 0xa9, 0x5e, 0x0f, 0x89, 0x42, 0x07, 0x33, 0x9a, 0xc5, 0xbd, 0x8a, + 0x02, 0x1a, 0xb2, 0xb6, 0xfe, 0x9b, 0x88, 0xac, 0x5f, 0xd7, 0x80, 0xd9, 0x61, 0xa1, 0x18, 0x06, + 0x84, 0xef, 0x7b, 0x1e, 0x55, 0xbb, 0xec, 0x72, 0x16, 0x31, 0x81, 0x7c, 0xb8, 0x0e, 0x6e, 0x48, + 0x2a, 0x7d, 0x62, 0x1a, 0x2d, 0x63, 0xa7, 0xee, 0x24, 0x2f, 0xb0, 0x05, 0x1a, 0x1e, 0x11, 0x98, + 0xd3, 0x48, 0x29, 0x9b, 0xb3, 0x7a, 0xad, 0x2c, 0x82, 0x1b, 0xa0, 0x96, 0xa4, 0x86, 0x7a, 0x66, + 0x45, 0x2f, 0xcf, 0xeb, 0xf7, 0x23, 0x0f, 0x7e, 0x04, 0x96, 0x68, 0x48, 0x25, 0x45, 0xbe, 0x3b, + 0x20, 0xea, 0x80, 0xcc, 0x6a, 0xcb, 0xd8, 0x69, 0xec, 0x6d, 0xda, 0xb4, 0x87, 0x6d, 0x75, 0xa6, + 0x76, 0x7a, 0x92, 0xf1, 0xae, 0x7d, 0x5f, 0x6b, 0x1c, 0x54, 0xbf, 0xfc, 0x7a, 0x7b, 0xc6, 0x59, + 0x4c, 0xed, 0x12, 0x21, 0xbc, 0x05, 0x16, 0xfa, 0x24, 0x24, 0x82, 0x0a, 0x77, 0x80, 0xc4, 0xc0, + 0xbc, 0xd1, 0x32, 0x76, 0x16, 0x9c, 0x46, 0x2a, 0xbb, 0x8f, 0xc4, 0x00, 0x6e, 0x83, 0x46, 0x8f, + 0x86, 0x88, 0x8f, 0x12, 0x8d, 0x39, 0xad, 0x01, 0x12, 0x91, 0x56, 0xe8, 0x00, 0x20, 0x22, 0x74, + 0x11, 0xba, 0xaa, 0x00, 0xcc, 0xf9, 0x34, 0x90, 0x24, 0xf9, 0x76, 0x96, 0x7c, 0xfb, 0x34, 0xab, + 0x8e, 0x83, 0x9a, 0x0a, 0xe4, 0xb3, 0x6f, 0xb6, 0x0d, 0xa7, 0xae, 0xed, 0xd4, 0x0a, 0x7c, 0x08, + 0x56, 0x86, 0x61, 0x8f, 0x85, 0x1e, 0x0d, 0xfb, 0x6e, 0x44, 0x38, 0x65, 0x9e, 0x59, 0xd3, 0x50, + 0x1b, 0x97, 0xa0, 0x0e, 0xd3, 0x3a, 0x4a, 0x90, 0x3e, 0x57, 0x48, 0xcb, 0xb9, 0x71, 0x57, 0xdb, + 0xc2, 0x4f, 0x01, 0xc4, 0x38, 0xd6, 0x21, 0xb1, 0xa1, 0xcc, 0x10, 0xeb, 0xd3, 0x23, 0xae, 0x60, + 0x1c, 0x9f, 0x26, 0xd6, 0x29, 0xe4, 0x2f, 0xc0, 0x4d, 0xc9, 0x51, 0x28, 0x9e, 0x10, 0x3e, 0x89, + 0x0b, 0xa6, 0xc7, 0x7d, 0x2d, 0xc3, 0x18, 0x07, 0xbf, 0x0f, 0x5a, 0x38, 0x2d, 0x20, 0x97, 0x13, + 0x8f, 0x0a, 0xc9, 0x69, 0x6f, 0xa8, 0x6c, 0xdd, 0x27, 0x1c, 0x61, 0x5d, 0x23, 0x0d, 0x5d, 0x04, + 0xcd, 0x4c, 0xcf, 0x19, 0x53, 0xfb, 0x30, 0xd5, 0x82, 0x8f, 0xc0, 0xf7, 0x7a, 0x3e, 0xc3, 0xe7, + 0x42, 0x05, 0xe7, 0x8e, 0x21, 0x69, 0xd7, 0x01, 0x15, 0x42, 0xa1, 0x2d, 0xb4, 0x8c, 0x9d, 0x8a, + 0x73, 0x2b, 0xd1, 0xed, 0x12, 0x7e, 0x58, 0xd2, 0x3c, 0x2d, 0x29, 0xc2, 0xdb, 0x00, 0x0e, 0xa8, + 0x90, 0x8c, 0x53, 0x8c, 0x7c, 0x97, 0x84, 0x92, 0x53, 0x22, 0xcc, 0x45, 0x6d, 0xbe, 0x5a, 0xac, + 0xdc, 0x4b, 0x16, 0xe0, 0x03, 0x70, 0xeb, 0x5a, 0xa7, 0x2e, 0x1e, 0xa0, 0x30, 0x24, 0xbe, 0xb9, + 0xa4, 0xb7, 0xb2, 0xed, 0x5d, 0xe3, 0xb3, 0x93, 0xa8, 0xc1, 0x35, 0x70, 0x43, 0xb2, 0xc8, 0x7d, + 0x68, 0x2e, 0xb7, 0x8c, 0x9d, 0x45, 0xa7, 0x2a, 0x59, 0xf4, 0x10, 0xbe, 0x0b, 0xd6, 0x63, 0xe4, + 0x53, 0x0f, 0x49, 0xc6, 0x85, 0x1b, 0xb1, 0x0b, 0xc2, 0x5d, 0x8c, 0x22, 0x73, 0x45, 0xeb, 0xc0, + 0x62, 0xad, 0xab, 0x96, 0x3a, 0x28, 0x82, 0x6f, 0x83, 0xd5, 0x5c, 0xea, 0x0a, 0x22, 0xb5, 0xfa, + 0xaa, 0x56, 0x5f, 0xce, 0x17, 0x4e, 0x88, 0x54, 0xba, 0x5b, 0xa0, 0x8e, 0x7c, 0x9f, 0x5d, 0xf8, + 0x54, 0x48, 0x13, 0xb6, 0x2a, 0x3b, 0x75, 0xa7, 0x10, 0xc0, 0x4d, 0x50, 0xf3, 0x48, 0x38, 0xd2, + 0x8b, 0x6b, 0x7a, 0x31, 0x7f, 0xbf, 0x5b, 0xfb, 0xd5, 0x17, 0xdb, 0x33, 0x9f, 0x7f, 0xb1, 0x3d, + 0x63, 0xfd, 0xcd, 0x00, 0x37, 0x3b, 0x79, 0x96, 0x02, 0x16, 0x23, 0xff, 0xff, 0xd9, 0x0d, 0xf6, + 0x41, 0x5d, 0xa8, 0x63, 0xd2, 0xf7, 0xaf, 0xfa, 0x0a, 0xf7, 0xaf, 0xa6, 0xcc, 0xd4, 0x82, 0xf5, + 0x7b, 0x03, 0xac, 0xdf, 0x7b, 0x3a, 0xa4, 0x31, 0xc3, 0xe8, 0x7f, 0xd2, 0xbc, 0x8e, 0xc1, 0x22, + 0x29, 0xe1, 0x09, 0xb3, 0xd2, 0xaa, 0xec, 0x34, 0xf6, 0xde, 0xb4, 0x93, 0xe6, 0x6a, 0xe7, 0x3d, + 0x37, 0x6d, 0xb0, 0x76, 0xd9, 0xbb, 0x33, 0x6e, 0x7b, 0x77, 0xd6, 0x34, 0xac, 0x3f, 0x1a, 0x60, + 0x53, 0x95, 0x45, 0x9f, 0x38, 0xe4, 0x02, 0x71, 0xef, 0x90, 0x84, 0x2c, 0x10, 0xdf, 0x3a, 0x4e, + 0x0b, 0x2c, 0x7a, 0x1a, 0xc9, 0x95, 0xcc, 0x45, 0x9e, 0xa7, 0xe3, 0xd4, 0x3a, 0x4a, 0x78, 0xca, + 0xf6, 0x3d, 0x0f, 0xee, 0x80, 0x95, 0x42, 0x87, 0xab, 0x7c, 0xaa, 0x63, 0x56, 0x6a, 0x4b, 0x99, + 0x9a, 0xce, 0x32, 0xb1, 0xfe, 0x6d, 0x80, 0x95, 0x8f, 0x7c, 0xd6, 0x43, 0xfe, 0x89, 0x8f, 0xc4, + 0x40, 0x5d, 0x89, 0x91, 0x4a, 0x0f, 0x27, 0x69, 0x2f, 0xd2, 0xe1, 0x4d, 0x9d, 0x1e, 0x65, 0xa6, + 0xbb, 0xe3, 0x07, 0x60, 0x35, 0xef, 0x0e, 0x79, 0x15, 0xe8, 0xdd, 0x1c, 0xac, 0x3d, 0xff, 0x7a, + 0x7b, 0x39, 0x2b, 0xb6, 0x8e, 0xae, 0x88, 0x43, 0x67, 0x19, 0x8f, 0x09, 0x3c, 0xd8, 0x04, 0x0d, + 0xda, 0xc3, 0xae, 0x20, 0x4f, 0xdd, 0x70, 0x18, 0xe8, 0x02, 0xaa, 0x3a, 0x75, 0xda, 0xc3, 0x27, + 0xe4, 0xe9, 0xc3, 0x61, 0x00, 0xdf, 0x03, 0xaf, 0x67, 0x83, 0x81, 0x1b, 0x23, 0xdf, 0x55, 0xf6, + 0xea, 0x38, 0xb8, 0xae, 0xa7, 0x05, 0x67, 0x2d, 0x5b, 0x3d, 0x43, 0xbe, 0x72, 0xb6, 0xef, 0x79, + 0xdc, 0xfa, 0xeb, 0x1c, 0x98, 0xeb, 0x22, 0x8e, 0x02, 0x01, 0x4f, 0xc1, 0xb2, 0x24, 0x41, 0xe4, + 0x23, 0x49, 0xdc, 0x84, 0x79, 0xd2, 0x9d, 0xbe, 0xa3, 0x19, 0xa9, 0x4c, 0xe2, 0x76, 0x89, 0xb6, + 0xe3, 0x5d, 0xbb, 0xa3, 0xa5, 0x27, 0x12, 0x49, 0xe2, 0x2c, 0x65, 0x18, 0x89, 0x10, 0xde, 0x01, + 0xa6, 0xe4, 0x43, 0x21, 0x0b, 0x4e, 0x28, 0x9a, 0x61, 0x92, 0xcb, 0xd7, 0xb3, 0xf5, 0xa4, 0x8d, + 0xe6, 0x4d, 0xf0, 0xea, 0xf6, 0x5f, 0xf9, 0x36, 0xed, 0xff, 0x04, 0xac, 0x29, 0xee, 0x9c, 0xc4, + 0xac, 0x4e, 0x8f, 0xb9, 0xaa, 0xec, 0xc7, 0x41, 0x3f, 0x05, 0x30, 0x16, 0x78, 0x12, 0xf3, 0xc6, + 0x2b, 0xc4, 0x19, 0x0b, 0x3c, 0x0e, 0xe9, 0x81, 0x2d, 0xa1, 0x8a, 0xcf, 0x0d, 0x88, 0xd4, 0x64, + 0x12, 0xf9, 0x24, 0xa4, 0x62, 0x90, 0x81, 0xcf, 0x4d, 0x0f, 0xbe, 0xa1, 0x81, 0x3e, 0x51, 0x38, + 0x4e, 0x06, 0x93, 0x7a, 0xe9, 0x80, 0xe6, 0xd5, 0x5e, 0xf2, 0x04, 0xcd, 0xeb, 0x04, 0x7d, 0xe7, + 0x0a, 0x88, 0x3c, 0x4b, 0x02, 0xbc, 0x55, 0x22, 0x3d, 0x75, 0xab, 0x5d, 0x7d, 0xa1, 0x5c, 0x4e, + 0xfa, 0x8a, 0x19, 0x50, 0xc2, 0x7f, 0x84, 0xe4, 0xc4, 0x9d, 0x76, 0x0f, 0x35, 0x9a, 0xe5, 0x9d, + 0xa3, 0xc3, 0x68, 0x98, 0x4e, 0x37, 0x56, 0xc1, 0x8d, 0x79, 0x8f, 0x70, 0x4a, 0x58, 0x1f, 0x12, + 0xa2, 0x6e, 0x73, 0x89, 0x1f, 0x49, 0xc4, 0xf0, 0x40, 0xf3, 0x77, 0xc5, 0x59, 0xca, 0xb9, 0xf0, + 0x9e, 0x92, 0xc2, 0x23, 0x70, 0x2b, 0x40, 0xcf, 0xdc, 0xfc, 0x62, 0x28, 0x70, 0x12, 0x8a, 0xa1, + 0x70, 0x0b, 0x8e, 0xd1, 0xa4, 0x5c, 0x71, 0x9a, 0x01, 0x7a, 0xd6, 0x4d, 0xf5, 0x3a, 0x99, 0xda, + 0x59, 0xae, 0xf5, 0xa0, 0x5a, 0xab, 0xad, 0xd4, 0xad, 0x1f, 0x80, 0xba, 0xee, 0x0b, 0xfb, 0xf8, + 0x5c, 0x68, 0xa2, 0xf1, 0x3c, 0x4e, 0x84, 0x20, 0xc2, 0x34, 0x52, 0xa2, 0xc9, 0x04, 0x96, 0x04, + 0x1b, 0xd7, 0x0d, 0x94, 0x02, 0x3e, 0x06, 0xf3, 0x11, 0xd1, 0xd3, 0x8e, 0x36, 0x6c, 0xec, 0xbd, + 0x6f, 0x4f, 0x31, 0xee, 0xdb, 0xd7, 0x01, 0x3a, 0x19, 0x9a, 0xc5, 0x8b, 0x31, 0x76, 0x82, 0xb7, + 0x04, 0x3c, 0x9b, 0x74, 0xfa, 0x93, 0x57, 0x72, 0x3a, 0x81, 0x57, 0xf8, 0x7c, 0x07, 0x34, 0xf6, + 0x93, 0x6d, 0x7f, 0xac, 0x18, 0xf6, 0xd2, 0xb1, 0x2c, 0x94, 0x8f, 0xe5, 0x01, 0x58, 0x4a, 0x67, + 0x83, 0x53, 0xa6, 0x7b, 0x1b, 0xfc, 0x2e, 0x00, 0xe9, 0x50, 0xa1, 0x7a, 0x62, 0xd2, 0xfd, 0xeb, + 0xa9, 0xe4, 0xc8, 0x1b, 0xa3, 0xcd, 0xd9, 0x31, 0xda, 0xb4, 0x1c, 0xb0, 0x7c, 0x26, 0xf0, 0x4f, + 0xb3, 0xc1, 0xf1, 0x51, 0x24, 0xe0, 0x6b, 0x60, 0x4e, 0x5d, 0xc7, 0x14, 0xa8, 0xea, 0xdc, 0x88, + 0x05, 0x3e, 0xd2, 0x04, 0x50, 0x0c, 0xa7, 0x2c, 0x72, 0xa9, 0x27, 0xcc, 0xd9, 0x56, 0x65, 0xa7, + 0xea, 0x2c, 0x0d, 0x0b, 0xf3, 0x23, 0x4f, 0x58, 0x3f, 0x03, 0x8d, 0x12, 0x20, 0x5c, 0x02, 0xb3, + 0x39, 0xd6, 0x2c, 0xf5, 0xe0, 0x5d, 0xb0, 0x51, 0x00, 0x8d, 0x77, 0xf4, 0x04, 0xb1, 0xee, 0xdc, + 0xcc, 0x15, 0xc6, 0x9a, 0xba, 0xb0, 0x1e, 0x81, 0xf5, 0xa3, 0xa2, 0x7f, 0xe4, 0x7c, 0x31, 0xb6, + 0x43, 0x63, 0x7c, 0x30, 0xd8, 0x02, 0xf5, 0xfc, 0x47, 0x99, 0xde, 0x7d, 0xd5, 0x29, 0x04, 0x56, + 0x00, 0x56, 0xce, 0x04, 0x3e, 0x21, 0xa1, 0x57, 0x80, 0x5d, 0x73, 0x00, 0x07, 0x93, 0x40, 0x53, + 0x4f, 0xf8, 0x85, 0x3b, 0x06, 0x36, 0xce, 0xca, 0xb3, 0x96, 0xe6, 0xf2, 0x2e, 0xc2, 0xe7, 0x44, + 0x0a, 0xe8, 0x80, 0xaa, 0x9e, 0xa9, 0x92, 0xca, 0xba, 0x73, 0x6d, 0x65, 0xc5, 0xbb, 0xf6, 0x75, + 0x20, 0x87, 0x48, 0xa2, 0xb4, 0x0d, 0x68, 0x2c, 0xeb, 0xfb, 0x60, 0xed, 0x13, 0x24, 0x87, 0x9c, + 0x78, 0x63, 0x39, 0x5e, 0x01, 0x15, 0x95, 0x3f, 0x43, 0xe7, 0x4f, 0x3d, 0xaa, 0xd1, 0xc2, 0xbc, + 0xf7, 0x2c, 0x62, 0x5c, 0x12, 0xef, 0xd2, 0x89, 0xbc, 0xe4, 0x78, 0xcf, 0xc1, 0x9a, 0x3a, 0x2c, + 0x41, 0x42, 0xcf, 0xcd, 0xf7, 0x99, 0xe4, 0xb1, 0xb1, 0xf7, 0xe3, 0xa9, 0x6e, 0xc7, 0xa4, 0xbb, + 0x74, 0x03, 0xab, 0xf1, 0x84, 0x5c, 0x58, 0xbf, 0x35, 0x80, 0x79, 0x4c, 0x46, 0xfb, 0x42, 0xd0, + 0x7e, 0x18, 0x90, 0x50, 0xaa, 0x76, 0x8a, 0x30, 0x51, 0x8f, 0xf0, 0x0d, 0xb0, 0x98, 0x77, 0x29, + 0xcd, 0xda, 0x86, 0x66, 0xed, 0x85, 0x4c, 0xa8, 0x2e, 0x18, 0xbc, 0x0b, 0x40, 0xc4, 0x49, 0xec, + 0x62, 0xf7, 0x9c, 0x8c, 0xd2, 0x2c, 0x6e, 0x95, 0xd9, 0x38, 0xf9, 0xc9, 0x6c, 0x77, 0x87, 0x3d, + 0x9f, 0xe2, 0x63, 0x32, 0x72, 0x6a, 0x4a, 0xbf, 0x73, 0x4c, 0x46, 0x6a, 0xbc, 0xd2, 0x83, 0xb6, + 0xa6, 0xd0, 0x8a, 0x93, 0xbc, 0x58, 0xbf, 0x33, 0xc0, 0xcd, 0x3c, 0x1d, 0x59, 0xb9, 0x76, 0x87, + 0x3d, 0x65, 0xf1, 0x92, 0x73, 0xbb, 0x14, 0xed, 0xec, 0x15, 0xd1, 0x7e, 0x00, 0x16, 0xf2, 0x0b, + 0xa2, 0xe2, 0xad, 0x4c, 0x11, 0x6f, 0x23, 0xb3, 0x38, 0x26, 0x23, 0xeb, 0x97, 0xa5, 0xd8, 0x0e, + 0x46, 0xa5, 0xde, 0xc7, 0xff, 0x4b, 0x6c, 0xb9, 0xdb, 0x72, 0x6c, 0xb8, 0x6c, 0x7f, 0x69, 0x03, + 0x95, 0xcb, 0x1b, 0xb0, 0xfe, 0x60, 0x80, 0xf5, 0xb2, 0x57, 0x71, 0xca, 0xba, 0x7c, 0x18, 0x92, + 0x97, 0x79, 0x2f, 0xae, 0xdf, 0x6c, 0xf9, 0xfa, 0x3d, 0x06, 0x4b, 0x63, 0x41, 0x89, 0xf4, 0x34, + 0xde, 0x9d, 0xaa, 0xc6, 0x4a, 0xdd, 0xd5, 0x59, 0x2c, 0xef, 0x43, 0x58, 0x7f, 0x32, 0xc0, 0x6a, + 0x16, 0x63, 0x7e, 0x58, 0xf0, 0x87, 0x00, 0x8e, 0x71, 0x5e, 0xb9, 0xa4, 0x56, 0xa2, 0x12, 0xcb, + 0xe9, 0xc3, 0xc8, 0x4b, 0x63, 0xb6, 0x54, 0x1a, 0xf0, 0x63, 0xb0, 0x96, 0x87, 0x1c, 0xe9, 0x04, + 0x4d, 0x9d, 0xc5, 0x7c, 0xd4, 0xcd, 0x45, 0xd6, 0x6f, 0x8c, 0x82, 0x0e, 0x13, 0x6a, 0x17, 0xfb, + 0xbe, 0x9f, 0xfe, 0x3e, 0x80, 0x11, 0x98, 0x4f, 0xa6, 0x07, 0x91, 0xf6, 0x8f, 0xad, 0x2b, 0xe7, + 0x84, 0x43, 0x82, 0xf5, 0xa8, 0x70, 0x47, 0x5d, 0xb1, 0xbf, 0x7c, 0xb3, 0xfd, 0x4e, 0x9f, 0xca, + 0xc1, 0xb0, 0x67, 0x63, 0x16, 0xb4, 0xd3, 0x4f, 0x3e, 0xc9, 0xbf, 0xdb, 0xc2, 0x3b, 0x6f, 0xcb, + 0x51, 0x44, 0x44, 0x66, 0x23, 0xfe, 0xfc, 0xaf, 0xbf, 0xbf, 0x6d, 0x38, 0x99, 0x9b, 0x83, 0xc7, + 0x5f, 0x3e, 0x6f, 0x1a, 0x5f, 0x3d, 0x6f, 0x1a, 0xff, 0x7c, 0xde, 0x34, 0x3e, 0x7b, 0xd1, 0x9c, + 0xf9, 0xea, 0x45, 0x73, 0xe6, 0x1f, 0x2f, 0x9a, 0x33, 0x3f, 0x7f, 0xff, 0x32, 0x68, 0x91, 0xa3, + 0xdb, 0xf9, 0x47, 0xb6, 0xf8, 0x47, 0xed, 0x67, 0xe3, 0x9f, 0xf0, 0xb4, 0xbf, 0xde, 0x9c, 0xee, + 0xa6, 0xef, 0xfd, 0x27, 0x00, 0x00, 0xff, 0xff, 0x39, 0x1a, 0x7d, 0x58, 0xf3, 0x13, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -2075,6 +2087,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MaxProviderConsensusValidators != 0 { + i = encodeVarintProvider(dAtA, i, uint64(m.MaxProviderConsensusValidators)) + i-- + dAtA[i] = 0x58 + } if m.BlocksPerEpoch != 0 { i = encodeVarintProvider(dAtA, i, uint64(m.BlocksPerEpoch)) i-- @@ -3086,6 +3103,9 @@ func (m *Params) Size() (n int) { if m.BlocksPerEpoch != 0 { n += 1 + sovProvider(uint64(m.BlocksPerEpoch)) } + if m.MaxProviderConsensusValidators != 0 { + n += 1 + sovProvider(uint64(m.MaxProviderConsensusValidators)) + } return n } @@ -4994,6 +5014,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxProviderConsensusValidators", wireType) + } + m.MaxProviderConsensusValidators = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxProviderConsensusValidators |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipProvider(dAtA[iNdEx:]) From 48f40858d67b4987dda2d31b82ff4c3d1bdc65eb Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Tue, 14 May 2024 17:00:01 +0200 Subject: [PATCH 07/28] Use MaxProviderConsensusValidators param --- x/ccv/provider/keeper/relay.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index cab1c1cbb7..f8e381409e 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -265,10 +265,8 @@ func (k Keeper) ProviderValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate // get the last validator set sent to consensus currentValidators := k.GetLastProviderConsensusValSet(ctx) - MAX_CONSENSUS_VALIDATORS := 180 // TODO: make this a parameter - nextValidators := []types.ConsumerValidator{} - for _, val := range bondedValidators[:MAX_CONSENSUS_VALIDATORS] { + for _, val := range bondedValidators[:k.GetMaxProviderConsensusValidators(ctx)] { // create the validator from the staking validator consAddr, err := val.GetConsAddr() if err != nil { From 5ccdb188ed9028ca6f1f4ed4aede93d7ffc41179 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Tue, 14 May 2024 17:49:47 +0200 Subject: [PATCH 08/28] Utilize wrapper around staking module --- app/provider/app.go | 12 ++++-- x/ccv/provider/keeper/relay.go | 7 +++- x/ccv/staking_no_val_updates/doc.go | 8 ++++ x/ccv/staking_no_val_updates/module.go | 54 ++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 x/ccv/staking_no_val_updates/doc.go create mode 100644 x/ccv/staking_no_val_updates/module.go diff --git a/app/provider/app.go b/app/provider/app.go index 9cf45fe7e3..068ec52eb0 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -86,7 +86,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/slashing" slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" - "github.com/cosmos/cosmos-sdk/x/staking" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/cosmos-sdk/x/upgrade" @@ -107,6 +106,8 @@ import ( ibcproviderclient "github.com/cosmos/interchain-security/v4/x/ccv/provider/client" ibcproviderkeeper "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" providertypes "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" + + wrapped_staking "github.com/cosmos/interchain-security/v4/x/ccv/staking_no_val_updates" ) const ( @@ -129,7 +130,8 @@ var ( genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), bank.AppModuleBasic{}, capability.AppModuleBasic{}, - staking.AppModuleBasic{}, + // staking.AppModuleBasic{}, + wrapped_staking.AppModuleBasic{}, mint.AppModuleBasic{}, distr.AppModuleBasic{}, gov.NewAppModuleBasic( @@ -513,7 +515,8 @@ func New( mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName)), distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), - staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), + // staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), + wrapped_staking.NewAppModule(appCodec, *app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), upgrade.NewAppModule(&app.UpgradeKeeper), evidence.NewAppModule(app.EvidenceKeeper), ibc.NewAppModule(app.IBCKeeper), @@ -608,7 +611,8 @@ func New( capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), - staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), + // staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), + wrapped_staking.NewAppModule(appCodec, *app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName)), params.NewAppModule(app.ParamsKeeper), diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index f8e381409e..3c6a98a945 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -266,7 +266,12 @@ func (k Keeper) ProviderValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate currentValidators := k.GetLastProviderConsensusValSet(ctx) nextValidators := []types.ConsumerValidator{} - for _, val := range bondedValidators[:k.GetMaxProviderConsensusValidators(ctx)] { + maxValidators := k.GetMaxProviderConsensusValidators(ctx) + // avoid out of range errors by bounding the max validators to the number of bonded validators + if maxValidators > int64(len(bondedValidators)) { + maxValidators = int64(len(bondedValidators)) + } + for _, val := range bondedValidators[:maxValidators] { // create the validator from the staking validator consAddr, err := val.GetConsAddr() if err != nil { diff --git a/x/ccv/staking_no_val_updates/doc.go b/x/ccv/staking_no_val_updates/doc.go new file mode 100644 index 0000000000..0cd53a9166 --- /dev/null +++ b/x/ccv/staking_no_val_updates/doc.go @@ -0,0 +1,8 @@ +/* +Package staking defines a "wrapper" module around the Cosmos SDK's native +x/staking module. In other words, it provides the exact same functionality as +the native module in that it simply embeds the native module. However, it +overrides `EndBlock` which will return no validator set updates. Instead, +it is assumed that some other module will provide the validator set updates. +*/ +package staking diff --git a/x/ccv/staking_no_val_updates/module.go b/x/ccv/staking_no_val_updates/module.go new file mode 100644 index 0000000000..f8c261423c --- /dev/null +++ b/x/ccv/staking_no_val_updates/module.go @@ -0,0 +1,54 @@ +package staking + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/exported" + "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/types" + + abci "github.com/cometbft/cometbft/abci/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModule{} +) + +// AppModule embeds the Cosmos SDK's x/staking AppModuleBasic. +type AppModuleBasic struct { + staking.AppModuleBasic +} + +// AppModule embeds the Cosmos SDK's x/staking AppModule where we only override +// specific methods. +type AppModule struct { + // embed the Cosmos SDK's x/staking AppModule + staking.AppModule + + keeper keeper.Keeper + accKeeper types.AccountKeeper + bankKeeper types.BankKeeper +} + +// NewAppModule creates a new AppModule object using the native x/staking module +// AppModule constructor. +func NewAppModule(cdc codec.Codec, keeper keeper.Keeper, ak types.AccountKeeper, bk types.BankKeeper, subspace exported.Subspace) AppModule { + stakingAppMod := staking.NewAppModule(cdc, &keeper, ak, bk, subspace) + return AppModule{ + AppModule: stakingAppMod, + keeper: keeper, + accKeeper: ak, + bankKeeper: bk, + } +} + +// EndBlock delegates the EndBlock call to the underlying x/staking module, +// however, it returns no validator updates as validator updates will be provided by the provider module. +func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + _ = am.keeper.BlockValidatorUpdates(ctx) + return []abci.ValidatorUpdate{} +} From 48a1aba6fed1bff15ec89d5760c85cf4efc0409b Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 15 May 2024 08:45:59 +0200 Subject: [PATCH 09/28] Use consensus power for throttle meter --- x/ccv/provider/keeper/provider_consensus.go | 9 +++++++++ x/ccv/provider/keeper/throttle.go | 4 +++- x/ccv/provider/keeper/validator_set_storage.go | 10 ++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/provider_consensus.go b/x/ccv/provider/keeper/provider_consensus.go index 8f2f494975..364d32b6f6 100644 --- a/x/ccv/provider/keeper/provider_consensus.go +++ b/x/ccv/provider/keeper/provider_consensus.go @@ -1,6 +1,7 @@ package keeper import ( + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" @@ -45,3 +46,11 @@ func (k Keeper) GetLastProviderConsensusValSet( ) []types.ConsumerValidator { return k.getValSet(ctx, []byte{types.LastProviderConsensusValsPrefix}) } + +// GetLastTotalProviderConsensusPower returns the total power of the last stored +// validator set sent to the consensus engine on the provider +func (k Keeper) GetLastTotalProviderConsensusPower( + ctx sdk.Context, +) math.Int { + return k.getTotalPower(ctx, []byte{types.LastProviderConsensusValsPrefix}) +} diff --git a/x/ccv/provider/keeper/throttle.go b/x/ccv/provider/keeper/throttle.go index b7e7fd5941..1843f78831 100644 --- a/x/ccv/provider/keeper/throttle.go +++ b/x/ccv/provider/keeper/throttle.go @@ -102,7 +102,9 @@ func (k Keeper) GetSlashMeterAllowance(ctx sdktypes.Context) math.Int { // Compute allowance in units of tendermint voting power (integer), // noting that total power changes over time - totalPower := k.stakingKeeper.GetLastTotalPower(ctx) + + // Get total total power of the last consensus validator set on the provider + totalPower := k.GetLastTotalProviderConsensusPower(ctx) roundedInt := sdktypes.NewInt(decFrac.MulInt(totalPower).RoundInt64()) if roundedInt.IsZero() { diff --git a/x/ccv/provider/keeper/validator_set_storage.go b/x/ccv/provider/keeper/validator_set_storage.go index 65f86a23c5..48b0833da4 100644 --- a/x/ccv/provider/keeper/validator_set_storage.go +++ b/x/ccv/provider/keeper/validator_set_storage.go @@ -3,6 +3,7 @@ package keeper import ( "fmt" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" @@ -92,3 +93,12 @@ func (k Keeper) getValSet( return validators } + +func (k Keeper) getTotalPower(ctx sdk.Context, prefix []byte) math.Int { + var totalPower math.Int + validators := k.getValSet(ctx, prefix) + for _, val := range validators { + totalPower = totalPower.Add(math.NewInt(val.Power)) + } + return totalPower +} From 66139cc670bc14ea022299fa64407c99ff34812b Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 15 May 2024 08:48:47 +0200 Subject: [PATCH 10/28] Add new param to tests --- x/ccv/provider/keeper/params_test.go | 1 + x/ccv/provider/types/genesis_test.go | 26 +++++++++++++------------- x/ccv/provider/types/params_test.go | 24 ++++++++++++------------ 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/x/ccv/provider/keeper/params_test.go b/x/ccv/provider/keeper/params_test.go index 88175431c0..fb93d6c249 100644 --- a/x/ccv/provider/keeper/params_test.go +++ b/x/ccv/provider/keeper/params_test.go @@ -49,6 +49,7 @@ func TestParams(t *testing.T) { Amount: sdk.NewInt(10000000), }, 600, + 180, ) providerKeeper.SetParams(ctx, newParams) params = providerKeeper.GetParams(ctx) diff --git a/x/ccv/provider/types/genesis_test.go b/x/ccv/provider/types/genesis_test.go index 41a716757f..094625d2c7 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)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600, 180), 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)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600, 180), 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)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600, 180), 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)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600, 180), 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)}, 600), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600, 180), 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)}, 600), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600, 180), 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)}, 600), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(1000000)}, 600, 180), 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)}, 600), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600, 180), 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)}, 600), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600, 180), 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)}, 600), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600, 180), 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)}, 600), + sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 600, 180), 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)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "st", Amount: sdk.NewInt(10000000)}, 600, 180), 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)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(-1000000)}, 600, 180), nil, nil, nil, diff --git a/x/ccv/provider/types/params_test.go b/x/ccv/provider/types/params_test.go index 4e72c233af..91feb9f276 100644 --- a/x/ccv/provider/types/params_test.go +++ b/x/ccv/provider/types/params_test.go @@ -24,39 +24,39 @@ 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)}, 1000), true}, + "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000, 180), 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)}, 1000), false}, + "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000, 180), 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)}, 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}, + "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000, 180), 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, 180), 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)}, 1000), true}, + "0.00", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000, 180), 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)}, 1000), false}, + "0.33", 0, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000, 180), 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)}, 1000), false}, + "0.33", time.Hour, 0, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000, 180), 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)}, 1000), false}, + "0.33", time.Hour, time.Hour, 0, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000, 180), 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)}, 1000), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, 0, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000, 180), 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)}, 1000), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "1.5", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(10000000)}, 1000, 180), 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)}, 1000), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "0.1", sdk.Coin{Denom: "st", Amount: sdk.NewInt(10000000)}, 1000, 180), 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)}, 1000), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: sdk.NewInt(-10000000)}, 1000, 180), false}, } for _, tc := range testCases { From c11e65d97041a9eb9e836e1ab52e311dfe8b50b7 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 15 May 2024 09:37:18 +0200 Subject: [PATCH 11/28] Start fixing genesis --- testutil/keeper/unit_test_helpers.go | 19 +++++++++++++++ x/ccv/provider/keeper/genesis.go | 33 ++++++++++++++++++++++++++ x/ccv/provider/module_test.go | 11 ++++++--- x/ccv/staking_no_val_updates/module.go | 13 ++++++++++ 4 files changed, 73 insertions(+), 3 deletions(-) diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index abba1a23c9..7eec725579 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -10,6 +10,7 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" + cometbfted25519 "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" @@ -297,3 +298,21 @@ func GetNewCrossChainValidator(t *testing.T) consumertypes.CrossChainValidator { require.NoError(t, err) return validator } + +// CreateStakingValidator helper function to generate a validator with the given power and with a provider address based on index +func CreateStakingValidator(ctx sdk.Context, mocks MockedKeepers, index int, power int64) stakingtypes.Validator { + providerConsPubKey := cometbfted25519.GenPrivKeyFromSecret([]byte{byte(index)}).PubKey() + consAddr := sdk.ConsAddress(providerConsPubKey.Address()) + providerAddr := providertypes.NewProviderConsAddress(consAddr) + pk, _ := cryptocodec.FromTmPubKeyInterface(providerConsPubKey) + pkAny, _ := codectypes.NewAnyWithValue(pk) + providerValidatorAddr := sdk.ValAddress(providerAddr.Address.Bytes()) + + mocks.MockStakingKeeper.EXPECT(). + GetLastValidatorPower(ctx, providerValidatorAddr).Return(power).AnyTimes() + + return stakingtypes.Validator{ + OperatorAddress: providerValidatorAddr.String(), + ConsensusPubkey: pkAny, + } +} diff --git a/x/ccv/provider/keeper/genesis.go b/x/ccv/provider/keeper/genesis.go index 2075ff48ae..722843398d 100644 --- a/x/ccv/provider/keeper/genesis.go +++ b/x/ccv/provider/keeper/genesis.go @@ -102,6 +102,39 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) { } k.SetParams(ctx, genState.Params) + // get the staking validator set + valSet := k.stakingKeeper.GetLastValidators(ctx) + + // restrict the set to the first MaxProviderConsensusValidators + maxVals := k.GetParams(ctx).MaxProviderConsensusValidators + if int64(len(valSet)) > maxVals { + valSet = valSet[:maxVals] + } + + reducedValSet := make([]types.ConsumerValidator, len(valSet)) + for i, val := range valSet { + consAddr, err := val.GetConsAddr() + if err != nil { + k.Logger(ctx).Error("could not create consumer validator", + "validator", val.GetOperator().String(), + "error", err) + continue + } + pubKey, err := val.TmConsPublicKey() + if err != nil { + k.Logger(ctx).Error("could not create consumer validator", + "validator", val.GetOperator().String(), + "error", err) + continue + } + reducedValSet[i] = types.ConsumerValidator{ + ProviderConsAddr: consAddr, + Power: k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()), + ConsumerPublicKey: &pubKey, + } + } + + k.SetLastProviderConsensusValSet(ctx, reducedValSet) k.InitializeSlashMeter(ctx) } diff --git a/x/ccv/provider/module_test.go b/x/ccv/provider/module_test.go index 869c24253f..06fe4374b8 100644 --- a/x/ccv/provider/module_test.go +++ b/x/ccv/provider/module_test.go @@ -15,6 +15,8 @@ import ( "github.com/cosmos/interchain-security/v4/x/ccv/provider" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ccv "github.com/cosmos/interchain-security/v4/x/ccv/types" + + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // Tests the provider's InitGenesis implementation against the spec. @@ -138,12 +140,15 @@ func TestInitGenesis(t *testing.T) { ) } - // Last total power is queried in InitGenesis, only if method has not + // Staking validators are queried in InitGenesis, only if method has not // already panicked from unowned capability. if !tc.expPanic { orderedCalls = append(orderedCalls, - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - ctx).Return(sdk.NewInt(100)).Times(1), // Return total voting power as 100 + mocks.MockStakingKeeper.EXPECT().GetLastValidators( + ctx).Return( + []stakingtypes.Validator{ + testkeeper.CreateStakingValidator(ctx, mocks, 1, 100), + }).Times(1), // Return total voting power as 100 ) } diff --git a/x/ccv/staking_no_val_updates/module.go b/x/ccv/staking_no_val_updates/module.go index f8c261423c..624ee315ff 100644 --- a/x/ccv/staking_no_val_updates/module.go +++ b/x/ccv/staking_no_val_updates/module.go @@ -1,6 +1,8 @@ package staking import ( + "encoding/json" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" @@ -46,6 +48,17 @@ func NewAppModule(cdc codec.Codec, keeper keeper.Keeper, ak types.AccountKeeper, } } +// InitGenesis delegates the InitGenesis call to the underlying x/staking module, +// however, it returns no validator updates as validator updates will be provided by the provider module. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState types.GenesisState + + cdc.MustUnmarshalJSON(data, &genesisState) + _ = am.keeper.InitGenesis(ctx, &genesisState) + + return []abci.ValidatorUpdate{} +} + // EndBlock delegates the EndBlock call to the underlying x/staking module, // however, it returns no validator updates as validator updates will be provided by the provider module. func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { From d21cf9ab2f8c261bfa12d6473d00ff1bc3597d56 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 15 May 2024 10:30:05 +0200 Subject: [PATCH 12/28] Fix mock usage in tests --- x/ccv/provider/keeper/genesis_test.go | 8 +- x/ccv/provider/keeper/proposal_test.go | 3 +- x/ccv/provider/keeper/throttle_test.go | 152 +++++++++++------- .../provider/keeper/validator_set_storage.go | 2 +- 4 files changed, 104 insertions(+), 61 deletions(-) diff --git a/x/ccv/provider/keeper/genesis_test.go b/x/ccv/provider/keeper/genesis_test.go index 81b0a90bd8..f526a06ff4 100644 --- a/x/ccv/provider/keeper/genesis_test.go +++ b/x/ccv/provider/keeper/genesis_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/interchain-security/v4/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v4/testutil/keeper" @@ -136,8 +137,11 @@ func TestInitAndExportGenesis(t *testing.T) { mocks.MockScopedKeeper.EXPECT().GetCapability( ctx, host.PortPath(ccv.ProviderPortID), ).Return(nil, true).Times(1), - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - ctx).Return(sdk.NewInt(100)).Times(1), // Return total voting power as 100 + mocks.MockStakingKeeper.EXPECT().GetLastValidators( + ctx).Return( + []stakingtypes.Validator{ + testkeeper.CreateStakingValidator(ctx, mocks, 1, 100), + }).Times(1), // Return total voting power as 100 ) // init provider chain diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index 80b4531b75..faff88e0c9 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -786,7 +786,8 @@ func TestMakeConsumerGenesis(t *testing.T) { Denom: "stake", Amount: sdk.NewInt(1000000), }, - BlocksPerEpoch: 600, + BlocksPerEpoch: 600, + MaxProviderConsensusValidators: 100, } providerKeeper.SetParams(ctx, moduleParams) defer ctrl.Finish() diff --git a/x/ccv/provider/keeper/throttle_test.go b/x/ccv/provider/keeper/throttle_test.go index 478ec0f235..4b25af8585 100644 --- a/x/ccv/provider/keeper/throttle_test.go +++ b/x/ccv/provider/keeper/throttle_test.go @@ -4,13 +4,13 @@ import ( "testing" "time" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" "cosmossdk.io/math" sdktypes "github.com/cosmos/cosmos-sdk/types" + "github.com/cometbft/cometbft/proto/tendermint/crypto" tmtypes "github.com/cometbft/cometbft/types" testkeeper "github.com/cosmos/interchain-security/v4/testutil/keeper" @@ -48,7 +48,7 @@ func TestSlashMeterReplenishment(t *testing.T) { } for _, tc := range testCases { - providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx( + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx( t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() @@ -62,11 +62,15 @@ func TestSlashMeterReplenishment(t *testing.T) { providerKeeper.SetParams(ctx, params) // Mock total power from staking keeper using test case value - // Any ctx is accepted, and the method will be called multiple times during the tests - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - gomock.Any()).Return(tc.totalPower).AnyTimes(), - ) + // we just set the last provider consensus validator set to have the desired total power + providerKeeper.SetLastProviderConsensusValSet(ctx, + []providertypes.ConsumerValidator{ + { + ProviderConsAddr: []byte("providerConsAddr"), + Power: tc.totalPower.Int64(), + ConsumerPublicKey: &crypto.PublicKey{}, + }, + }) // Now we can initialize the slash meter (this would happen in InitGenesis) providerKeeper.InitializeSlashMeter(ctx) @@ -129,7 +133,7 @@ func TestSlashMeterReplenishment(t *testing.T) { // Tests that the slash meter exhibits desired behavior when multiple replenishments are needed // to restore it to a full value. func TestConsecutiveReplenishments(t *testing.T) { - providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx( + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx( t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() @@ -142,13 +146,15 @@ func TestConsecutiveReplenishments(t *testing.T) { params.SlashMeterReplenishFraction = "0.05" providerKeeper.SetParams(ctx, params) - // Mock total power from staking keeper using test case value - // Any ctx is accepted, and the method will be called multiple times during the tests - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - gomock.Any()).Return(sdktypes.NewInt(1000)).AnyTimes(), - ) - + // Set a mocked last consensus validator set with the desired total power + providerKeeper.SetLastProviderConsensusValSet(ctx, + []providertypes.ConsumerValidator{ + { + ProviderConsAddr: []byte("providerConsAddr"), + Power: 1000, + ConsumerPublicKey: &crypto.PublicKey{}, + }, + }) // Now we can initialize the slash meter (this would happen in InitGenesis) providerKeeper.InitializeSlashMeter(ctx) @@ -204,7 +210,7 @@ func TestConsecutiveReplenishments(t *testing.T) { // TestSlashMeterAllowanceChanges tests the behavior of a full slash meter // when total voting power becomes higher and lower. func TestTotalVotingPowerChanges(t *testing.T) { - providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() now := time.Now() @@ -216,11 +222,14 @@ func TestTotalVotingPowerChanges(t *testing.T) { providerKeeper.SetParams(ctx, params) // Mock total power to be 1000 - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - // Expect two calls, once for initialization, once for allowance check - ctx).Return(sdktypes.NewInt(1000)).Times(2), - ) + providerKeeper.SetLastProviderConsensusValSet(ctx, + []providertypes.ConsumerValidator{ + { + ProviderConsAddr: []byte("providerConsAddr"), + Power: 1000, + ConsumerPublicKey: &crypto.PublicKey{}, + }, + }) // Initialize the slash meter (this would happen in InitGenesis) providerKeeper.InitializeSlashMeter(ctx) @@ -231,11 +240,15 @@ func TestTotalVotingPowerChanges(t *testing.T) { // Mutate context so mocked total power is less than before ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Microsecond)) // Don't add enough time for replenishment - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - // Expect two calls, once for replenish check, once for allowance check - ctx).Return(sdktypes.NewInt(500)).Times(2), - ) + // expect the total power to be less to simulate slashes + providerKeeper.SetLastProviderConsensusValSet(ctx, + []providertypes.ConsumerValidator{ + { + ProviderConsAddr: []byte("providerConsAddr"), + Power: 500, + ConsumerPublicKey: &crypto.PublicKey{}, + }, + }) // Replenishment should not happen here, but slash meter should be decremented to new allowance providerKeeper.CheckForSlashMeterReplenishment(ctx) @@ -245,12 +258,15 @@ func TestTotalVotingPowerChanges(t *testing.T) { // Mutate context so mocked total power is again less than before, // with ctx time set to a time that will replenish meter ctx = ctx.WithBlockTime(ctx.BlockTime().Add(5 * time.Hour)) - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - // Expect three calls, once for replenish check, - // once for replenishment, once for allowance check - ctx).Return(sdktypes.NewInt(100)).Times(3), - ) + // expect the total power to be even less + providerKeeper.SetLastProviderConsensusValSet(ctx, + []providertypes.ConsumerValidator{ + { + ProviderConsAddr: []byte("providerConsAddr"), + Power: 100, + ConsumerPublicKey: &crypto.PublicKey{}, + }, + }) // Replenishment should happen here, slash meter should be decremented to new allowance regardless providerKeeper.CheckForSlashMeterReplenishment(ctx) @@ -259,11 +275,14 @@ func TestTotalVotingPowerChanges(t *testing.T) { // Mutate context so mocked total power is now more than before ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Microsecond)) // Don't add enough time for replenishment - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - // Expect two calls, once for replenish check, once for allowance check - ctx).Return(sdktypes.NewInt(5000)).Times(2), - ) + providerKeeper.SetLastProviderConsensusValSet(ctx, + []providertypes.ConsumerValidator{ + { + ProviderConsAddr: []byte("providerConsAddr"), + Power: 5000, + ConsumerPublicKey: &crypto.PublicKey{}, + }, + }) // // Important: Without a replenishment, the meter should remain at its previous value @@ -277,12 +296,14 @@ func TestTotalVotingPowerChanges(t *testing.T) { // Mutate context so mocked total power is again more than before, // with ctx time set to a time that will replenish meter ctx = ctx.WithBlockTime(ctx.BlockTime().Add(5 * time.Hour)) - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - // Expect three calls, once for replenish check, - // once for replenishment, once for allowance check - ctx).Return(sdktypes.NewInt(10000)).Times(3), - ) + providerKeeper.SetLastProviderConsensusValSet(ctx, + []providertypes.ConsumerValidator{ + { + ProviderConsAddr: []byte("providerConsAddr"), + Power: 10000, + ConsumerPublicKey: &crypto.PublicKey{}, + }, + }) // Replenishment should happen here, slash meter should be set to new allowance providerKeeper.CheckForSlashMeterReplenishment(ctx) @@ -337,7 +358,7 @@ func TestNegativeSlashMeter(t *testing.T) { } for _, tc := range testCases { - providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx( + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx( t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() @@ -345,19 +366,30 @@ func TestNegativeSlashMeter(t *testing.T) { params.SlashMeterReplenishFraction = tc.replenishFraction providerKeeper.SetParams(ctx, params) - // Return mocked values: total power once, + // Return mocked values by setting the provider consensus validator set: total power once, // then total power minus slashed power any amount of times - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - gomock.Any()).Return(tc.totalPower).Times(1), - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - gomock.Any()).Return(tc.totalPower.Sub(tc.slashedPower)).AnyTimes(), - ) + providerKeeper.SetLastProviderConsensusValSet(ctx, + []providertypes.ConsumerValidator{ + { + ProviderConsAddr: []byte("providerConsAddr"), + Power: tc.totalPower.Int64(), + ConsumerPublicKey: &crypto.PublicKey{}, + }, + }) // Initialize the slash meter (using first mocked value) providerKeeper.InitializeSlashMeter(ctx) - // remaining calls to GetLastTotalPower should return the second mocked value. + // remaining calls to to the valiodator set should return total power - slashed power + // to simulate the validator being slashes + providerKeeper.SetLastProviderConsensusValSet(ctx, + []providertypes.ConsumerValidator{ + { + ProviderConsAddr: []byte("providerConsAddr"), + Power: tc.totalPower.Int64() - tc.slashedPower.Int64(), + ConsumerPublicKey: &crypto.PublicKey{}, + }, + }) // Confirm that meter is initialized to expected initial allowance decFrac, err := sdktypes.NewDecFromStr(tc.replenishFraction) @@ -447,14 +479,20 @@ func TestGetSlashMeterAllowance(t *testing.T) { } for _, tc := range testCases { - providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx( + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx( t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( - gomock.Any()).Return(tc.totalPower).Times(1), - ) + // Mock total power by creating a validator with the desired total power + // and setting it as the last provider consensus validator set + providerKeeper.SetLastProviderConsensusValSet(ctx, + []providertypes.ConsumerValidator{ + { + ProviderConsAddr: []byte("providerConsAddr"), + Power: tc.totalPower.Int64(), + ConsumerPublicKey: &crypto.PublicKey{}, + }, + }) // Set desired params params := providertypes.DefaultParams() diff --git a/x/ccv/provider/keeper/validator_set_storage.go b/x/ccv/provider/keeper/validator_set_storage.go index 48b0833da4..7fa334bd2a 100644 --- a/x/ccv/provider/keeper/validator_set_storage.go +++ b/x/ccv/provider/keeper/validator_set_storage.go @@ -95,7 +95,7 @@ func (k Keeper) getValSet( } func (k Keeper) getTotalPower(ctx sdk.Context, prefix []byte) math.Int { - var totalPower math.Int + totalPower := math.ZeroInt() validators := k.getValSet(ctx, prefix) for _, val := range validators { totalPower = totalPower.Add(math.NewInt(val.Power)) From 87742aa4c5b45dcc42ca8f001fcc0301931495ce Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 15 May 2024 10:40:43 +0200 Subject: [PATCH 13/28] Make Genesis return consensus validators --- x/ccv/provider/keeper/genesis.go | 12 +++++++++++- x/ccv/provider/module.go | 4 +--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/x/ccv/provider/keeper/genesis.go b/x/ccv/provider/keeper/genesis.go index 722843398d..54290a2587 100644 --- a/x/ccv/provider/keeper/genesis.go +++ b/x/ccv/provider/keeper/genesis.go @@ -3,6 +3,7 @@ package keeper import ( "fmt" + abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" @@ -10,7 +11,7 @@ import ( ) // InitGenesis initializes the CCV provider state and binds to PortID. -func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) { +func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) []abci.ValidatorUpdate { k.SetPort(ctx, ccv.ProviderPortID) // Only try to bind to port if it is not already bound, since we may already own @@ -136,6 +137,15 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) { k.SetLastProviderConsensusValSet(ctx, reducedValSet) k.InitializeSlashMeter(ctx) + + valUpdates := make([]abci.ValidatorUpdate, len(reducedValSet)) + for i, val := range reducedValSet { + valUpdates[i] = abci.ValidatorUpdate{ + PubKey: *val.ConsumerPublicKey, + Power: val.Power, + } + } + return valUpdates } // ExportGenesis returns the CCV provider module's exported genesis diff --git a/x/ccv/provider/module.go b/x/ccv/provider/module.go index 723d1080e8..748f5fc023 100644 --- a/x/ccv/provider/module.go +++ b/x/ccv/provider/module.go @@ -127,9 +127,7 @@ func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json. var genesisState providertypes.GenesisState cdc.MustUnmarshalJSON(data, &genesisState) - am.keeper.InitGenesis(ctx, &genesisState) - - return []abci.ValidatorUpdate{} + return am.keeper.InitGenesis(ctx, &genesisState) } // ExportGenesis returns the exported genesis state as raw bytes for the provider From fb1a8e51a705980e110a3ce4de4d9040a09a7569 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 15 May 2024 12:59:41 +0200 Subject: [PATCH 14/28] Add debugging info in InitGenesis --- app/provider/app.go | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/app/provider/app.go b/app/provider/app.go index 068ec52eb0..145584ac3f 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -1,6 +1,7 @@ package app import ( + "encoding/json" "fmt" "io" stdlog "log" @@ -108,6 +109,8 @@ import ( providertypes "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" wrapped_staking "github.com/cosmos/interchain-security/v4/x/ccv/staking_no_val_updates" + + sdkmoduletypes "github.com/cosmos/cosmos-sdk/types/module" ) const ( @@ -725,7 +728,45 @@ func (app *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.Res app.UpgradeKeeper.SetModuleVersionMap(ctx, app.MM.GetVersionMap()) - return app.MM.InitGenesis(ctx, app.appCodec, genesisState) + return InitGenesis(app.MM, ctx, app.appCodec, genesisState) +} + +// InitGenesis performs init genesis functionality for modules. Exactly one +// module must return a non-empty validator set update to correctly initialize +// the chain. +func InitGenesis(m *sdkmoduletypes.Manager, ctx sdk.Context, cdc codec.JSONCodec, genesisData map[string]json.RawMessage) abci.ResponseInitChain { + var validatorUpdates []abci.ValidatorUpdate + ctx.Logger().Info("initializing blockchain state from genesis.json") + for _, moduleName := range m.OrderInitGenesis { + if genesisData[moduleName] == nil { + continue + } + + if module, ok := m.Modules[moduleName].(sdkmoduletypes.HasGenesis); ok { + ctx.Logger().Debug("running initialization for module", "module", moduleName) + + moduleValUpdates := module.InitGenesis(ctx, cdc, genesisData[moduleName]) + + // use these validator updates if provided, the module manager assumes + // only one module will update the validator set + if len(moduleValUpdates) > 0 { + fmt.Println("val updates by module", moduleName) + if len(validatorUpdates) > 0 { + panic("validator InitGenesis updates already set by a previous module") + } + validatorUpdates = moduleValUpdates + } + } + } + + // a chain must initialize with a non-empty validator set + if len(validatorUpdates) == 0 { + panic(fmt.Sprintf("validator set is empty after InitGenesis, please ensure at least one validator is initialized with a delegation greater than or equal to the DefaultPowerReduction (%d)", sdk.DefaultPowerReduction)) + } + + return abci.ResponseInitChain{ + Validators: validatorUpdates, + } } // LoadHeight loads a particular height From b632acaf2525fb547009ad55444fb767b237ad0e Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 15 May 2024 14:56:26 +0200 Subject: [PATCH 15/28] Add wrapped_genutil --- app/provider/app.go | 8 +-- x/ccv/provider/module_test.go | 2 +- .../doc.go | 0 x/ccv/wrapped_genutil/module.go | 66 +++++++++++++++++++ x/ccv/wrapped_staking/doc.go | 8 +++ .../module.go | 0 6 files changed, 79 insertions(+), 5 deletions(-) rename x/ccv/{staking_no_val_updates => wrapped_genutil}/doc.go (100%) create mode 100644 x/ccv/wrapped_genutil/module.go create mode 100644 x/ccv/wrapped_staking/doc.go rename x/ccv/{staking_no_val_updates => wrapped_staking}/module.go (100%) diff --git a/app/provider/app.go b/app/provider/app.go index 145584ac3f..16d8919160 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -69,7 +69,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/evidence" evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - "github.com/cosmos/cosmos-sdk/x/genutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" "github.com/cosmos/cosmos-sdk/x/gov" govclient "github.com/cosmos/cosmos-sdk/x/gov/client" @@ -108,7 +107,8 @@ import ( ibcproviderkeeper "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" providertypes "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" - wrapped_staking "github.com/cosmos/interchain-security/v4/x/ccv/staking_no_val_updates" + wrapped_genutil "github.com/cosmos/interchain-security/v4/x/ccv/wrapped_genutil" + wrapped_staking "github.com/cosmos/interchain-security/v4/x/ccv/wrapped_staking" sdkmoduletypes "github.com/cosmos/cosmos-sdk/types/module" ) @@ -130,7 +130,7 @@ var ( // and genesis verification. ModuleBasics = module.NewBasicManager( auth.AppModuleBasic{}, - genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + wrapped_genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), bank.AppModuleBasic{}, capability.AppModuleBasic{}, // staking.AppModuleBasic{}, @@ -503,7 +503,7 @@ func New( // NOTE: Any module instantiated in the module manager that is later modified // must be passed by reference here. app.MM = module.NewManager( - genutil.NewAppModule( + wrapped_genutil.NewAppModule( app.AccountKeeper, app.StakingKeeper, app.BaseApp.DeliverTx, diff --git a/x/ccv/provider/module_test.go b/x/ccv/provider/module_test.go index 06fe4374b8..f7b8d59bf6 100644 --- a/x/ccv/provider/module_test.go +++ b/x/ccv/provider/module_test.go @@ -179,7 +179,7 @@ func TestInitGenesis(t *testing.T) { } require.Equal(t, len(tc.consumerStates), numStatesCounted) - require.Empty(t, valUpdates, "InitGenesis should return no validator updates") + require.NotEmpty(t, valUpdates, "InitGenesis should return validator updates") // Expect slash meter to be initialized to it's allowance value // (replenish fraction * mocked value defined above) diff --git a/x/ccv/staking_no_val_updates/doc.go b/x/ccv/wrapped_genutil/doc.go similarity index 100% rename from x/ccv/staking_no_val_updates/doc.go rename to x/ccv/wrapped_genutil/doc.go diff --git a/x/ccv/wrapped_genutil/module.go b/x/ccv/wrapped_genutil/module.go new file mode 100644 index 0000000000..91b4657887 --- /dev/null +++ b/x/ccv/wrapped_genutil/module.go @@ -0,0 +1,66 @@ +package staking + +import ( + "encoding/json" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/genutil" + "github.com/cosmos/cosmos-sdk/x/genutil/types" + + abci "github.com/cometbft/cometbft/abci/types" +) + +var ( + _ module.AppModuleGenesis = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// AppModule embeds the Cosmos SDK's x/genutil AppModuleBasic. +type AppModuleBasic struct { + genutil.AppModuleBasic +} + +func NewAppModuleBasic(validator types.MessageValidator) AppModuleBasic { + return AppModuleBasic{genutil.NewAppModuleBasic(validator)} +} + +// AppModule implements an application module for the genutil module. +type AppModule struct { + genutil.AppModule + + stakingKeeper types.StakingKeeper + deliverTx func(abci.RequestDeliverTx) abci.ResponseDeliverTx + txEncodingConfig client.TxEncodingConfig +} + +// NewAppModule creates a new AppModule object +func NewAppModule(accountKeeper types.AccountKeeper, + stakingKeeper types.StakingKeeper, deliverTx func(abci.RequestDeliverTx) abci.ResponseDeliverTx, + txEncodingConfig client.TxEncodingConfig, +) module.GenesisOnlyAppModule { + genutilAppModule := genutil.NewAppModule(accountKeeper, stakingKeeper, deliverTx, txEncodingConfig) + genutilAppModule.AppModuleGenesis = AppModule{ + AppModule: genutilAppModule.AppModuleGenesis.(genutil.AppModule), + stakingKeeper: stakingKeeper, + deliverTx: deliverTx, + txEncodingConfig: txEncodingConfig, + } + return genutilAppModule +} + +// InitGenesis delegates the InitGenesis call to the underlying x/genutil module, +// however, it returns no validator updates as validator updates will be provided by the provider module. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState types.GenesisState + + cdc.MustUnmarshalJSON(data, &genesisState) + _, err := genutil.InitGenesis(ctx, am.stakingKeeper, am.deliverTx, genesisState, am.txEncodingConfig) + if err != nil { + panic(err) + } + + return []abci.ValidatorUpdate{} +} diff --git a/x/ccv/wrapped_staking/doc.go b/x/ccv/wrapped_staking/doc.go new file mode 100644 index 0000000000..0cd53a9166 --- /dev/null +++ b/x/ccv/wrapped_staking/doc.go @@ -0,0 +1,8 @@ +/* +Package staking defines a "wrapper" module around the Cosmos SDK's native +x/staking module. In other words, it provides the exact same functionality as +the native module in that it simply embeds the native module. However, it +overrides `EndBlock` which will return no validator set updates. Instead, +it is assumed that some other module will provide the validator set updates. +*/ +package staking diff --git a/x/ccv/staking_no_val_updates/module.go b/x/ccv/wrapped_staking/module.go similarity index 100% rename from x/ccv/staking_no_val_updates/module.go rename to x/ccv/wrapped_staking/module.go From 519a329394761763a85614cafa4d5af2aaa36449 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Thu, 16 May 2024 08:59:22 +0200 Subject: [PATCH 16/28] Fix wrapped genutil integration --- app/provider/app.go | 3 ++- x/ccv/wrapped_genutil/doc.go | 2 +- x/ccv/wrapped_genutil/module.go | 13 ++----------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/app/provider/app.go b/app/provider/app.go index 16d8919160..4590a6f662 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -69,6 +69,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/evidence" evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + "github.com/cosmos/cosmos-sdk/x/genutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" "github.com/cosmos/cosmos-sdk/x/gov" govclient "github.com/cosmos/cosmos-sdk/x/gov/client" @@ -130,7 +131,7 @@ var ( // and genesis verification. ModuleBasics = module.NewBasicManager( auth.AppModuleBasic{}, - wrapped_genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), bank.AppModuleBasic{}, capability.AppModuleBasic{}, // staking.AppModuleBasic{}, diff --git a/x/ccv/wrapped_genutil/doc.go b/x/ccv/wrapped_genutil/doc.go index 0cd53a9166..466804b6cc 100644 --- a/x/ccv/wrapped_genutil/doc.go +++ b/x/ccv/wrapped_genutil/doc.go @@ -5,4 +5,4 @@ the native module in that it simply embeds the native module. However, it overrides `EndBlock` which will return no validator set updates. Instead, it is assumed that some other module will provide the validator set updates. */ -package staking +package genutil diff --git a/x/ccv/wrapped_genutil/module.go b/x/ccv/wrapped_genutil/module.go index 91b4657887..5a625d66e1 100644 --- a/x/ccv/wrapped_genutil/module.go +++ b/x/ccv/wrapped_genutil/module.go @@ -1,4 +1,4 @@ -package staking +package genutil import ( "encoding/json" @@ -15,18 +15,9 @@ import ( var ( _ module.AppModuleGenesis = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleBasic = genutil.AppModuleBasic{} ) -// AppModule embeds the Cosmos SDK's x/genutil AppModuleBasic. -type AppModuleBasic struct { - genutil.AppModuleBasic -} - -func NewAppModuleBasic(validator types.MessageValidator) AppModuleBasic { - return AppModuleBasic{genutil.NewAppModuleBasic(validator)} -} - // AppModule implements an application module for the genutil module. type AppModule struct { genutil.AppModule From 028503949e93227bb48ea8c5642bcc7500615085 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Thu, 23 May 2024 13:51:12 +0200 Subject: [PATCH 17/28] Start adding e2e test --- tests/e2e/config.go | 14 ++ tests/e2e/main.go | 6 + tests/e2e/state.go | 13 +- tests/e2e/steps_inactive_vals.go | 405 +++++++++++++++++++++++++++++++ testutil/keeper/mocks.go | 14 ++ x/ccv/provider/keeper/relay.go | 4 +- x/ccv/types/expected_keepers.go | 1 + 7 files changed, 451 insertions(+), 6 deletions(-) create mode 100644 tests/e2e/steps_inactive_vals.go diff --git a/tests/e2e/config.go b/tests/e2e/config.go index a5b86e85f9..36629f57d9 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -87,6 +87,7 @@ const ( MulticonsumerTestCfg TestConfigType = "multi-consumer" ConsumerMisbehaviourTestCfg TestConfigType = "consumer-misbehaviour" CompatibilityTestCfg TestConfigType = "compatibility" + InactiveValsCfg TestConfigType = "inactive-vals" ) // Attributes that are unique to a validator. Allows us to map (part of) @@ -264,6 +265,8 @@ func GetTestConfig(cfgType TestConfigType, providerVersion, consumerVersion stri testCfg = ConsumerMisbehaviourTestConfig() case CompatibilityTestCfg: testCfg = CompatibilityTestConfig(pv, cv) + case InactiveValsCfg: + testCfg = InactiveValsConfig() default: panic(fmt.Sprintf("Invalid test config: %s", cfgType)) } @@ -500,6 +503,17 @@ func CompatibilityTestConfig(providerVersion, consumerVersion string) TestConfig return testCfg } +func InactiveValsConfig() TestConfig { + tr := DefaultTestConfig() + tr.name = "InactiveValsConfig" + // set the MaxProviderConsensusValidators param to 2 + proviConfig := tr.chainConfigs[ChainID("provi")] + proviConfig.GenesisChanges += " | .app_state.provider.params.max_provider_consensus_validators = \"2\"" + tr.chainConfigs[ChainID("provi")] = proviConfig + + return tr +} + func DefaultTestConfig() TestConfig { tr := TestConfig{ name: string(DefaultTestCfg), diff --git a/tests/e2e/main.go b/tests/e2e/main.go index c1766ccf37..09e963d84f 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -168,6 +168,12 @@ var stepChoices = map[string]StepChoice{ description: "test partial set security for a Top-N chain", testConfig: DefaultTestCfg, }, + "inactive-validators-on-consumer": { + name: "inactive-validators-on-consumer", + steps: stepsInactiveValidatorsOnConsumer(), + description: "test inactive validators on consumer", + testConfig: InactiveValsCfg, + }, } func getTestCaseUsageString() string { diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 7b674b0df8..ab93240723 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -328,14 +328,14 @@ func (tr TestConfig) getRewards(chain ChainID, modelState Rewards) Rewards { func (tr TestConfig) getReward(chain ChainID, validator ValidatorID, blockHeight uint, isNativeDenom bool) float64 { valCfg := tr.validatorConfigs[validator] - delAddresss := valCfg.DelAddress + delAddress := valCfg.DelAddress if chain != ChainID("provi") { // use binary with Bech32Prefix set to ConsumerAccountPrefix if valCfg.UseConsumerKey { - delAddresss = valCfg.ConsumerDelAddress + delAddress = valCfg.ConsumerDelAddress } else { // use the same address as on the provider but with different prefix - delAddresss = valCfg.DelAddressOnConsumer + delAddress = valCfg.DelAddressOnConsumer } } @@ -343,7 +343,7 @@ func (tr TestConfig) getReward(chain ChainID, validator ValidatorID, blockHeight bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, "query", "distribution", "rewards", - delAddresss, + delAddress, `--height`, fmt.Sprint(blockHeight), `--node`, tr.getQueryNode(chain), @@ -358,6 +358,11 @@ func (tr TestConfig) getReward(chain ChainID, validator ValidatorID, blockHeight denomCondition = `total.#(denom=="stake").amount` } + if *verbose { + log.Println("Getting reward for chain: ", chain, " validator: ", validator, " block: ", blockHeight) + log.Println("Reward response: ", string(bz)) + } + return gjson.Get(string(bz), denomCondition).Float() } diff --git a/tests/e2e/steps_inactive_vals.go b/tests/e2e/steps_inactive_vals.go new file mode 100644 index 0000000000..01b647d06b --- /dev/null +++ b/tests/e2e/steps_inactive_vals.go @@ -0,0 +1,405 @@ +package main + +import clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + +// stepsOptInChain starts a provider chain and an Opt-In chain and opts in and out validators +func stepsInactiveValidatorsOnConsumer() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // max consensus validators is 2, so alice should not be in power + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 300000000, + }, + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check current rewards, because alice gets one block of rewards due to being in the genesis + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + }, + setupOptInChain(), + []Step{ + // check that active-but-not-consensus validators do not get slashed for downtime + { + // alices provider node goes offline + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // still 0 consensus power + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, // but alice does not get jailed or slashed + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 300000000, + }, + // check that bob and carol get rewards, but alice does not + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards since block 1 + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + // give carol more power so that she has enough power to validate if bob goes down + { + Action: DelegateTokensAction{ + Chain: ChainID("provi"), + From: ValidatorID("bob"), + To: ValidatorID("carol"), + Amount: 200000000, // carol needs to have more than 2/3rds of power(carol) + power(bob), so if bob has 200 power, carol needs at least 401, so we just go for 500 + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 200, + ValidatorID("carol"): 500, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 500000000, + }, + }, + }, + }, + // bob goes offline + { + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // alice gets into the active set + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 500, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 198000000, // 1% slash + ValidatorID("carol"): 500000000, + }, + // check that now every validator got rewarded since the first block + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards for currently produced blocks only + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): true, // alice is participating right now, so gets rewards + ValidatorID("bob"): false, // bob does not get rewards since he is not participating in consensus + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + { + // relay packets so that the consumer gets up to date with the provider + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 0, + ValidatorID("carol"): 500, + }, + }, + }, + }, + // unjail bob + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus + ValidatorID("bob"): 198, // bob was slashed 1% + ValidatorID("carol"): 500, + }, + // check that between two blocks now, alice does not get rewarded with the native denom + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards for currently produced blocks only + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + // bob is still at 0 power on the consumer chain + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 0, + ValidatorID("carol"): 500, + }, + }, + }, + }, + // relay packets so that the consumer gets up to date with the provider + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 198, + ValidatorID("carol"): 500, + }, + }, + }, + }, + }, + ) + + return s +} + +// Precondition: The provider chain is running. +// Postcondition: A consumer chain with Top N = 0 is running, including an up-and-running IBC connection to the provider. +// "alice", "bob", "carol" have opted in and are validating. +func setupOptInChain() []Step { + return []Step{ + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: "PROPOSAL_STATUS_VOTING_PERIOD", + }, + }, + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, + ValidatorID("bob"): {}, + ValidatorID("carol"): {}, + }, + }, + }, + }, + // Οpt in "alice" and "bob" so the chain is not empty when it is about to start. Note, that "alice" and "bob" use + // the provider's public key (see `UseConsumerKey` is set to `false` in `getDefaultValidators`) and hence do not + // need a consumer-key assignment. + { + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("provi"): ChainState{ + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, // chain is not running yet + ValidatorID("bob"): {}, + ValidatorID("carol"): {}, + }, + }, + }, + }, + { + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, + ValidatorID("bob"): {}, + ValidatorID("carol"): {}, + }, + }, + }, + }, + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: "PROPOSAL_STATUS_PASSED", + }, + }, + }, + }, + }, + { + // we start all the validators but only "alice" and "bob" have opted in and hence + // only "alice" and "bob" are validating blocks + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 200, + // carol has not yet opted in + ValidatorID("carol"): 0, + }, + }, + }, + }, + { + Action: AddIbcConnectionAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + ClientA: 0, + ClientB: 0, + }, + State: State{}, + }, + { + Action: AddIbcChannelAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + ConnectionA: 0, + PortA: "consumer", + PortB: "provider", + Order: "ordered", + }, + State: State{}, + }, + { + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("carol"), + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 200, + // "carol" has opted in, but the VSCPacket capturing the opt-in was not relayed yet + ValidatorID("carol"): 0, + }, + }, + ChainID("provi"): ChainState{ + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {"consu"}, + ValidatorID("bob"): {"consu"}, + ValidatorID("carol"): {"consu"}, + }, + }, + }, + }, + { + // assign the consumer key "carol" is using on the consumer chain to be the one "carol" uses when opting in + Action: AssignConsumerPubKeyAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("carol"), + // reconfigure the node -> validator was using provider key + // until this point -> key matches config.consumerValPubKey for "carol" + ConsumerPubkey: getDefaultValidators()[ValidatorID("carol")].ConsumerValPubKey, + ReconfigureNode: true, + }, + State: State{}, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 200, + // carol has now opted in + ValidatorID("carol"): 300, + }, + }, + ChainID("provi"): ChainState{ + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {"consu"}, + ValidatorID("bob"): {"consu"}, + ValidatorID("carol"): {"consu"}, + }, + }, + }, + }, + } +} diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index ffa76ad40c..4998da9bdf 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -77,6 +77,20 @@ func (mr *MockStakingKeeperMockRecorder) Delegation(ctx, addr, valAddr interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delegation", reflect.TypeOf((*MockStakingKeeper)(nil).Delegation), ctx, addr, valAddr) } +// GetBondedValidatorsByPower mocks base method. +func (m *MockStakingKeeper) GetBondedValidatorsByPower(ctx types0.Context) []types5.Validator { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBondedValidatorsByPower", ctx) + ret0, _ := ret[0].([]types5.Validator) + return ret0 +} + +// GetBondedValidatorsByPower indicates an expected call of GetBondedValidatorsByPower. +func (mr *MockStakingKeeperMockRecorder) GetBondedValidatorsByPower(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBondedValidatorsByPower", reflect.TypeOf((*MockStakingKeeper)(nil).GetBondedValidatorsByPower), ctx) +} + // GetLastTotalPower mocks base method. func (m *MockStakingKeeper) GetLastTotalPower(ctx types0.Context) math.Int { m.ctrl.T.Helper() diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 3c6a98a945..ae1cbdb8dc 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -260,7 +260,7 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { func (k Keeper) ProviderValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate { // get the bonded validators from the staking module - bondedValidators := k.stakingKeeper.GetLastValidators(ctx) + bondedValidators := k.stakingKeeper.GetBondedValidatorsByPower(ctx) // get the last validator set sent to consensus currentValidators := k.GetLastProviderConsensusValSet(ctx) @@ -275,7 +275,7 @@ func (k Keeper) ProviderValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate // create the validator from the staking validator consAddr, err := val.GetConsAddr() if err != nil { - k.Logger(ctx).Error("could not create consumer validator", + k.Logger(ctx).Error("could not create validator", "validator", val.GetOperator().String(), "error", err) continue diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index b3815b6d65..1334f1b143 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -58,6 +58,7 @@ type StakingKeeper interface { GetRedelegationsFromSrcValidator(ctx sdk.Context, valAddr sdk.ValAddress) (reds []stakingtypes.Redelegation) GetUnbondingType(ctx sdk.Context, id uint64) (unbondingType stakingtypes.UnbondingType, found bool) MinCommissionRate(ctx sdk.Context) math.LegacyDec + GetBondedValidatorsByPower(ctx sdk.Context) (validators []stakingtypes.Validator) } // SlashingKeeper defines the contract expected to perform ccv slashing From 9e58c13f2ea0e6b9c53307a3507762ad801a825f Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 24 May 2024 11:31:07 +0200 Subject: [PATCH 18/28] Adjust tests --- tests/e2e/config.go | 5 + tests/e2e/steps_inactive_vals.go | 189 ++++++++++++++++-- x/ccv/provider/keeper/keeper.go | 7 +- x/ccv/provider/keeper/partial_set_security.go | 24 +-- .../keeper/partial_set_security_test.go | 114 +++++++---- x/ccv/provider/keeper/proposal.go | 7 +- x/ccv/provider/keeper/relay.go | 7 +- x/ccv/provider/keeper/relay_test.go | 15 ++ x/ccv/provider/module.go | 9 +- 9 files changed, 286 insertions(+), 91 deletions(-) diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 36629f57d9..767bb0aad9 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -509,7 +509,12 @@ func InactiveValsConfig() TestConfig { // set the MaxProviderConsensusValidators param to 2 proviConfig := tr.chainConfigs[ChainID("provi")] proviConfig.GenesisChanges += " | .app_state.provider.params.max_provider_consensus_validators = \"2\"" + + consuConfig := tr.chainConfigs[ChainID("consu")] + // set the soft_opt_out threshold to 0% to make sure all validators are slashed for downtime + consuConfig.GenesisChanges += " | .app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.0\"" tr.chainConfigs[ChainID("provi")] = proviConfig + tr.chainConfigs[ChainID("consu")] = consuConfig return tr } diff --git a/tests/e2e/steps_inactive_vals.go b/tests/e2e/steps_inactive_vals.go index 01b647d06b..982f8f54e5 100644 --- a/tests/e2e/steps_inactive_vals.go +++ b/tests/e2e/steps_inactive_vals.go @@ -3,6 +3,13 @@ package main import clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" // stepsOptInChain starts a provider chain and an Opt-In chain and opts in and out validators +// high-level, this test does: +// - start the provider chain +// - start a consumer chain +// - check that non-consensus validators do not get slashed for downtime; and that they don't get rewards +// - check that active validators *do* get slashed for downtime, and don't get rewards while they are down +// - check that non-consensus validators *do* get jailed for downtime on consumer chains +// - check that non-consensus validators *become* consensus validators when they have enough power func stepsInactiveValidatorsOnConsumer() []Step { s := concatSteps( []Step{ @@ -61,16 +68,6 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValidatorID("bob"): 200000000, ValidatorID("carol"): 300000000, }, - // check that bob and carol get rewards, but alice does not - Rewards: &Rewards{ - IsNativeDenom: true, // check for rewards in the provider denom - IsIncrementalReward: true, // check rewards since block 1 - IsRewarded: map[ValidatorID]bool{ - ValidatorID("alice"): false, - ValidatorID("bob"): true, - ValidatorID("carol"): true, - }, - }, }, }, }, @@ -78,7 +75,7 @@ func stepsInactiveValidatorsOnConsumer() []Step { { Action: DelegateTokensAction{ Chain: ChainID("provi"), - From: ValidatorID("bob"), + From: ValidatorID("carol"), To: ValidatorID("carol"), Amount: 200000000, // carol needs to have more than 2/3rds of power(carol) + power(bob), so if bob has 200 power, carol needs at least 401, so we just go for 500 }, @@ -94,6 +91,16 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValidatorID("bob"): 200000000, ValidatorID("carol"): 500000000, }, + // check that bob and carol get rewards, but alice does not + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards since block 1 + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, }, }, }, @@ -115,16 +122,6 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValidatorID("bob"): 198000000, // 1% slash ValidatorID("carol"): 500000000, }, - // check that now every validator got rewarded since the first block - Rewards: &Rewards{ - IsNativeDenom: true, // check for rewards in the provider denom - IsIncrementalReward: true, // check rewards for currently produced blocks only - IsRewarded: map[ValidatorID]bool{ - ValidatorID("alice"): true, // alice is participating right now, so gets rewards - ValidatorID("bob"): false, // bob does not get rewards since he is not participating in consensus - ValidatorID("carol"): true, - }, - }, }, }, }, @@ -137,6 +134,18 @@ func stepsInactiveValidatorsOnConsumer() []Step { Channel: 0, }, State: State{ + ChainID("provi"): ChainState{ + // check that now every validator got rewarded since the first block + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards for currently produced blocks only + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): true, // alice is participating right now, so gets rewards + ValidatorID("bob"): false, // bob does not get rewards since he is not participating in consensus + ValidatorID("carol"): true, + }, + }, + }, ChainID("consu"): ChainState{ ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 100, @@ -198,6 +207,144 @@ func stepsInactiveValidatorsOnConsumer() []Step { }, }, }, + // alice goes offline on the consumer chain + { + Action: DowntimeSlashAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // power not affected yet + ValidatorID("bob"): 198, + ValidatorID("carol"): 500, + }, + }, + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is not consensus-active anyways, since we allow two vals at maximum + ValidatorID("bob"): 198, + ValidatorID("carol"): 500, + }, + }, + }, + }, + // relay the packets so that the provider chain knows about alice's downtime + { + Action: RelayPacketsAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + Port: "consumer", + Channel: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is still not in the active set, and should now be jailed too + ValidatorID("bob"): 198, + ValidatorID("carol"): 500, + }, + }, + }, + }, + // we need to double-check that alice is actually jailed, so we get bob jailed, too, which usually would mean alice gets into power + { + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is jailed + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 500, + }, + }, + }, + }, + // relay the packets so that the consumer chain is in sync again + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is jailed + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 500, + }, + }, + ChainID("provi"): ChainState{ + // check that alice and bob don't get consumer rewards + Rewards: &Rewards{ + IsNativeDenom: false, // check for rewards from consumer + IsIncrementalReward: true, // check rewards for currently produced blocks only + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): false, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + + // unjail alice + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // alice is back as an active consensus validator + ValidatorID("bob"): 0, // bob is still jailed + ValidatorID("carol"): 500, + }, + }, + }, + }, + // unjail bob + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus + ValidatorID("bob"): 196, // bob is back as an active consensus validator and lost 2 more power due to the second downtime + ValidatorID("carol"): 500, + }, + }, + }, + }, + // relay the packets so that the consumer chain is in sync again + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // both alice and bob are validating the consumer + ValidatorID("bob"): 196, + ValidatorID("carol"): 500, + }, + }, + }, + }, }, ) diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index bc3664bfd7..d48b97ca6a 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -1266,13 +1266,14 @@ func (k Keeper) HasToValidate( return true, nil } + validators := k.GetLastProviderConsensusValSet(ctx) + // if the validator was not part of the last epoch, check if the validator is going to be part of te next epoch - bondedValidators := k.stakingKeeper.GetLastValidators(ctx) if topN, found := k.GetTopN(ctx, chainID); found && topN > 0 { // in a Top-N chain, we automatically opt in all validators that belong to the top N - minPower, err := k.ComputeMinPowerToOptIn(ctx, chainID, bondedValidators, topN) + minPower, err := k.ComputeMinPowerToOptIn(ctx, chainID, validators, topN) if err == nil { - k.OptInTopNValidators(ctx, chainID, bondedValidators, minPower) + k.OptInTopNValidators(ctx, chainID, validators, minPower) } } diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go index 0d5177089a..3568927b81 100644 --- a/x/ccv/provider/keeper/partial_set_security.go +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -63,7 +63,7 @@ func (k Keeper) HandleOptOut(ctx sdk.Context, chainID string, providerAddr types "validator with consensus address %s could not be found", providerAddr.ToSdkConsAddr()) } power := k.stakingKeeper.GetLastValidatorPower(ctx, validator.GetOperator()) - minPowerToOptIn, err := k.ComputeMinPowerToOptIn(ctx, chainID, k.stakingKeeper.GetLastValidators(ctx), topN) + minPowerToOptIn, err := k.ComputeMinPowerToOptIn(ctx, chainID, k.GetLastProviderConsensusValSet(ctx), topN) if err != nil || power >= minPowerToOptIn { return errorsmod.Wrapf( @@ -78,27 +78,18 @@ func (k Keeper) HandleOptOut(ctx sdk.Context, chainID string, providerAddr types } // OptInTopNValidators opts in to `chainID` all the `bondedValidators` that have at least `minPowerToOptIn` power -func (k Keeper) OptInTopNValidators(ctx sdk.Context, chainID string, bondedValidators []stakingtypes.Validator, minPowerToOptIn int64) { +func (k Keeper) OptInTopNValidators(ctx sdk.Context, chainID string, bondedValidators []types.ConsumerValidator, minPowerToOptIn int64) { for _, val := range bondedValidators { - power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()) - if power >= minPowerToOptIn { - consAddr, err := val.GetConsAddr() - if err != nil { - k.Logger(ctx).Error("could not retrieve validators consensus address", - "validator", val, - "error", err) - continue - } - + if val.Power >= minPowerToOptIn { // if validator already exists it gets overwritten - k.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(consAddr)) + k.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(val.ProviderConsAddr)) } // else validators that do not belong to the top N validators but were opted in, remain opted in } } // ComputeMinPowerToOptIn returns the minimum power needed for a validator (from the bonded validators) // to belong to the `topN` validators. `chainID` is only used for logging purposes. -func (k Keeper) ComputeMinPowerToOptIn(ctx sdk.Context, chainID string, bondedValidators []stakingtypes.Validator, topN uint32) (int64, error) { +func (k Keeper) ComputeMinPowerToOptIn(ctx sdk.Context, chainID string, bondedValidators []types.ConsumerValidator, topN uint32) (int64, error) { if topN == 0 || topN > 100 { return 0, fmt.Errorf("trying to compute minimum power with an incorrect topN value (%d)."+ "topN has to be between (0, 100]", topN) @@ -108,9 +99,8 @@ func (k Keeper) ComputeMinPowerToOptIn(ctx sdk.Context, chainID string, bondedVa var powers []int64 for _, val := range bondedValidators { - power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()) - powers = append(powers, power) - totalPower = totalPower.Add(sdk.NewDecFromInt(sdk.NewInt(power))) + powers = append(powers, val.Power) + totalPower = totalPower.Add(sdk.NewDecFromInt(sdk.NewInt(val.Power))) } // sort by powers descending diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go index afbd420e7f..2c16a0594b 100644 --- a/x/ccv/provider/keeper/partial_set_security_test.go +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -116,20 +116,31 @@ func TestHandleOptOutFromTopNChain(t *testing.T) { // set the chain as Top 50 and create 4 validators with 10%, 20%, 30%, and 40% of the total voting power // respectively providerKeeper.SetTopN(ctx, "chainID", 50) - valA := createStakingValidator(ctx, mocks, 1, 1) // 10% of the total voting power (can opt out) - valAConsAddr, _ := valA.GetConsAddr() - mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valAConsAddr).Return(valA, true).AnyTimes() - valB := createStakingValidator(ctx, mocks, 2, 2) // 20% of the total voting power (can opt out) - valBConsAddr, _ := valB.GetConsAddr() - mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valBConsAddr).Return(valB, true).AnyTimes() - valC := createStakingValidator(ctx, mocks, 3, 3) // 30% of the total voting power (cannot opt out) - valCConsAddr, _ := valC.GetConsAddr() - mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valCConsAddr).Return(valC, true).AnyTimes() - valD := createStakingValidator(ctx, mocks, 4, 4) // 40% of the total voting power (cannot opt out) - valDConsAddr, _ := valD.GetConsAddr() - mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valDConsAddr).Return(valD, true).AnyTimes() - - mocks.MockStakingKeeper.EXPECT().GetLastValidators(ctx).Return([]stakingtypes.Validator{valA, valB, valC, valD}).AnyTimes() + stakingValA := createStakingValidator(ctx, mocks, 1, 1) + valA, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", stakingValA) // 10% of the total voting power (can opt out) + require.NoError(t, err) + valAConsAddr := valA.ProviderConsAddr + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valAConsAddr).Return(stakingValA, true).AnyTimes() + + stakingValB := createStakingValidator(ctx, mocks, 2, 2) + valB, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", stakingValB) // 20% of the total voting power (can opt out) + require.NoError(t, err) + valBConsAddr := valB.ProviderConsAddr + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valBConsAddr).Return(stakingValB, true).AnyTimes() + + stakingValC := createStakingValidator(ctx, mocks, 3, 3) + valC, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", stakingValC) // 30% of the total voting power (cannot opt out) + require.NoError(t, err) + valCConsAddr := valC.ProviderConsAddr + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valCConsAddr).Return(stakingValC, true).AnyTimes() + + stakingValD := createStakingValidator(ctx, mocks, 4, 4) + valD, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", stakingValD) // 40% of the total voting power (cannot opt out) + require.NoError(t, err) + valDConsAddr := valD.ProviderConsAddr + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valDConsAddr).Return(stakingValD, true).AnyTimes() + + providerKeeper.SetLastProviderConsensusValSet(ctx, []types.ConsumerValidator{valA, valB, valC, valD}) // opt in all validators providerKeeper.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(valAConsAddr)) @@ -205,17 +216,21 @@ func TestOptInTopNValidators(t *testing.T) { defer ctrl.Finish() // create 4 validators with powers 1, 2, 3, and 1 respectively - valA := createStakingValidator(ctx, mocks, 1, 1) - valAConsAddr, _ := valA.GetConsAddr() - valB := createStakingValidator(ctx, mocks, 2, 2) - valBConsAddr, _ := valB.GetConsAddr() - valC := createStakingValidator(ctx, mocks, 3, 3) - valCConsAddr, _ := valC.GetConsAddr() - valD := createStakingValidator(ctx, mocks, 4, 1) - valDConsAddr, _ := valD.GetConsAddr() + valA, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 1, 1)) + require.NoError(t, err) + valAConsAddr := valA.ProviderConsAddr + valB, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 2, 2)) + require.NoError(t, err) + valBConsAddr := valB.ProviderConsAddr + valC, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 3, 3)) + require.NoError(t, err) + valCConsAddr := valC.ProviderConsAddr + valD, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 4, 1)) + require.NoError(t, err) + valDConsAddr := valD.ProviderConsAddr // Start Test 1: opt in all validators with power >= 0 - providerKeeper.OptInTopNValidators(ctx, "chainID", []stakingtypes.Validator{valA, valB, valC, valD}, 0) + providerKeeper.OptInTopNValidators(ctx, "chainID", []types.ConsumerValidator{valA, valB, valC, valD}, 0) expectedOptedInValidators := []types.ProviderConsAddress{ types.NewProviderConsAddress(valAConsAddr), types.NewProviderConsAddress(valBConsAddr), @@ -244,7 +259,7 @@ func TestOptInTopNValidators(t *testing.T) { // Start Test 2: opt in all validators with power >= 1 // We expect the same `expectedOptedInValidators` as when we opted in all validators with power >= 0 because the // validators with the smallest power have power == 1 - providerKeeper.OptInTopNValidators(ctx, "chainID", []stakingtypes.Validator{valA, valB, valC, valD}, 0) + providerKeeper.OptInTopNValidators(ctx, "chainID", []types.ConsumerValidator{valA, valB, valC, valD}, 0) actualOptedInValidators = providerKeeper.GetAllOptedIn(ctx, "chainID") sortUpdates(actualOptedInValidators) require.Equal(t, expectedOptedInValidators, actualOptedInValidators) @@ -255,7 +270,7 @@ func TestOptInTopNValidators(t *testing.T) { providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valDConsAddr)) // Start Test 3: opt in all validators with power >= 2 and hence we do not expect to opt in validator A - providerKeeper.OptInTopNValidators(ctx, "chainID", []stakingtypes.Validator{valA, valB, valC, valD}, 2) + providerKeeper.OptInTopNValidators(ctx, "chainID", []types.ConsumerValidator{valA, valB, valC, valD}, 2) expectedOptedInValidators = []types.ProviderConsAddress{ types.NewProviderConsAddress(valBConsAddr), types.NewProviderConsAddress(valCConsAddr), @@ -274,7 +289,7 @@ func TestOptInTopNValidators(t *testing.T) { providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valDConsAddr)) // Start Test 4: opt in all validators with power >= 4 and hence we do not expect any opted-in validators - providerKeeper.OptInTopNValidators(ctx, "chainID", []stakingtypes.Validator{valA, valB, valC, valD}, 4) + providerKeeper.OptInTopNValidators(ctx, "chainID", []types.ConsumerValidator{valA, valB, valC, valD}, 4) require.Empty(t, providerKeeper.GetAllOptedIn(ctx, "chainID")) } @@ -291,59 +306,70 @@ func TestComputeMinPowerToOptIn(t *testing.T) { // 3 => 96% // 1 => 100% - bondedValidators := []stakingtypes.Validator{ - createStakingValidator(ctx, mocks, 1, 5), - createStakingValidator(ctx, mocks, 2, 10), - createStakingValidator(ctx, mocks, 3, 3), - createStakingValidator(ctx, mocks, 4, 1), - createStakingValidator(ctx, mocks, 5, 6), + valA, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 1, 5)) + require.NoError(t, err) + + valB, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 2, 10)) + require.NoError(t, err) + + valC, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 3, 3)) + require.NoError(t, err) + + valD, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 4, 1)) + require.NoError(t, err) + + valE, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 5, 6)) + require.NoError(t, err) + + consensusValidators := []types.ConsumerValidator{ + valA, valB, valC, valD, valE, } - m, err := providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 100) + m, err := providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 100) require.NoError(t, err) require.Equal(t, int64(1), m) - m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 97) + m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 97) require.NoError(t, err) require.Equal(t, int64(1), m) - m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 96) + m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 96) require.NoError(t, err) require.Equal(t, int64(3), m) - m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 85) + m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 85) require.NoError(t, err) require.Equal(t, int64(3), m) - m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 84) + m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 84) require.NoError(t, err) require.Equal(t, int64(5), m) - m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 65) + m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 65) require.NoError(t, err) require.Equal(t, int64(5), m) - m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 64) + m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 64) require.NoError(t, err) require.Equal(t, int64(6), m) - m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 41) + m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 41) require.NoError(t, err) require.Equal(t, int64(6), m) - m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 40) + m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 40) require.NoError(t, err) require.Equal(t, int64(10), m) - m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 1) + m, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 1) require.NoError(t, err) require.Equal(t, int64(10), m) // exceptional case when we erroneously call with `topN == 0` or `topN > 100` - _, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 0) + _, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 0) require.Error(t, err) - _, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 101) + _, err = providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", consensusValidators, 101) require.Error(t, err) } diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 2e8c9b46d4..71a6414ecd 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -262,11 +262,14 @@ func (k Keeper) MakeConsumerGenesis( // get the bonded validators from the staking module bondedValidators := k.stakingKeeper.GetLastValidators(ctx) + // get the consensus validators (to compute the minimal power in the top N) + consensusValidators := k.GetLastProviderConsensusValSet(ctx) + if topN, found := k.GetTopN(ctx, chainID); found && topN > 0 { // in a Top-N chain, we automatically opt in all validators that belong to the top N - minPower, err := k.ComputeMinPowerToOptIn(ctx, chainID, bondedValidators, prop.Top_N) + minPower, err := k.ComputeMinPowerToOptIn(ctx, chainID, consensusValidators, prop.Top_N) if err == nil { - k.OptInTopNValidators(ctx, chainID, bondedValidators, minPower) + k.OptInTopNValidators(ctx, chainID, consensusValidators, minPower) } } diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index ae1cbdb8dc..9800eea86c 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -219,7 +219,8 @@ func (k Keeper) SendVSCPacketsToChain(ctx sdk.Context, chainID, channelID string func (k Keeper) QueueVSCPackets(ctx sdk.Context) { valUpdateID := k.GetValidatorSetUpdateId(ctx) // current valset update ID - // get the bonded validators from the staking module + // get the last validator set sent to consensus + consensusValidators := k.GetLastProviderConsensusValSet(ctx) bondedValidators := k.stakingKeeper.GetLastValidators(ctx) for _, chain := range k.GetAllConsumerChains(ctx) { @@ -227,9 +228,9 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { if topN, found := k.GetTopN(ctx, chain.ChainId); found && topN > 0 { // in a Top-N chain, we automatically opt in all validators that belong to the top N - minPower, err := k.ComputeMinPowerToOptIn(ctx, chain.ChainId, bondedValidators, topN) + minPower, err := k.ComputeMinPowerToOptIn(ctx, chain.ChainId, consensusValidators, topN) if err == nil { - k.OptInTopNValidators(ctx, chain.ChainId, bondedValidators, minPower) + k.OptInTopNValidators(ctx, chain.ChainId, consensusValidators, minPower) } } diff --git a/x/ccv/provider/keeper/relay_test.go b/x/ccv/provider/keeper/relay_test.go index 5f74d93424..6c3a20cf06 100644 --- a/x/ccv/provider/keeper/relay_test.go +++ b/x/ccv/provider/keeper/relay_test.go @@ -760,14 +760,20 @@ func TestEndBlockVSU(t *testing.T) { // create 4 sample lastValidators var lastValidators []stakingtypes.Validator var valAddresses []sdk.ValAddress + var lastConsensusValidators []types.ConsumerValidator for i := 0; i < 4; i++ { validator := crypto.NewCryptoIdentityFromIntSeed(i).SDKStakingValidator() lastValidators = append(lastValidators, validator) valAddresses = append(valAddresses, validator.GetOperator()) mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), validator.GetOperator()).Return(int64(i + 1)).AnyTimes() + consVal, err := providerKeeper.CreateConsumerValidator(ctx, "chainId", validator) + require.NoError(t, err) + lastConsensusValidators = append(lastConsensusValidators, consVal) + } mocks.MockStakingKeeper.EXPECT().GetLastValidators(gomock.Any()).Return(lastValidators).AnyTimes() + providerKeeper.SetLastProviderConsensusValSet(ctx, lastConsensusValidators) // set a sample client for a consumer chain so that `GetAllConsumerChains` in `QueueVSCPackets` iterates at least once providerKeeper.SetConsumerClientId(ctx, chainID, "clientID") @@ -824,6 +830,15 @@ func TestQueueVSCPacketsWithPowerCapping(t *testing.T) { mocks.MockStakingKeeper.EXPECT().GetLastValidators(ctx).Return([]stakingtypes.Validator{valA, valB, valC, valD, valE}).AnyTimes() + // create the consensus validators + providerKeeper.SetLastProviderConsensusValSet(ctx, []types.ConsumerValidator{ + {ProviderConsAddr: valAConsAddr, Power: 1}, + {ProviderConsAddr: valBConsAddr, Power: 3}, + {ProviderConsAddr: valCConsAddr, Power: 4}, + {ProviderConsAddr: valDConsAddr, Power: 8}, + {ProviderConsAddr: valEConsAddr, Power: 16}, + }) + // add a consumer chain providerKeeper.SetConsumerClientId(ctx, "chainID", "clientID") diff --git a/x/ccv/provider/module.go b/x/ccv/provider/module.go index 748f5fc023..228cdcfc4b 100644 --- a/x/ccv/provider/module.go +++ b/x/ccv/provider/module.go @@ -159,10 +159,17 @@ func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.V am.keeper.EndBlockCIS(ctx) // EndBlock logic needed for the Consumer Chain Removal sub-protocol am.keeper.EndBlockCCR(ctx) + + // logic to update the provider consensus validator set. + // Important: must be called before EndBlockVSU, because + // EndBlockVSU needs to know the updated provider validator set + // to compute the minimum power in the top N + providerUpdates := am.keeper.ProviderValidatorUpdates(ctx) + // EndBlock logic needed for the Validator Set Update sub-protocol am.keeper.EndBlockVSU(ctx) - return am.keeper.ProviderValidatorUpdates(ctx) + return providerUpdates } // AppModuleSimulation functions From 70a6bf1a53ad7c7d19c2ff56ed58f94bc8b5c1cd Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 24 May 2024 11:48:13 +0200 Subject: [PATCH 19/28] Adjust expected calls --- x/ccv/provider/keeper/keeper.go | 2 +- x/ccv/provider/keeper/keeper_test.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 241b330844..aef1f9fe21 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -257,7 +257,7 @@ func (k Keeper) GetAllConsumerChains(ctx sdk.Context) (chains []types.Chain) { var minPowerInTopN int64 if found && topN > 0 { - res, err := k.ComputeMinPowerToOptIn(ctx, chainID, k.stakingKeeper.GetLastValidators(ctx), topN) + res, err := k.ComputeMinPowerToOptIn(ctx, chainID, k.GetLastProviderConsensusValSet(ctx), topN) if err != nil { k.Logger(ctx).Error("failed to compute min power to opt in for chain", "chain", chainID, "error", err) minPowerInTopN = -1 diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index 894011cfab..1d04d1ebfa 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -417,6 +417,15 @@ func TestGetAllConsumerChains(t *testing.T) { mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), val.GetOperator()).Return(powers[i]).AnyTimes() } + consensusValSet := []types.ConsumerValidator{ + {ProviderConsAddr: []byte("providerAddr1"), Power: 50}, + {ProviderConsAddr: []byte("providerAddr2"), Power: 150}, + {ProviderConsAddr: []byte("providerAddr3"), Power: 300}, + {ProviderConsAddr: []byte("providerAddr4"), Power: 500}, + } + + pk.SetLastProviderConsensusValSet(ctx, consensusValSet) + // set Top N parameters, client ids and expected result topNs := []uint32{0, 70, 90, 100} expectedMinPowerInTopNs := []int64{ From 12658369e7c7180dae2164b7ab7c1a6d6e779b14 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 24 May 2024 11:58:08 +0200 Subject: [PATCH 20/28] Set ProviderConsensusValSet in proposal test --- x/ccv/provider/keeper/proposal_test.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index c03fa4f71b..f464232cb1 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -3,11 +3,12 @@ package keeper_test import ( "bytes" "encoding/json" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "sort" "testing" "time" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" _go "github.com/cosmos/ics23/go" @@ -21,6 +22,7 @@ import ( cryptotestutil "github.com/cosmos/interchain-security/v4/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v4/testutil/keeper" providerkeeper "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" + "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" providertypes "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ccvtypes "github.com/cosmos/interchain-security/v4/x/ccv/types" ) @@ -1050,6 +1052,14 @@ func TestBeginBlockInit(t *testing.T) { mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), validator.GetOperator()).Return(int64(1)).AnyTimes() providerKeeper.SetOptedIn(ctx, pendingProps[4].ChainId, providertypes.NewProviderConsAddress(consAddr)) + providerKeeper.SetLastProviderConsensusValSet(ctx, []types.ConsumerValidator{ + { + ProviderConsAddr: consAddr, + Power: 1, + }, + }, + ) + providerKeeper.BeginBlockInit(ctx) // first proposal is not pending anymore because its spawn time already passed and was executed From affd10fa4dee8ea768a8839e9bd0344f5fa2bba1 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 24 May 2024 12:00:30 +0200 Subject: [PATCH 21/28] Extract variable --- x/ccv/provider/keeper/proposal_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index f464232cb1..78f92a9ef4 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -1048,14 +1048,15 @@ func TestBeginBlockInit(t *testing.T) { // opt in a sample validator so the chain's proposal can successfully execute validator := cryptotestutil.NewCryptoIdentityFromIntSeed(0).SDKStakingValidator() consAddr, _ := validator.GetConsAddr() + power := int64(1) mocks.MockStakingKeeper.EXPECT().GetLastValidators(gomock.Any()).Return([]stakingtypes.Validator{validator}).AnyTimes() - mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), validator.GetOperator()).Return(int64(1)).AnyTimes() + mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), validator.GetOperator()).Return(power).AnyTimes() providerKeeper.SetOptedIn(ctx, pendingProps[4].ChainId, providertypes.NewProviderConsAddress(consAddr)) providerKeeper.SetLastProviderConsensusValSet(ctx, []types.ConsumerValidator{ { ProviderConsAddr: consAddr, - Power: 1, + Power: power, }, }, ) From 5ea92ea7cc55bb9d5cf35949b6901117eb927ce4 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 24 May 2024 12:21:08 +0200 Subject: [PATCH 22/28] Improve comment --- tests/e2e/steps_inactive_vals.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/e2e/steps_inactive_vals.go b/tests/e2e/steps_inactive_vals.go index 982f8f54e5..50e2d12b39 100644 --- a/tests/e2e/steps_inactive_vals.go +++ b/tests/e2e/steps_inactive_vals.go @@ -2,7 +2,9 @@ package main import clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" -// stepsOptInChain starts a provider chain and an Opt-In chain and opts in and out validators +// stepsInactiveValidatorsOnConsumer tests situations where validators that are *not* in the active set on the +// provider chain validate on the consumer chain. +// The provider chain is set to have at most *2* validators active in consensus, and there are 3 validators in total. // high-level, this test does: // - start the provider chain // - start a consumer chain From 9822bbf152b8d9914477605fc82836f7e9a68b70 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 5 Jun 2024 13:16:21 +0200 Subject: [PATCH 23/28] Comment on slashing vs jailing --- tests/e2e/steps_inactive_vals.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e/steps_inactive_vals.go b/tests/e2e/steps_inactive_vals.go index 50e2d12b39..d4b9eec0dc 100644 --- a/tests/e2e/steps_inactive_vals.go +++ b/tests/e2e/steps_inactive_vals.go @@ -306,7 +306,8 @@ func stepsInactiveValidatorsOnConsumer() []Step { State: State{ ChainID("provi"): ChainState{ ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 100, // alice is back as an active consensus validator + // alice was not slashed because consumer downtime just jails without slashing tokens + ValidatorID("alice"): 100, // alice is back as an active consensus validator. ValidatorID("bob"): 0, // bob is still jailed ValidatorID("carol"): 500, }, From b29b867e4b56cfb333dff1a08b20ce9c6ee891ff Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 5 Jun 2024 22:11:11 +0200 Subject: [PATCH 24/28] Update e2e test --- tests/e2e/config.go | 5 + tests/e2e/steps_inactive_vals.go | 118 ++++++------------ .../keeper/validator_set_update_test.go | 36 ++---- 3 files changed, 47 insertions(+), 112 deletions(-) diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 767bb0aad9..5589daa759 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -516,6 +516,11 @@ func InactiveValsConfig() TestConfig { tr.chainConfigs[ChainID("provi")] = proviConfig tr.chainConfigs[ChainID("consu")] = consuConfig + // make is to that carol does not use a consumer key + carolConfig := tr.validatorConfigs[ValidatorID("carol")] + carolConfig.UseConsumerKey = false + tr.validatorConfigs[ValidatorID("carol")] = carolConfig + return tr } diff --git a/tests/e2e/steps_inactive_vals.go b/tests/e2e/steps_inactive_vals.go index d4b9eec0dc..5ae1a087f7 100644 --- a/tests/e2e/steps_inactive_vals.go +++ b/tests/e2e/steps_inactive_vals.go @@ -79,19 +79,19 @@ func stepsInactiveValidatorsOnConsumer() []Step { Chain: ChainID("provi"), From: ValidatorID("carol"), To: ValidatorID("carol"), - Amount: 200000000, // carol needs to have more than 2/3rds of power(carol) + power(bob), so if bob has 200 power, carol needs at least 401, so we just go for 500 + Amount: 1000000000, // carol needs to have more than 2/3rds of power(alice) + power(carol) + power(bob) to run both chains alone, so we stake some more to her }, State: State{ ChainID("provi"): ChainState{ ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 0, ValidatorID("bob"): 200, - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, StakedTokens: &map[ValidatorID]uint{ ValidatorID("alice"): 100000000, ValidatorID("bob"): 200000000, - ValidatorID("carol"): 500000000, + ValidatorID("carol"): 1000000000, }, // check that bob and carol get rewards, but alice does not Rewards: &Rewards{ @@ -117,12 +117,12 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 100, // alice gets into the active set ValidatorID("bob"): 0, // bob is jailed - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, StakedTokens: &map[ValidatorID]uint{ ValidatorID("alice"): 100000000, ValidatorID("bob"): 198000000, // 1% slash - ValidatorID("carol"): 500000000, + ValidatorID("carol"): 1000000000, }, }, }, @@ -152,7 +152,7 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 100, ValidatorID("bob"): 0, - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, }, }, @@ -168,7 +168,7 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus ValidatorID("bob"): 198, // bob was slashed 1% - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, // check that between two blocks now, alice does not get rewarded with the native denom Rewards: &Rewards{ @@ -204,7 +204,7 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 100, ValidatorID("bob"): 198, - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, }, }, @@ -220,14 +220,14 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 100, // power not affected yet ValidatorID("bob"): 198, - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, }, ChainID("provi"): ChainState{ ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 0, // alice is not consensus-active anyways, since we allow two vals at maximum ValidatorID("bob"): 198, - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, }, }, @@ -243,9 +243,10 @@ func stepsInactiveValidatorsOnConsumer() []Step { State: State{ ChainID("provi"): ChainState{ ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // alice is still not in the active set, and should now be jailed too + ValidatorID("alice"): 0, // alice is still not in the active set, and should now be jailed too. + // we cannot test directly whether alice is jailed, but we will test this below ValidatorID("bob"): 198, - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, }, }, @@ -261,7 +262,7 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 0, // alice is jailed ValidatorID("bob"): 0, // bob is jailed - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, }, }, @@ -279,7 +280,7 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 0, // alice is jailed ValidatorID("bob"): 0, // bob is jailed - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, }, ChainID("provi"): ChainState{ @@ -296,7 +297,6 @@ func stepsInactiveValidatorsOnConsumer() []Step { }, }, }, - // unjail alice { Action: UnjailValidatorAction{ @@ -309,7 +309,7 @@ func stepsInactiveValidatorsOnConsumer() []Step { // alice was not slashed because consumer downtime just jails without slashing tokens ValidatorID("alice"): 100, // alice is back as an active consensus validator. ValidatorID("bob"): 0, // bob is still jailed - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, }, }, @@ -325,7 +325,7 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus ValidatorID("bob"): 196, // bob is back as an active consensus validator and lost 2 more power due to the second downtime - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, }, }, @@ -343,7 +343,7 @@ func stepsInactiveValidatorsOnConsumer() []Step { ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 100, // both alice and bob are validating the consumer ValidatorID("bob"): 196, - ValidatorID("carol"): 500, + ValidatorID("carol"): 1000, }, }, }, @@ -388,9 +388,7 @@ func setupOptInChain() []Step { }, }, }, - // Οpt in "alice" and "bob" so the chain is not empty when it is about to start. Note, that "alice" and "bob" use - // the provider's public key (see `UseConsumerKey` is set to `false` in `getDefaultValidators`) and hence do not - // need a consumer-key assignment. + // Οpt in all validators { Action: OptInAction{ Chain: ChainID("consu"), @@ -421,6 +419,21 @@ func setupOptInChain() []Step { }, }, }, + { + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("carol"), + }, + State: State{ + ChainID("provi"): ChainState{ + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, + ValidatorID("bob"): {}, + ValidatorID("carol"): {}, + }, + }, + }, + }, { Action: VoteGovProposalAction{ Chain: ChainID("provi"), @@ -465,8 +478,7 @@ func setupOptInChain() []Step { ValPowers: &map[ValidatorID]uint{ ValidatorID("alice"): 100, ValidatorID("bob"): 200, - // carol has not yet opted in - ValidatorID("carol"): 0, + ValidatorID("carol"): 300, }, }, }, @@ -491,65 +503,5 @@ func setupOptInChain() []Step { }, State: State{}, }, - { - Action: OptInAction{ - Chain: ChainID("consu"), - Validator: ValidatorID("carol"), - }, - State: State{ - ChainID("consu"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 100, - ValidatorID("bob"): 200, - // "carol" has opted in, but the VSCPacket capturing the opt-in was not relayed yet - ValidatorID("carol"): 0, - }, - }, - ChainID("provi"): ChainState{ - HasToValidate: &map[ValidatorID][]ChainID{ - ValidatorID("alice"): {"consu"}, - ValidatorID("bob"): {"consu"}, - ValidatorID("carol"): {"consu"}, - }, - }, - }, - }, - { - // assign the consumer key "carol" is using on the consumer chain to be the one "carol" uses when opting in - Action: AssignConsumerPubKeyAction{ - Chain: ChainID("consu"), - Validator: ValidatorID("carol"), - // reconfigure the node -> validator was using provider key - // until this point -> key matches config.consumerValPubKey for "carol" - ConsumerPubkey: getDefaultValidators()[ValidatorID("carol")].ConsumerValPubKey, - ReconfigureNode: true, - }, - State: State{}, - }, - { - Action: RelayPacketsAction{ - ChainA: ChainID("provi"), - ChainB: ChainID("consu"), - Port: "provider", - Channel: 0, - }, - State: State{ - ChainID("consu"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 100, - ValidatorID("bob"): 200, - // carol has now opted in - ValidatorID("carol"): 300, - }, - }, - ChainID("provi"): ChainState{ - HasToValidate: &map[ValidatorID][]ChainID{ - ValidatorID("alice"): {"consu"}, - ValidatorID("bob"): {"consu"}, - ValidatorID("carol"): {"consu"}, - }, - }, - }, - }, } } diff --git a/x/ccv/provider/keeper/validator_set_update_test.go b/x/ccv/provider/keeper/validator_set_update_test.go index 3ce6b34e90..ec1667892e 100644 --- a/x/ccv/provider/keeper/validator_set_update_test.go +++ b/x/ccv/provider/keeper/validator_set_update_test.go @@ -7,13 +7,9 @@ import ( "github.com/stretchr/testify/require" - 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" abci "github.com/cometbft/cometbft/abci/types" - "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cometbft/cometbft/proto/tendermint/crypto" cryptotestutil "github.com/cosmos/interchain-security/v4/testutil/crypto" @@ -110,24 +106,6 @@ func createConsumerValidator(index int, power int64, seed int) (types.ConsumerVa }, publicKey } -// createStakingValidator helper function to generate a validator with the given power and with a provider address based on index -func createStakingValidator(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) - providerValidatorAddr := sdk.ValAddress(providerAddr.Address.Bytes()) - - mocks.MockStakingKeeper.EXPECT(). - GetLastValidatorPower(ctx, providerValidatorAddr).Return(power).AnyTimes() - - return stakingtypes.Validator{ - OperatorAddress: providerValidatorAddr.String(), - ConsensusPubkey: pkAny, - } -} - 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: @@ -340,7 +318,7 @@ func TestFilterValidatorsConsiderAll(t *testing.T) { var expectedValidators []types.ConsumerValidator // create a staking validator A that has not set a consumer public key - valA := createStakingValidator(ctx, mocks, 1, 1) + valA := testkeeper.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() @@ -351,7 +329,7 @@ func TestFilterValidatorsConsiderAll(t *testing.T) { }) // create a staking validator B that has set a consumer public key - valB := createStakingValidator(ctx, mocks, 2, 2) + valB := testkeeper.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() @@ -382,7 +360,7 @@ func TestFilterValidatorsConsiderOnlyOptIn(t *testing.T) { var expectedValidators []types.ConsumerValidator // create a staking validator A that has not set a consumer public key - valA := createStakingValidator(ctx, mocks, 1, 1) + valA := testkeeper.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() @@ -394,7 +372,7 @@ func TestFilterValidatorsConsiderOnlyOptIn(t *testing.T) { expectedValidators = append(expectedValidators, expectedValAConsumerValidator) // create a staking validator B that has set a consumer public key - valB := createStakingValidator(ctx, mocks, 2, 2) + valB := testkeeper.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() @@ -429,7 +407,7 @@ func TestFilterValidatorsConsiderOnlyOptIn(t *testing.T) { require.Equal(t, expectedValidators, actualValidators) // create a staking validator C that is not opted in, hence `expectedValidators` remains the same - valC := createStakingValidator(ctx, mocks, 3, 3) + valC := testkeeper.CreateStakingValidator(ctx, mocks, 3, 3) bondedValidators = []stakingtypes.Validator{valA, valB, valC} actualValidators = providerKeeper.FilterValidators(ctx, "chainID", bondedValidators, func(providerAddr types.ProviderConsAddress) bool { @@ -448,7 +426,7 @@ func TestCreateConsumerValidator(t *testing.T) { chainID := "chainID" // create a validator which has set a consumer public key - valA := createStakingValidator(ctx, mocks, 0, 1) + valA := testkeeper.CreateStakingValidator(ctx, mocks, 0, 1) valAConsumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() valAConsAddr, _ := valA.GetConsAddr() valAProviderConsAddr := types.NewProviderConsAddress(valAConsAddr) @@ -463,7 +441,7 @@ func TestCreateConsumerValidator(t *testing.T) { require.NoError(t, err) // create a validator which has not set a consumer public key - valB := createStakingValidator(ctx, mocks, 1, 2) + valB := testkeeper.CreateStakingValidator(ctx, mocks, 1, 2) valBConsAddr, _ := valB.GetConsAddr() valBProviderConsAddr := types.NewProviderConsAddress(valBConsAddr) valBPublicKey, _ := valB.TmConsPublicKey() From 9b5e578ceb6421264a81e198f5abbcb21688e1b1 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Tue, 11 Jun 2024 15:29:51 +0200 Subject: [PATCH 25/28] Remove commented code --- app/provider/app.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/provider/app.go b/app/provider/app.go index 4590a6f662..9d4f668172 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -134,7 +134,6 @@ var ( genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), bank.AppModuleBasic{}, capability.AppModuleBasic{}, - // staking.AppModuleBasic{}, wrapped_staking.AppModuleBasic{}, mint.AppModuleBasic{}, distr.AppModuleBasic{}, @@ -519,7 +518,6 @@ func New( mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName)), distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), - // staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), wrapped_staking.NewAppModule(appCodec, *app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), upgrade.NewAppModule(&app.UpgradeKeeper), evidence.NewAppModule(app.EvidenceKeeper), @@ -615,7 +613,6 @@ func New( capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), - // staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), wrapped_staking.NewAppModule(appCodec, *app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName)), From bfb83ab66c44db526c0f4b0f71ba274da4a780eb Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Mon, 24 Jun 2024 12:41:45 +0200 Subject: [PATCH 26/28] Move createStakingValidator to testkeeper --- x/ccv/provider/keeper/grpc_query_test.go | 11 ++++--- .../keeper/partial_set_security_test.go | 32 +++++++++---------- x/ccv/provider/keeper/relay_test.go | 10 +++--- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/x/ccv/provider/keeper/grpc_query_test.go b/x/ccv/provider/keeper/grpc_query_test.go index 71a9b82b7e..ce2bc2c5d1 100644 --- a/x/ccv/provider/keeper/grpc_query_test.go +++ b/x/ccv/provider/keeper/grpc_query_test.go @@ -1,11 +1,12 @@ package keeper_test import ( - "github.com/cometbft/cometbft/proto/tendermint/crypto" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "testing" "time" + "github.com/cometbft/cometbft/proto/tendermint/crypto" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" sdktypes "github.com/cosmos/cosmos-sdk/types" @@ -177,7 +178,7 @@ func TestQueryConsumerChainsValidatorHasToValidate(t *testing.T) { pk, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - val := createStakingValidator(ctx, mocks, 1, 1) + val := testkeeper.CreateStakingValidator(ctx, mocks, 1, 1) valConsAddr, _ := val.GetConsAddr() providerAddr := types.NewProviderConsAddress(valConsAddr) mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valConsAddr).Return(val, true).AnyTimes() @@ -200,7 +201,9 @@ func TestQueryConsumerChainsValidatorHasToValidate(t *testing.T) { ConsumerPublicKey: &crypto.PublicKey{ Sum: &crypto.PublicKey_Ed25519{ Ed25519: []byte{1}, - }}}) + }, + }, + }) // set `providerAddr` as an opted-in validator on "chain3" pk.SetOptedIn(ctx, "chain3", providerAddr) diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go index 2c16a0594b..be585f511b 100644 --- a/x/ccv/provider/keeper/partial_set_security_test.go +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -116,25 +116,25 @@ func TestHandleOptOutFromTopNChain(t *testing.T) { // set the chain as Top 50 and create 4 validators with 10%, 20%, 30%, and 40% of the total voting power // respectively providerKeeper.SetTopN(ctx, "chainID", 50) - stakingValA := createStakingValidator(ctx, mocks, 1, 1) + stakingValA := testkeeper.CreateStakingValidator(ctx, mocks, 1, 1) valA, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", stakingValA) // 10% of the total voting power (can opt out) require.NoError(t, err) valAConsAddr := valA.ProviderConsAddr mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valAConsAddr).Return(stakingValA, true).AnyTimes() - stakingValB := createStakingValidator(ctx, mocks, 2, 2) + stakingValB := testkeeper.CreateStakingValidator(ctx, mocks, 2, 2) valB, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", stakingValB) // 20% of the total voting power (can opt out) require.NoError(t, err) valBConsAddr := valB.ProviderConsAddr mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valBConsAddr).Return(stakingValB, true).AnyTimes() - stakingValC := createStakingValidator(ctx, mocks, 3, 3) + stakingValC := testkeeper.CreateStakingValidator(ctx, mocks, 3, 3) valC, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", stakingValC) // 30% of the total voting power (cannot opt out) require.NoError(t, err) valCConsAddr := valC.ProviderConsAddr mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valCConsAddr).Return(stakingValC, true).AnyTimes() - stakingValD := createStakingValidator(ctx, mocks, 4, 4) + stakingValD := testkeeper.CreateStakingValidator(ctx, mocks, 4, 4) valD, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", stakingValD) // 40% of the total voting power (cannot opt out) require.NoError(t, err) valDConsAddr := valD.ProviderConsAddr @@ -158,7 +158,7 @@ func TestHandleOptOutFromTopNChain(t *testing.T) { require.Error(t, providerKeeper.HandleOptOut(ctx, chainID, types.NewProviderConsAddress(valDConsAddr))) // opting out a validator that cannot be found from a Top N chain should also return an error - notFoundValidator := createStakingValidator(ctx, mocks, 5, 5) + notFoundValidator := testkeeper.CreateStakingValidator(ctx, mocks, 5, 5) notFoundValidatorConsAddr, _ := notFoundValidator.GetConsAddr() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, notFoundValidatorConsAddr). Return(stakingtypes.Validator{}, false) @@ -216,16 +216,16 @@ func TestOptInTopNValidators(t *testing.T) { defer ctrl.Finish() // create 4 validators with powers 1, 2, 3, and 1 respectively - valA, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 1, 1)) + valA, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", testkeeper.CreateStakingValidator(ctx, mocks, 1, 1)) require.NoError(t, err) valAConsAddr := valA.ProviderConsAddr - valB, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 2, 2)) + valB, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", testkeeper.CreateStakingValidator(ctx, mocks, 2, 2)) require.NoError(t, err) valBConsAddr := valB.ProviderConsAddr - valC, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 3, 3)) + valC, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", testkeeper.CreateStakingValidator(ctx, mocks, 3, 3)) require.NoError(t, err) valCConsAddr := valC.ProviderConsAddr - valD, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 4, 1)) + valD, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", testkeeper.CreateStakingValidator(ctx, mocks, 4, 1)) require.NoError(t, err) valDConsAddr := valD.ProviderConsAddr @@ -306,19 +306,19 @@ func TestComputeMinPowerToOptIn(t *testing.T) { // 3 => 96% // 1 => 100% - valA, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 1, 5)) + valA, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", testkeeper.CreateStakingValidator(ctx, mocks, 1, 5)) require.NoError(t, err) - valB, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 2, 10)) + valB, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", testkeeper.CreateStakingValidator(ctx, mocks, 2, 10)) require.NoError(t, err) - valC, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 3, 3)) + valC, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", testkeeper.CreateStakingValidator(ctx, mocks, 3, 3)) require.NoError(t, err) - valD, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 4, 1)) + valD, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", testkeeper.CreateStakingValidator(ctx, mocks, 4, 1)) require.NoError(t, err) - valE, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", createStakingValidator(ctx, mocks, 5, 6)) + valE, err := providerKeeper.CreateConsumerValidator(ctx, "chainID", testkeeper.CreateStakingValidator(ctx, mocks, 5, 6)) require.NoError(t, err) consensusValidators := []types.ConsumerValidator{ @@ -378,7 +378,7 @@ func TestFilterOptedInAndAllowAndDenylistedPredicate(t *testing.T) { providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - validator := createStakingValidator(ctx, mocks, 0, 1) + validator := testkeeper.CreateStakingValidator(ctx, mocks, 0, 1) consAddr, _ := validator.GetConsAddr() providerAddr := types.NewProviderConsAddress(consAddr) @@ -388,7 +388,7 @@ func TestFilterOptedInAndAllowAndDenylistedPredicate(t *testing.T) { require.True(t, providerKeeper.FilterOptedInAndAllowAndDenylistedPredicate(ctx, "chainID", providerAddr)) // create an allow list but do not add the validator `providerAddr` to it - validatorA := createStakingValidator(ctx, mocks, 1, 1) + validatorA := testkeeper.CreateStakingValidator(ctx, mocks, 1, 1) consAddrA, _ := validatorA.GetConsAddr() providerKeeper.SetAllowlist(ctx, "chainID", types.NewProviderConsAddress(consAddrA)) require.False(t, providerKeeper.FilterOptedInAndAllowAndDenylistedPredicate(ctx, "chainID", providerAddr)) diff --git a/x/ccv/provider/keeper/relay_test.go b/x/ccv/provider/keeper/relay_test.go index 6c3a20cf06..157833128b 100644 --- a/x/ccv/provider/keeper/relay_test.go +++ b/x/ccv/provider/keeper/relay_test.go @@ -808,22 +808,22 @@ func TestQueueVSCPacketsWithPowerCapping(t *testing.T) { providerKeeper.SetValidatorSetUpdateId(ctx, 1) - valA := createStakingValidator(ctx, mocks, 1, 1) // 3.125% of the total voting power + valA := testkeeper.CreateStakingValidator(ctx, mocks, 1, 1) // 3.125% of the total voting power valAConsAddr, _ := valA.GetConsAddr() valAPubKey, _ := valA.TmConsPublicKey() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valAConsAddr).Return(valA, true).AnyTimes() - valB := createStakingValidator(ctx, mocks, 2, 3) // 9.375% of the total voting power + valB := testkeeper.CreateStakingValidator(ctx, mocks, 2, 3) // 9.375% of the total voting power valBConsAddr, _ := valB.GetConsAddr() valBPubKey, _ := valB.TmConsPublicKey() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valBConsAddr).Return(valB, true).AnyTimes() - valC := createStakingValidator(ctx, mocks, 3, 4) // 12.5% of the total voting power + valC := testkeeper.CreateStakingValidator(ctx, mocks, 3, 4) // 12.5% of the total voting power valCConsAddr, _ := valC.GetConsAddr() valCPubKey, _ := valC.TmConsPublicKey() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valCConsAddr).Return(valC, true).AnyTimes() - valD := createStakingValidator(ctx, mocks, 4, 8) // 25% of the total voting power + valD := testkeeper.CreateStakingValidator(ctx, mocks, 4, 8) // 25% of the total voting power valDConsAddr, _ := valD.GetConsAddr() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valDConsAddr).Return(valD, true).AnyTimes() - valE := createStakingValidator(ctx, mocks, 5, 16) // 50% of the total voting power + valE := testkeeper.CreateStakingValidator(ctx, mocks, 5, 16) // 50% of the total voting power valEConsAddr, _ := valE.GetConsAddr() valEPubKey, _ := valE.TmConsPublicKey() mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, valEConsAddr).Return(valE, true).AnyTimes() From a7de72a9d4d9f442ee66dd0d50241671bbcf2505 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Mon, 24 Jun 2024 12:47:10 +0200 Subject: [PATCH 27/28] Address comments --- x/ccv/provider/keeper/keeper.go | 6 +++--- x/ccv/provider/types/params.go | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index aef1f9fe21..582f42a71c 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -1298,14 +1298,14 @@ func (k Keeper) HasToValidate( return true, nil } - validators := k.GetLastProviderConsensusValSet(ctx) + providerValidators := k.GetLastProviderConsensusValSet(ctx) // if the validator was not part of the last epoch, check if the validator is going to be part of te next epoch if topN, found := k.GetTopN(ctx, chainID); found && topN > 0 { // in a Top-N chain, we automatically opt in all validators that belong to the top N - minPower, err := k.ComputeMinPowerToOptIn(ctx, chainID, validators, topN) + minPower, err := k.ComputeMinPowerToOptIn(ctx, chainID, providerValidators, topN) if err == nil { - k.OptInTopNValidators(ctx, chainID, validators, minPower) + k.OptInTopNValidators(ctx, chainID, providerValidators, minPower) } } diff --git a/x/ccv/provider/types/params.go b/x/ccv/provider/types/params.go index 664cf3db94..83adffab0f 100644 --- a/x/ccv/provider/types/params.go +++ b/x/ccv/provider/types/params.go @@ -156,6 +156,9 @@ func (p Params) Validate() error { if err := ccvtypes.ValidateInt64(p.BlocksPerEpoch); err != nil { return fmt.Errorf("blocks per epoch is invalid: %s", err) } + if err := ccvtypes.ValidatePositiveInt64(p.MaxProviderConsensusValidators); err != nil { + return fmt.Errorf("max provider consensus validators is invalid: %s", err) + } return nil } From faff1a7bef02ce64424f759442c5e69ed8fdebdf Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Mon, 24 Jun 2024 16:56:43 +0200 Subject: [PATCH 28/28] Address comments --- x/ccv/provider/keeper/relay.go | 10 +++++++++- x/ccv/provider/module.go | 10 +--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 9800eea86c..9f78a39138 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -146,7 +146,13 @@ func (k Keeper) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet) err // EndBlockVSU contains the EndBlock logic needed for // the Validator Set Update sub-protocol -func (k Keeper) EndBlockVSU(ctx sdk.Context) { +func (k Keeper) EndBlockVSU(ctx sdk.Context) []abci.ValidatorUpdate { + // logic to update the provider consensus validator set. + // Important: must be called before the rest of EndBlockVSU, because + // EndBlockVSU needs to know the updated provider validator set + // to compute the minimum power in the top N + providerUpdates := k.ProviderValidatorUpdates(ctx) + // notify the staking module to complete all matured unbonding ops k.completeMaturedUnbondingOps(ctx) @@ -161,6 +167,8 @@ func (k Keeper) EndBlockVSU(ctx sdk.Context) { // the updates will remain queued until the channel is established k.SendVSCPackets(ctx) } + + return providerUpdates } // SendVSCPackets iterates over all registered consumers and sends pending diff --git a/x/ccv/provider/module.go b/x/ccv/provider/module.go index 228cdcfc4b..5e033ab57c 100644 --- a/x/ccv/provider/module.go +++ b/x/ccv/provider/module.go @@ -160,16 +160,8 @@ func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.V // EndBlock logic needed for the Consumer Chain Removal sub-protocol am.keeper.EndBlockCCR(ctx) - // logic to update the provider consensus validator set. - // Important: must be called before EndBlockVSU, because - // EndBlockVSU needs to know the updated provider validator set - // to compute the minimum power in the top N - providerUpdates := am.keeper.ProviderValidatorUpdates(ctx) - // EndBlock logic needed for the Validator Set Update sub-protocol - am.keeper.EndBlockVSU(ctx) - - return providerUpdates + return am.keeper.EndBlockVSU(ctx) } // AppModuleSimulation functions