Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: introduce power shaping #1830

Merged
merged 20 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions proto/interchain_security/ccv/provider/v1/provider.proto
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ message ConsumerAdditionProposal {
// have to validate the proposed consumer chain. top_N can either be 0 or any value in [50, 100].
// A chain can join with top_N == 0 as an Opt In chain, or with top_N ∈ [50, 100] as a Top N chain.
uint32 top_N = 15;
// Corresponds to the maximum power (percentage-wise) a validator can have on the consumer chain. For instance, if
// `validators_power_cap` is set to 32, it means that no validator can have more than 32% of the voting power on the
// consumer chain. Note that this might not be feasible. For example, think of a consumer chain with only
// 5 validators and with `validators_power_cap` set to 10%. In such a scenario, at least one validator would need
// to have more than 20% of the total voting power. Therefore, `validators_power_cap` operates on a best-effort basis.
uint32 validators_power_cap = 16;
// Corresponds to the maximum number of validators that can validate a consumer chain.
// Only applicable to Opt In chains. Setting `validator_set_cap` on a Top N chain is a no-op.
uint32 validator_set_cap = 17;
// Corresponds to a list of provider consensus addresses of validators that are the ONLY ones that can validate
// the consumer chain.
repeated string allowlist = 18;
// Corresponds to a list of provider consensus addresses of validators that CANNOT validate the consumer chain.
repeated string denylist = 19;
}

// ConsumerRemovalProposal is a governance proposal on the provider chain to
Expand Down
4 changes: 3 additions & 1 deletion tests/mbt/driver/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/cosmos/interchain-security/v4/testutil/integration"
simibc "github.com/cosmos/interchain-security/v4/testutil/simibc"
consumertypes "github.com/cosmos/interchain-security/v4/x/ccv/consumer/types"
"github.com/cosmos/interchain-security/v4/x/ccv/provider/types"
ccvtypes "github.com/cosmos/interchain-security/v4/x/ccv/types"
)

Expand Down Expand Up @@ -377,7 +378,8 @@ func (s *Driver) ConfigureNewPath(consumerChain, providerChain *ibctesting.TestC
stakingValidators = append(stakingValidators, v)
}

nextValidators := s.providerKeeper().ComputeNextEpochConsumerValSet(s.providerCtx(), string(consumerChainId), stakingValidators, func(validator stakingtypes.Validator) bool { return true })
considerAll := func(providerAddr types.ProviderConsAddress) bool { return true }
nextValidators := s.providerKeeper().FilterValidators(s.providerCtx(), string(consumerChainId), stakingValidators, considerAll)
s.providerKeeper().SetConsumerValSet(s.providerCtx(), string(consumerChainId), nextValidators)

err = s.providerKeeper().SetConsumerGenesis(
Expand Down
8 changes: 8 additions & 0 deletions testutil/ibc_testing/generic_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
testutil "github.com/cosmos/interchain-security/v4/testutil/integration"
testkeeper "github.com/cosmos/interchain-security/v4/testutil/keeper"
consumerkeeper "github.com/cosmos/interchain-security/v4/x/ccv/consumer/keeper"
providertypes "github.com/cosmos/interchain-security/v4/x/ccv/provider/types"
)

type (
Expand Down Expand Up @@ -140,6 +141,13 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp](
prop := testkeeper.GetTestConsumerAdditionProp()
prop.ChainId = chainID
prop.Top_N = consumerTopNParams[index] // isn't used in CreateConsumerClient

// opt-in all validators
for _, v := range providerApp.GetTestStakingKeeper().GetLastValidators(providerChain.GetContext()) {
consAddr, _ := v.GetConsAddr()
providerKeeper.SetOptedIn(providerChain.GetContext(), chainID, providertypes.NewProviderConsAddress(consAddr))
}

// NOTE: the initial height passed to CreateConsumerClient
// must be the height on the consumer when InitGenesis is called
prop.InitialHeight = clienttypes.Height{RevisionNumber: 0, RevisionHeight: 3}
Expand Down
4 changes: 4 additions & 0 deletions testutil/keeper/unit_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,10 @@ func GetTestConsumerAdditionProp() *providertypes.ConsumerAdditionProposal {
types.DefaultTransferTimeoutPeriod,
types.DefaultConsumerUnbondingPeriod,
0,
0,
0,
nil,
nil,
).(*providertypes.ConsumerAdditionProposal)

return prop
Expand Down
16 changes: 13 additions & 3 deletions x/ccv/provider/client/proposal_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ Where proposal.json contains:
"ccv_timeout_period": 2419200000000000,
"unbonding_period": 1728000000000000,
"deposit": "10000stake",
"top_n": 0,
"top_n": 0,
"validators_power_cap": 32,
"validator_set_cap": 50,
"allowlist": [],
"denylist": ["validatorAConsensusAddress", "validatorBConsensusAddress"]
}
`,
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -87,7 +91,8 @@ Where proposal.json contains:
proposal.GenesisHash, proposal.BinaryHash, proposal.SpawnTime,
proposal.ConsumerRedistributionFraction, proposal.BlocksPerDistributionTransmission,
proposal.DistributionTransmissionChannel, proposal.HistoricalEntries,
proposal.CcvTimeoutPeriod, proposal.TransferTimeoutPeriod, proposal.UnbondingPeriod, proposal.TopN)
proposal.CcvTimeoutPeriod, proposal.TransferTimeoutPeriod, proposal.UnbondingPeriod, proposal.TopN,
proposal.ValidatorsPowerCap, proposal.ValidatorSetCap, proposal.Allowlist, proposal.Denylist)

from := clientCtx.GetFromAddress()

Expand Down Expand Up @@ -242,7 +247,12 @@ type ConsumerAdditionProposalJSON struct {
UnbondingPeriod time.Duration `json:"unbonding_period"`

Deposit string `json:"deposit"`
TopN uint32 `json:"top_N"`

TopN uint32 `json:"top_N"`
ValidatorsPowerCap uint32 `json:"validators_power_cap"`
ValidatorSetCap uint32 `json:"validator_set_cap"`
Allowlist []string `json:"allowlist"`
Denylist []string `json:"denylist"`
}

type ConsumerAdditionProposalReq struct {
Expand Down
172 changes: 172 additions & 0 deletions x/ccv/provider/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1300,3 +1300,175 @@ func (k Keeper) DeleteConsumerCommissionRate(
store := ctx.KVStore(k.storeKey)
store.Delete(types.ConsumerCommissionRateKey(chainID, providerAddr))
}

// SetValidatorsPowersCap sets the power-cap value `p` associated to chain with `chainID`
func (k Keeper) SetValidatorsPowersCap(
insumity marked this conversation as resolved.
Show resolved Hide resolved
ctx sdk.Context,
chainID string,
p uint32,
) {
store := ctx.KVStore(k.storeKey)

buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, p)

store.Set(types.ValidatorsPowerCapKey(chainID), buf)
}

// DeleteValidatorsPowerCap removes the power-cap value associated to chain with `chainID`
func (k Keeper) DeleteValidatorsPowerCap(
ctx sdk.Context,
chainID string,
) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.ValidatorsPowerCapKey(chainID))
}

// GetValidatorsPowerCap returns `(p, true)` if chain `chainID` has power cap `p` associated with it, and (0, false) otherwise
func (k Keeper) GetValidatorsPowerCap(
ctx sdk.Context,
chainID string,
) (uint32, bool) {
store := ctx.KVStore(k.storeKey)
buf := store.Get(types.ValidatorsPowerCapKey(chainID))
if buf == nil {
return 0, false
}
return binary.BigEndian.Uint32(buf), true
}

// SetValidatorSetCap stores the validator-set cap value `c` associated to chain with `chainID`
func (k Keeper) SetValidatorSetCap(
ctx sdk.Context,
chainID string,
c uint32,
) {
store := ctx.KVStore(k.storeKey)

buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, c)

store.Set(types.ValidatorSetCapKey(chainID), buf)
}

// DeleteValidatorSetCap removes the validator-set cap value associated to chain with `chainID`
func (k Keeper) DeleteValidatorSetCap(
ctx sdk.Context,
chainID string,
) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.ValidatorSetCapKey(chainID))
}

// GetValidatorSetCap returns `(c, true)` if chain `chainID` has validator-set cap `c` associated with it, and (0, false) otherwise
func (k Keeper) GetValidatorSetCap(
ctx sdk.Context,
chainID string,
) (uint32, bool) {
store := ctx.KVStore(k.storeKey)
buf := store.Get(types.ValidatorSetCapKey(chainID))
if buf == nil {
return 0, false
}
return binary.BigEndian.Uint32(buf), true
}

// SetAllowlist allowlists validator with `providerAddr` address on chain `chainID`
func (k Keeper) SetAllowlist(
ctx sdk.Context,
chainID string,
providerAddr types.ProviderConsAddress,
) {
store := ctx.KVStore(k.storeKey)
store.Set(types.AllowlistCapKey(chainID, providerAddr), []byte{})
}

// IsAllowlisted returns `true` if validator with `providerAddr` has been allowlisted on chain `chainID`
func (k Keeper) IsAllowlisted(
ctx sdk.Context,
chainID string,
providerAddr types.ProviderConsAddress,
) bool {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.AllowlistCapKey(chainID, providerAddr))
return bz != nil
}

// DeleteAllowlist deletes all allowlisted validators
func (k Keeper) DeleteAllowlist(ctx sdk.Context, chainID string) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.ChainIdWithLenKey(types.AllowlistPrefix, chainID))
defer iterator.Close()

keysToDel := [][]byte{}
for ; iterator.Valid(); iterator.Next() {
keysToDel = append(keysToDel, iterator.Key())
}

for _, key := range keysToDel {
store.Delete(key)
}
}

// IsAllowlistEmpty returns `true` if no validator is allowlisted on chain `chainID`
func (k Keeper) IsAllowlistEmpty(ctx sdk.Context, chainID string) bool {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.ChainIdWithLenKey(types.AllowlistPrefix, chainID))
defer iterator.Close()

if iterator.Valid() {
return false
}

return true
}

// SetDenylist denylists validator with `providerAddr` address on chain `chainID`
func (k Keeper) SetDenylist(
ctx sdk.Context,
chainID string,
providerAddr types.ProviderConsAddress,
) {
store := ctx.KVStore(k.storeKey)
store.Set(types.DenylistCapKey(chainID, providerAddr), []byte{})
}

// IsDenylisted returns `true` if validator with `providerAddr` has been denylisted on chain `chainID`
func (k Keeper) IsDenylisted(
ctx sdk.Context,
chainID string,
providerAddr types.ProviderConsAddress,
) bool {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.DenylistCapKey(chainID, providerAddr))
return bz != nil
}

// DeleteDenylist deletes all denylisted validators
func (k Keeper) DeleteDenylist(ctx sdk.Context, chainID string) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.ChainIdWithLenKey(types.DenylistPrefix, chainID))
defer iterator.Close()

keysToDel := [][]byte{}
for ; iterator.Valid(); iterator.Next() {
keysToDel = append(keysToDel, iterator.Key())
}

for _, key := range keysToDel {
store.Delete(key)
}
}

// IsDenylistEmpty returns `true` if no validator is allowlisted on chain `chainID`
insumity marked this conversation as resolved.
Show resolved Hide resolved
func (k Keeper) IsDenylistEmpty(ctx sdk.Context, chainID string) bool {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.ChainIdWithLenKey(types.DenylistPrefix, chainID))
defer iterator.Close()

if iterator.Valid() {
return false
}

return true
}
88 changes: 88 additions & 0 deletions x/ccv/provider/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -740,3 +740,91 @@ func TestConsumerCommissionRate(t *testing.T) {
_, found = providerKeeper.GetConsumerCommissionRate(ctx, "chainID", providerAddr2)
require.False(t, found)
}

// TestValidatorsPowersCap tests the `SetValidatorsPowerCap`, `GetValidatorsPowerCap`, and `DeleteValidatorsPowerCap` methods
func TestValidatorsPowersCap(t *testing.T) {
providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t))
defer ctrl.Finish()

expectedPowerCap := uint32(10)
providerKeeper.SetValidatorsPowersCap(ctx, "chainID", expectedPowerCap)
powerCap, found := providerKeeper.GetValidatorsPowerCap(ctx, "chainID")
require.Equal(t, expectedPowerCap, powerCap)
require.True(t, found)

providerKeeper.DeleteValidatorsPowerCap(ctx, "chainID")
_, found = providerKeeper.GetValidatorsPowerCap(ctx, "chainID")
require.False(t, found)
}

// TestValidatorSetCap tests the `SetValidatorSetCap`, `GetValidatorSetCap`, and `DeleteValidatorSetCap` methods
func TestValidatorSetCap(t *testing.T) {
providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t))
defer ctrl.Finish()

expectedPowerCap := uint32(10)
providerKeeper.SetValidatorSetCap(ctx, "chainID", expectedPowerCap)
powerCap, found := providerKeeper.GetValidatorSetCap(ctx, "chainID")
require.Equal(t, expectedPowerCap, powerCap)
require.True(t, found)

providerKeeper.DeleteValidatorSetCap(ctx, "chainID")
_, found = providerKeeper.GetValidatorSetCap(ctx, "chainID")
require.False(t, found)
}

// TestAllowlist tests the `SetAllowlist`, `IsAllowlisted`, `DeleteAllowlist`, and `IsAllowlistEmpty` methods
func TestAllowlist(t *testing.T) {
providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t))
defer ctrl.Finish()

chainID := "chainID"

// no validator was allowlisted and hence the allowlist is empty
require.True(t, providerKeeper.IsAllowlistEmpty(ctx, chainID))

providerAddr1 := types.NewProviderConsAddress([]byte("providerAddr1"))
providerKeeper.SetAllowlist(ctx, chainID, providerAddr1)
require.True(t, providerKeeper.IsAllowlisted(ctx, chainID, providerAddr1))

// allowlist is not empty anymore
require.False(t, providerKeeper.IsAllowlistEmpty(ctx, chainID))

providerAddr2 := types.NewProviderConsAddress([]byte("providerAddr2"))
providerKeeper.SetAllowlist(ctx, chainID, providerAddr2)
require.True(t, providerKeeper.IsAllowlisted(ctx, chainID, providerAddr2))
require.False(t, providerKeeper.IsAllowlistEmpty(ctx, chainID))

providerKeeper.DeleteAllowlist(ctx, chainID)
require.False(t, providerKeeper.IsAllowlisted(ctx, chainID, providerAddr1))
require.False(t, providerKeeper.IsAllowlisted(ctx, chainID, providerAddr2))
require.True(t, providerKeeper.IsAllowlistEmpty(ctx, chainID))
}

// TestDenylist tests the `SetDenylist`, `IsDenylisted`, `DeleteDenylist`, and `IsDenylistEmpty` methods
func TestDenylist(t *testing.T) {
providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t))
defer ctrl.Finish()

chainID := "chainID"

// no validator was denylisted and hence the denylist is empty
require.True(t, providerKeeper.IsDenylistEmpty(ctx, chainID))

providerAddr1 := types.NewProviderConsAddress([]byte("providerAddr1"))
providerKeeper.SetDenylist(ctx, chainID, providerAddr1)
require.True(t, providerKeeper.IsDenylisted(ctx, chainID, providerAddr1))

// denylist is not empty anymore
require.False(t, providerKeeper.IsDenylistEmpty(ctx, chainID))

providerAddr2 := types.NewProviderConsAddress([]byte("providerAddr2"))
providerKeeper.SetDenylist(ctx, chainID, providerAddr2)
require.True(t, providerKeeper.IsDenylisted(ctx, chainID, providerAddr2))
require.False(t, providerKeeper.IsDenylistEmpty(ctx, chainID))

providerKeeper.DeleteDenylist(ctx, chainID)
require.False(t, providerKeeper.IsDenylisted(ctx, chainID, providerAddr1))
require.False(t, providerKeeper.IsDenylisted(ctx, chainID, providerAddr2))
require.True(t, providerKeeper.IsDenylistEmpty(ctx, chainID))
}
4 changes: 2 additions & 2 deletions x/ccv/provider/keeper/key_assignment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,8 +768,8 @@ func TestSimulatedAssignmentsAndUpdateApplication(t *testing.T) {
})
}

nextValidators := k.ComputeNextEpochConsumerValSet(ctx, CHAINID, bondedValidators,
func(validator stakingtypes.Validator) bool {
nextValidators := k.FilterValidators(ctx, CHAINID, bondedValidators,
func(providerAddr types.ProviderConsAddress) bool {
return true
})
updates = providerkeeper.DiffValidators(k.GetConsumerValSet(ctx, CHAINID), nextValidators)
Expand Down
Loading
Loading