From b0d4cf94671aa8d10b3c6db0ae38b93ba8bcfe58 Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 13 Mar 2024 12:22:48 +0100 Subject: [PATCH 01/14] init commit --- .../ccv/provider/v1/provider.proto | 13 - x/ccv/provider/keeper/distribution_test.go | 9 +- x/ccv/provider/keeper/keeper.go | 22 +- x/ccv/provider/keeper/keeper_test.go | 68 ++- .../keeper/partial_set_security_test.go | 8 +- x/ccv/provider/keeper/relay.go | 15 +- x/ccv/provider/keeper/validator_set_update.go | 110 ++++ .../keeper/validator_set_update_test.go | 81 ++- x/ccv/provider/types/keys.go | 9 - x/ccv/provider/types/provider.pb.go | 538 ++++-------------- 10 files changed, 381 insertions(+), 492 deletions(-) diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index 8c70c9e428..1b5f7a515f 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -326,16 +326,3 @@ message ConsumerRewardsAllocation { (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins" ]; } - -// OptedInValidator is used to store a opted-in validator -// to a consumer chain with the following mapping: (chainID, providerAddr) -> optedInValidator -message OptedInValidator { - // validator address - bytes provider_addr = 1; - // block height at which the validator opted-in - int64 block_height = 2; - // validator voting power at the block it opted-in - int64 power = 3; - // public key used by the validator on the consumer - bytes public_key = 4; -} \ No newline at end of file diff --git a/x/ccv/provider/keeper/distribution_test.go b/x/ccv/provider/keeper/distribution_test.go index 52ac0e0a82..64359e4fe4 100644 --- a/x/ccv/provider/keeper/distribution_test.go +++ b/x/ccv/provider/keeper/distribution_test.go @@ -38,11 +38,10 @@ func TestComputeConsumerTotalVotingPower(t *testing.T) { keeper.SetOptedIn( ctx, chainID, - types.OptedInValidator{ - ProviderAddr: val.Address, - BlockHeight: ctx.BlockHeight(), - Power: val.VotingPower, - PublicKey: val.PubKey.Bytes(), + types.ConsumerValidator{ + ProviderConsAddr: val.Address, + Power: val.VotingPower, + //ConsumerPublicKey: val., FIXME }, ) diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index ec73d85383..154307dfef 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -1189,7 +1189,7 @@ func (k Keeper) IsOptIn(ctx sdk.Context, chainID string) bool { func (k Keeper) SetOptedIn( ctx sdk.Context, chainID string, - validator types.OptedInValidator, + validator types.ConsumerValidator, ) { store := ctx.KVStore(k.storeKey) bz, err := validator.Marshal() @@ -1197,7 +1197,7 @@ func (k Keeper) SetOptedIn( panic(fmt.Errorf("failed to marshal OptedInValidator: %w", err)) } - store.Set(types.OptedInKey(chainID, validator.ProviderAddr), bz) + store.Set(types.ConsumerValidatorKey(chainID, validator.ProviderConsAddr), bz) } func (k Keeper) DeleteOptedIn( @@ -1206,7 +1206,7 @@ func (k Keeper) DeleteOptedIn( providerAddr types.ProviderConsAddress, ) { store := ctx.KVStore(k.storeKey) - store.Delete(types.OptedInKey(chainID, providerAddr.ToSdkConsAddr())) + store.Delete(types.ConsumerValidatorKey(chainID, providerAddr.ToSdkConsAddr())) } func (k Keeper) IsOptedIn( @@ -1215,28 +1215,28 @@ func (k Keeper) IsOptedIn( providerAddr types.ProviderConsAddress, ) bool { store := ctx.KVStore(k.storeKey) - return store.Get(types.OptedInKey(chainID, providerAddr.ToSdkConsAddr())) != nil + return store.Get(types.ConsumerValidatorKey(chainID, providerAddr.ToSdkConsAddr())) != nil } // GetAllOptedIn returns all the opted-in validators on chain `chainID` func (k Keeper) GetAllOptedIn( ctx sdk.Context, - chainID string) (optedInValidators []types.OptedInValidator) { + chainID string) (consumerValidators []types.ConsumerValidator) { store := ctx.KVStore(k.storeKey) - key := types.ChainIdWithLenKey(types.OptedInBytePrefix, chainID) + key := types.ChainIdWithLenKey(types.ConsumerValidatorBytePrefix, chainID) iterator := sdk.KVStorePrefixIterator(store, key) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { iterator.Value() - var optedInValidator types.OptedInValidator - if err := optedInValidator.Unmarshal(iterator.Value()); err != nil { - panic(fmt.Errorf("failed to unmarshal OptedInValidator: %w", err)) + var consumerValidator types.ConsumerValidator + if err := consumerValidator.Unmarshal(iterator.Value()); err != nil { + panic(fmt.Errorf("failed to unmarshal ConsumerValidator: %w", err)) } - optedInValidators = append(optedInValidators, optedInValidator) + consumerValidators = append(consumerValidators, consumerValidator) } - return optedInValidators + return consumerValidators } func (k Keeper) SetToBeOptedIn( diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index 595e01100a..8525f4ed74 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -666,46 +666,53 @@ func TestGetAllOptedIn(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - expectedOptedInValidators := []types.OptedInValidator{ + expectedOptedInValidators := []types.ConsumerValidator{ { - ProviderAddr: []byte("providerAddr1"), - BlockHeight: 1, - Power: 2, - PublicKey: []byte{3}, + ProviderConsAddr: []byte("providerAddr1"), + Power: 2, + ConsumerPublicKey: &tmprotocrypto.PublicKey{ + Sum: &tmprotocrypto.PublicKey_Ed25519{ + Ed25519: []byte{3}, + }, + }, }, { - ProviderAddr: []byte("providerAddr2"), - BlockHeight: 2, - Power: 3, - PublicKey: []byte{4}, + ProviderConsAddr: []byte("providerAddr2"), + Power: 3, + ConsumerPublicKey: &tmprotocrypto.PublicKey{ + Sum: &tmprotocrypto.PublicKey_Ed25519{ + Ed25519: []byte{4}, + }, + }, }, { - ProviderAddr: []byte("providerAddr3"), - BlockHeight: 3, - Power: 4, - PublicKey: []byte{5}, + ProviderConsAddr: []byte("providerAddr3"), + Power: 4, + ConsumerPublicKey: &tmprotocrypto.PublicKey{ + Sum: &tmprotocrypto.PublicKey_Ed25519{ + Ed25519: []byte{5}, + }, + }, }, } for _, expectedOptedInValidator := range expectedOptedInValidators { providerKeeper.SetOptedIn(ctx, "chainID", - types.OptedInValidator{ - ProviderAddr: expectedOptedInValidator.ProviderAddr, - BlockHeight: expectedOptedInValidator.BlockHeight, - Power: expectedOptedInValidator.Power, - PublicKey: expectedOptedInValidator.PublicKey, + types.ConsumerValidator{ + ProviderConsAddr: expectedOptedInValidator.ProviderConsAddr, + Power: expectedOptedInValidator.Power, + ConsumerPublicKey: expectedOptedInValidator.ConsumerPublicKey, }) } actualOptedInValidators := providerKeeper.GetAllOptedIn(ctx, "chainID") // sort validators first to be able to compare - sortOptedInValidators := func(optedInValidators []types.OptedInValidator) { + sortOptedInValidators := func(optedInValidators []types.ConsumerValidator) { sort.Slice(optedInValidators, func(i int, j int) bool { a := optedInValidators[i] b := optedInValidators[j] - return a.BlockHeight < b.BlockHeight || - (a.BlockHeight == b.BlockHeight && bytes.Compare(a.ProviderAddr, b.ProviderAddr) < 0) + return bytes.Compare(a.ProviderConsAddr, b.ProviderConsAddr) < 0 }) } sortOptedInValidators(expectedOptedInValidators) @@ -718,17 +725,20 @@ func TestOptedIn(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - optedInValidator := types.OptedInValidator{ProviderAddr: []byte("providerAddr"), - BlockHeight: 1, - Power: 2, - PublicKey: []byte{3}, + optedInValidator := types.ConsumerValidator{ProviderConsAddr: []byte("providerAddr"), + Power: 2, + ConsumerPublicKey: &tmprotocrypto.PublicKey{ + Sum: &tmprotocrypto.PublicKey_Ed25519{ + Ed25519: []byte{3}, + }, + }, } - require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderAddr))) + require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderConsAddr))) providerKeeper.SetOptedIn(ctx, "chainID", optedInValidator) - require.True(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderAddr))) - providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderAddr)) - require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderAddr))) + require.True(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderConsAddr))) + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderConsAddr)) + require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderConsAddr))) } func TestGetAllToBeOptedIn(t *testing.T) { diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go index a6b2ec45e7..82a987018b 100644 --- a/x/ccv/provider/keeper/partial_set_security_test.go +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "github.com/cometbft/cometbft/proto/tendermint/crypto" "testing" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -33,9 +34,12 @@ func TestHandleOptIn(t *testing.T) { require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) // if validator (`providerAddr`) is already opted in, then the validator cannot be opted in - providerKeeper.SetOptedIn(ctx, "chainID", - types.OptedInValidator{ProviderAddr: providerAddr.ToSdkConsAddr(), BlockHeight: 1, Power: 1, PublicKey: []byte{1}}) + types.ConsumerValidator{ProviderConsAddr: providerAddr.ToSdkConsAddr(), Power: 1, ConsumerPublicKey: &crypto.PublicKey{ + Sum: &crypto.PublicKey_Ed25519{ + Ed25519: []byte{1}, + }, + }}) providerKeeper.HandleOptIn(ctx, "chainID", providerAddr, nil) require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) } diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 939f6d3995..14c4e9c712 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -222,7 +222,20 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { for _, chain := range k.GetAllConsumerChains(ctx) { currentValidators := k.GetConsumerValSet(ctx, chain.ChainId) - nextValidators := k.ComputeNextEpochConsumerValSet(ctx, chain.ChainId, bondedValidators) + + var nextValidators []providertypes.ConsumerValidator + + if k.IsTopN(ctx, chain.ChainId) { + topN, _ := k.GetTopN(ctx, chain.ChainId) + threshold := sdk.NewDec(int64(topN)).QuoInt64(100) + k.OptInValidators(ctx, chain.ChainId, threshold, bondedValidators) + } + + if k.IsOptIn(ctx, chain.ChainId) || k.IsTopN(ctx, chain.ChainId) { + //nextValidators = k.ComputeNextEpochOptedInConsumerValSet(ctx, chain.ChainId) + } else { + nextValidators = k.ComputeNextEpochConsumerValSet(ctx, chain.ChainId, bondedValidators) + } valUpdates := DiffValidators(currentValidators, nextValidators) k.SetConsumerValSet(ctx, chain.ChainId, nextValidators) diff --git a/x/ccv/provider/keeper/validator_set_update.go b/x/ccv/provider/keeper/validator_set_update.go index 71238d210d..ae6d6fbf5d 100644 --- a/x/ccv/provider/keeper/validator_set_update.go +++ b/x/ccv/provider/keeper/validator_set_update.go @@ -1,11 +1,13 @@ package keeper import ( + "cosmossdk.io/math" "fmt" abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" + "sort" ) // SetConsumerValidator sets provided consumer `validator` on the consumer chain with `chainID` @@ -176,3 +178,111 @@ func (k Keeper) SetConsumerValSet(ctx sdk.Context, chainID string, nextValidator k.SetConsumerValidator(ctx, chainID, val) } } + +// ComputeNextEpochOptedInConsumerValSet returns the next validator set that is responsible for validating consumer +// chain `chainID`, based on the bonded validators. +func (k Keeper) ComputeNextEpochOptedInConsumerValSet( + ctx sdk.Context, + chainID string, +) []types.ConsumerValidator { + var nextValidators []types.ConsumerValidator + for _, val := range k.GetAllOptedIn(ctx, chainID) { + // get next voting power and the next consumer public key + providerAddress := types.ProviderConsAddress{Address: val.ProviderConsAddr} + stakingValidator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddress.ToSdkConsAddr()) + if !found { + // this should never happen but is recoverable if we exclude this validator from the `nextValidators` + k.Logger(ctx).Error("could not get consensus address of validator", + "validator", stakingValidator.GetOperator().String()) + + fmt.Println("..") + } + nextPower := k.stakingKeeper.GetLastValidatorPower(ctx, stakingValidator.GetOperator()) + nextConsumerPublicKey, foundConsumerPublicKey := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddress) + if !foundConsumerPublicKey { + // if no consumer key assigned then use the validator's key itself + k.Logger(ctx).Info("could not retrieve public key for validator on consumer chain because"+ + " the validator did not assign a new consumer key", + "validator", stakingValidator.GetOperator().String(), + "chainID", chainID) + var err error + nextConsumerPublicKey, err = stakingValidator.TmConsPublicKey() + if err != nil { + // this should never happen and might not be recoverable because without the public key + // we cannot generate a validator update + panic(fmt.Errorf("could not retrieve validator's (%+v) public key: %w", val, err)) + } + } + + nextValidator := types.ConsumerValidator{ + ProviderConsAddr: val.ProviderConsAddr, + Power: nextPower, + ConsumerPublicKey: &nextConsumerPublicKey, + } + nextValidators = append(nextValidators, nextValidator) + } + + return nextValidators +} + +func (k Keeper) OptInValidators(ctx sdk.Context, chainID string, threshold math.LegacyDec, bondedValidators []stakingtypes.Validator) { + powerStop := k.ComputePowerThreshold(ctx, bondedValidators, threshold) + + for _, val := range bondedValidators { + power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()) + if power >= powerStop { + consAddr, err := val.GetConsAddr() + _ = err // fIXME + nextConsumerPublicKey, foundConsumerPublicKey := k.GetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(consAddr)) + if !foundConsumerPublicKey { + // if no consumer key assigned then use the validator's key itself + k.Logger(ctx).Info("could not retrieve public key for validator on consumer chain because"+ + " the validator did not assign a new consumer key", + "validator", val.GetOperator().String(), + "chainID", chainID) + nextConsumerPublicKey, err = val.TmConsPublicKey() + if err != nil { + // this should never happen and might not be recoverable because without the public key + // we cannot generate a validator update + panic(fmt.Errorf("could not retrieve validator's (%+v) public key: %w", val, err)) + } + } + // if validator already exists it gets overwritten + k.SetOptedIn(ctx, chainID, types.ConsumerValidator{ + ProviderConsAddr: consAddr, + Power: power, + ConsumerPublicKey: &nextConsumerPublicKey, + }) + } else { + // validators that are not on the top N and habe opte din remain opted in, + } + } +} + +func (k Keeper) ComputePowerThreshold(ctx sdk.Context, + bondedValidators []stakingtypes.Validator, + threshold math.LegacyDec, +) int64 { + totalPower := int64(0) + var powers []int64 + for _, val := range bondedValidators { + power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()) + powers = append(powers, power) + totalPower = totalPower + power + } + + // sort by powers ascending + sort.SliceStable(powers, func(i, j int) bool { + return powers[i] < powers[j] + }) + + powerSum := sdk.ZeroDec() + for _, power := range powers { + powerSum = powerSum.Add(sdk.NewDecFromInt(sdk.NewInt(power))) + if powerSum.Quo(sdk.NewDecFromInt(sdk.NewInt(totalPower))).GT(threshold) { + return power + } + } + + panic("UpdateSoftOptOutThresholdPower should not reach this point. Incorrect logic!") +} diff --git a/x/ccv/provider/keeper/validator_set_update_test.go b/x/ccv/provider/keeper/validator_set_update_test.go index 8505158816..40a22704fa 100644 --- a/x/ccv/provider/keeper/validator_set_update_test.go +++ b/x/ccv/provider/keeper/validator_set_update_test.go @@ -162,7 +162,7 @@ func TestComputeNextEpochConsumerValSet(t *testing.T) { }) bondedValidators := []stakingtypes.Validator{valA, valB} - actualValidators := providerKeeper.ComputeNextEpochConsumerValSet(ctx, "chainID", bondedValidators) + actualValidators := providerKeeper.ComputeNextEpochConsumerValSet(ctx, chainID, bondedValidators) require.Equal(t, expectedValidators, actualValidators) } @@ -353,3 +353,82 @@ func TestSetConsumerValSet(t *testing.T) { sortValidators(nextCurrentValidators) require.Equal(t, nextValidators, nextCurrentValidators) } + +func TestComputeNextEpochOptedInConsumerValSet(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + chainID := "chainID" + + // helper function to generate a validator with the given power and with a provider address based on index + createStakingValidator := func(ctx sdk.Context, mocks testkeeper.MockedKeepers, index int, power int64) stakingtypes.Validator { + providerConsPubKey := ed25519.GenPrivKeyFromSecret([]byte{byte(index)}).PubKey() + consAddr := sdk.ConsAddress(providerConsPubKey.Address()) + providerAddr := types.NewProviderConsAddress(consAddr) + pk, _ := cryptocodec.FromTmPubKeyInterface(providerConsPubKey) + pkAny, _ := codectypes.NewAnyWithValue(pk) + + var providerValidatorAddr sdk.ValAddress + providerValidatorAddr = providerAddr.Address.Bytes() + + mocks.MockStakingKeeper.EXPECT(). + GetLastValidatorPower(ctx, providerValidatorAddr).Return(power).AnyTimes() + + stakingValidator := stakingtypes.Validator{ + OperatorAddress: providerValidatorAddr.String(), + ConsensusPubkey: pkAny, + } + + mocks.MockStakingKeeper.EXPECT(). + GetValidatorByConsAddr(ctx, consAddr).Return(stakingValidator, true).AnyTimes() + + return stakingValidator + } + + // no consumer validators returned if we have no opted-in validators + require.Empty(t, providerKeeper.ComputeNextEpochOptedInConsumerValSet(ctx, chainID)) + + var expectedValidators []types.ConsumerValidator + + // create a staking validator A that has not set a consumer public key + valA := createStakingValidator(ctx, mocks, 1, 1) + // because validator A has no consumer key set, the `ConsumerPublicKey` we expect is the key on the provider chain + valAConsAddr, _ := valA.GetConsAddr() + valAPublicKey, _ := valA.TmConsPublicKey() + expectedValAConsumerValidator := types.ConsumerValidator{ + ProviderConsAddr: types.NewProviderConsAddress(valAConsAddr).Address.Bytes(), + Power: 1, + ConsumerPublicKey: &valAPublicKey} + expectedValidators = append(expectedValidators, expectedValAConsumerValidator) + + // create a staking validator B that has set a consumer public key + valB := createStakingValidator(ctx, mocks, 2, 2) + // validator B has set a consumer key, the `ConsumerPublicKey` we expect is the key set by `SetValidatorConsumerPubKey` + valBConsumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() + valBConsAddr, _ := valB.GetConsAddr() + providerKeeper.SetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(valBConsAddr), valBConsumerKey) + expectedValBConsumerValidator := types.ConsumerValidator{ + ProviderConsAddr: types.NewProviderConsAddress(valBConsAddr).Address.Bytes(), + Power: 2, + ConsumerPublicKey: &valBConsumerKey, + } + expectedValidators = append(expectedValidators, expectedValBConsumerValidator) + + // opt in validators A and B with 0 power and no consumer public keys + providerKeeper.SetOptedIn(ctx, chainID, types.ConsumerValidator{ProviderConsAddr: valAConsAddr, Power: 0, ConsumerPublicKey: nil}) + providerKeeper.SetOptedIn(ctx, chainID, types.ConsumerValidator{ProviderConsAddr: valBConsAddr, Power: 0, ConsumerPublicKey: nil}) + + // the expected actual validators are the opted-in validators but with the correct power and consumer public keys set + actualValidators := providerKeeper.ComputeNextEpochOptedInConsumerValSet(ctx, "chainID") + + // sort validators first to be able to compare + sortValidators := func(validators []types.ConsumerValidator) { + sort.Slice(validators, func(i, j int) bool { + return bytes.Compare(validators[i].ProviderConsAddr, validators[j].ProviderConsAddr) < 0 + }) + } + + sortValidators(actualValidators) + sortValidators(expectedValidators) + require.Equal(t, expectedValidators, actualValidators) +} diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 998608ef18..edc5c43c86 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -154,9 +154,6 @@ const ( // that corresponds to the N% of the top validators that have to validate this consumer chain TopNBytePrefix - // OptedInBytePrefix is the byte prefix used when storing for each consumer chain all the opted in validators - OptedInBytePrefix - // ToBeOptedInBytePrefix is the byte prefix used when storing for each consumer chain the validators that // are about to be opted in ToBeOptedInBytePrefix @@ -550,12 +547,6 @@ func TopNKey(chainID string) []byte { return ChainIdWithLenKey(TopNBytePrefix, chainID) } -// OptedInKey returns the key of consumer chain `chainID` and validator with `providerAddr` -func OptedInKey(chainID string, providerAddr []byte) []byte { - prefix := ChainIdWithLenKey(OptedInBytePrefix, chainID) - return append(prefix, providerAddr...) -} - // ToBeOptedInKey returns the key of consumer chain `chainID` and validator with `providerAddr` func ToBeOptedInKey(chainID string, providerAddr ProviderConsAddress) []byte { prefix := ChainIdWithLenKey(ToBeOptedInBytePrefix, chainID) diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index c2771de119..dee8a9520a 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -1513,80 +1513,6 @@ func (m *ConsumerRewardsAllocation) GetRewards() github_com_cosmos_cosmos_sdk_ty return nil } -// OptedInValidator is used to store a opted-in validator -// to a consumer chain with the following mapping: (chainID, providerAddr) -> optedInValidator -type OptedInValidator struct { - // validator address - ProviderAddr []byte `protobuf:"bytes,1,opt,name=provider_addr,json=providerAddr,proto3" json:"provider_addr,omitempty"` - // block height at which the validator opted-in - BlockHeight int64 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - // validator voting power at the block it opted-in - Power int64 `protobuf:"varint,3,opt,name=power,proto3" json:"power,omitempty"` - // public key used by the validator on the consumer - PublicKey []byte `protobuf:"bytes,4,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` -} - -func (m *OptedInValidator) Reset() { *m = OptedInValidator{} } -func (m *OptedInValidator) String() string { return proto.CompactTextString(m) } -func (*OptedInValidator) ProtoMessage() {} -func (*OptedInValidator) Descriptor() ([]byte, []int) { - return fileDescriptor_f22ec409a72b7b72, []int{24} -} -func (m *OptedInValidator) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *OptedInValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_OptedInValidator.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *OptedInValidator) XXX_Merge(src proto.Message) { - xxx_messageInfo_OptedInValidator.Merge(m, src) -} -func (m *OptedInValidator) XXX_Size() int { - return m.Size() -} -func (m *OptedInValidator) XXX_DiscardUnknown() { - xxx_messageInfo_OptedInValidator.DiscardUnknown(m) -} - -var xxx_messageInfo_OptedInValidator proto.InternalMessageInfo - -func (m *OptedInValidator) GetProviderAddr() []byte { - if m != nil { - return m.ProviderAddr - } - return nil -} - -func (m *OptedInValidator) GetBlockHeight() int64 { - if m != nil { - return m.BlockHeight - } - return 0 -} - -func (m *OptedInValidator) GetPower() int64 { - if m != nil { - return m.Power - } - return 0 -} - -func (m *OptedInValidator) GetPublicKey() []byte { - if m != nil { - return m.PublicKey - } - return nil -} - func init() { proto.RegisterType((*ConsumerAdditionProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerAdditionProposal") proto.RegisterType((*ConsumerRemovalProposal)(nil), "interchain_security.ccv.provider.v1.ConsumerRemovalProposal") @@ -1612,7 +1538,6 @@ func init() { proto.RegisterType((*ConsumerAddrsToPrune)(nil), "interchain_security.ccv.provider.v1.ConsumerAddrsToPrune") proto.RegisterType((*ConsumerValidator)(nil), "interchain_security.ccv.provider.v1.ConsumerValidator") proto.RegisterType((*ConsumerRewardsAllocation)(nil), "interchain_security.ccv.provider.v1.ConsumerRewardsAllocation") - proto.RegisterType((*OptedInValidator)(nil), "interchain_security.ccv.provider.v1.OptedInValidator") } func init() { @@ -1620,126 +1545,123 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1892 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x73, 0x1c, 0x47, - 0x15, 0xd7, 0x68, 0x57, 0x1f, 0xfb, 0x56, 0x9f, 0x23, 0x25, 0x1e, 0x19, 0xb1, 0x92, 0x27, 0x24, - 0x08, 0x82, 0x67, 0x90, 0x02, 0x55, 0x2e, 0x17, 0xa9, 0x94, 0xb4, 0x72, 0x62, 0x59, 0x89, 0xad, - 0x8c, 0x84, 0x5c, 0xc0, 0x61, 0xaa, 0xb7, 0xa7, 0xbd, 0xdb, 0xa5, 0xd9, 0xe9, 0x71, 0x77, 0xef, - 0x38, 0x7b, 0xe1, 0xcc, 0x85, 0x22, 0xdc, 0x52, 0x5c, 0x08, 0x54, 0x51, 0x45, 0x71, 0x81, 0x3f, - 0x23, 0xc7, 0x1c, 0x39, 0x25, 0x94, 0x7d, 0xe0, 0xc0, 0x95, 0x3f, 0x80, 0xea, 0x9e, 0xcf, 0x5d, - 0x49, 0x66, 0x5d, 0x81, 0x8b, 0x34, 0xf3, 0xfa, 0xbd, 0xdf, 0x7b, 0xfd, 0xbe, 0x77, 0x60, 0x8f, - 0x46, 0x92, 0x70, 0xdc, 0x43, 0x34, 0xf2, 0x05, 0xc1, 0x03, 0x4e, 0xe5, 0xd0, 0xc5, 0x38, 0x71, - 0x63, 0xce, 0x12, 0x1a, 0x10, 0xee, 0x26, 0xbb, 0xc5, 0xb3, 0x13, 0x73, 0x26, 0x99, 0xf9, 0xc6, - 0x15, 0x32, 0x0e, 0xc6, 0x89, 0x53, 0xf0, 0x25, 0xbb, 0x37, 0xdf, 0xbc, 0x0e, 0x38, 0xd9, 0x75, - 0x9f, 0x51, 0x4e, 0x52, 0xac, 0x9b, 0xeb, 0x5d, 0xd6, 0x65, 0xfa, 0xd1, 0x55, 0x4f, 0x19, 0x75, - 0xab, 0xcb, 0x58, 0x37, 0x24, 0xae, 0x7e, 0xeb, 0x0c, 0x9e, 0xb8, 0x92, 0xf6, 0x89, 0x90, 0xa8, - 0x1f, 0x67, 0x0c, 0xad, 0x71, 0x86, 0x60, 0xc0, 0x91, 0xa4, 0x2c, 0xca, 0x01, 0x68, 0x07, 0xbb, - 0x98, 0x71, 0xe2, 0xe2, 0x90, 0x92, 0x48, 0x2a, 0xad, 0xe9, 0x53, 0xc6, 0xe0, 0x2a, 0x86, 0x90, - 0x76, 0x7b, 0x32, 0x25, 0x0b, 0x57, 0x92, 0x28, 0x20, 0xbc, 0x4f, 0x53, 0xe6, 0xf2, 0x2d, 0x13, - 0xd8, 0xac, 0x9c, 0x63, 0x3e, 0x8c, 0x25, 0x73, 0x2f, 0xc8, 0x50, 0x64, 0xa7, 0x6f, 0x61, 0x26, - 0xfa, 0x4c, 0xb8, 0x44, 0xdd, 0x3f, 0xc2, 0xc4, 0x4d, 0x76, 0x3b, 0x44, 0xa2, 0xdd, 0x82, 0x90, - 0xdb, 0x9d, 0xf1, 0x75, 0x90, 0x28, 0x79, 0x30, 0xa3, 0xb9, 0xdd, 0xab, 0xa8, 0x4f, 0x23, 0xe6, - 0xea, 0xbf, 0x29, 0xc9, 0xfe, 0xf7, 0x2c, 0x58, 0x6d, 0x16, 0x89, 0x41, 0x9f, 0xf0, 0xfd, 0x20, - 0xa0, 0xea, 0x96, 0x27, 0x9c, 0xc5, 0x4c, 0xa0, 0xd0, 0x5c, 0x87, 0x19, 0x49, 0x65, 0x48, 0x2c, - 0x63, 0xdb, 0xd8, 0x69, 0x78, 0xe9, 0x8b, 0xb9, 0x0d, 0xcd, 0x80, 0x08, 0xcc, 0x69, 0xac, 0x98, - 0xad, 0x69, 0x7d, 0x56, 0x25, 0x99, 0x1b, 0x30, 0x9f, 0x86, 0x86, 0x06, 0x56, 0x4d, 0x1f, 0xcf, - 0xe9, 0xf7, 0xa3, 0xc0, 0xfc, 0x00, 0x96, 0x68, 0x44, 0x25, 0x45, 0xa1, 0xdf, 0x23, 0xca, 0x41, - 0x56, 0x7d, 0xdb, 0xd8, 0x69, 0xee, 0xdd, 0x74, 0x68, 0x07, 0x3b, 0xca, 0xa7, 0x4e, 0xe6, 0xc9, - 0x64, 0xd7, 0xb9, 0xaf, 0x39, 0x0e, 0xea, 0x5f, 0x7c, 0xb5, 0x35, 0xe5, 0x2d, 0x66, 0x72, 0x29, - 0xd1, 0xbc, 0x05, 0x0b, 0x5d, 0x12, 0x11, 0x41, 0x85, 0xdf, 0x43, 0xa2, 0x67, 0xcd, 0x6c, 0x1b, - 0x3b, 0x0b, 0x5e, 0x33, 0xa3, 0xdd, 0x47, 0xa2, 0x67, 0x6e, 0x41, 0xb3, 0x43, 0x23, 0xc4, 0x87, - 0x29, 0xc7, 0xac, 0xe6, 0x80, 0x94, 0xa4, 0x19, 0xda, 0x00, 0x22, 0x46, 0xcf, 0x22, 0x5f, 0x25, - 0x80, 0x35, 0x97, 0x19, 0x92, 0x06, 0xdf, 0xc9, 0x83, 0xef, 0x9c, 0xe5, 0xd9, 0x71, 0x30, 0xaf, - 0x0c, 0xf9, 0xf4, 0xeb, 0x2d, 0xc3, 0x6b, 0x68, 0x39, 0x75, 0x62, 0x3e, 0x84, 0x95, 0x41, 0xd4, - 0x61, 0x51, 0x40, 0xa3, 0xae, 0x1f, 0x13, 0x4e, 0x59, 0x60, 0xcd, 0x6b, 0xa8, 0x8d, 0x4b, 0x50, - 0x87, 0x59, 0x1e, 0xa5, 0x48, 0x9f, 0x29, 0xa4, 0xe5, 0x42, 0xf8, 0x44, 0xcb, 0x9a, 0x1f, 0x83, - 0x89, 0x71, 0xa2, 0x4d, 0x62, 0x03, 0x99, 0x23, 0x36, 0x26, 0x47, 0x5c, 0xc1, 0x38, 0x39, 0x4b, - 0xa5, 0x33, 0xc8, 0x5f, 0xc0, 0x0d, 0xc9, 0x51, 0x24, 0x9e, 0x10, 0x3e, 0x8e, 0x0b, 0x93, 0xe3, - 0xbe, 0x96, 0x63, 0x8c, 0x82, 0xdf, 0x87, 0x6d, 0x9c, 0x25, 0x90, 0xcf, 0x49, 0x40, 0x85, 0xe4, - 0xb4, 0x33, 0x50, 0xb2, 0xfe, 0x13, 0x8e, 0xb0, 0xce, 0x91, 0xa6, 0x4e, 0x82, 0x56, 0xce, 0xe7, - 0x8d, 0xb0, 0xbd, 0x9f, 0x71, 0x99, 0x8f, 0xe0, 0x3b, 0x9d, 0x90, 0xe1, 0x0b, 0xa1, 0x8c, 0xf3, - 0x47, 0x90, 0xb4, 0xea, 0x3e, 0x15, 0x42, 0xa1, 0x2d, 0x6c, 0x1b, 0x3b, 0x35, 0xef, 0x56, 0xca, - 0x7b, 0x42, 0xf8, 0x61, 0x85, 0xf3, 0xac, 0xc2, 0x68, 0xde, 0x06, 0xb3, 0x47, 0x85, 0x64, 0x9c, - 0x62, 0x14, 0xfa, 0x24, 0x92, 0x9c, 0x12, 0x61, 0x2d, 0x6a, 0xf1, 0xd5, 0xf2, 0xe4, 0x5e, 0x7a, - 0x60, 0x3e, 0x80, 0x5b, 0xd7, 0x2a, 0xf5, 0x71, 0x0f, 0x45, 0x11, 0x09, 0xad, 0x25, 0x7d, 0x95, - 0xad, 0xe0, 0x1a, 0x9d, 0xed, 0x94, 0xcd, 0x5c, 0x83, 0x19, 0xc9, 0x62, 0xff, 0xa1, 0xb5, 0xbc, - 0x6d, 0xec, 0x2c, 0x7a, 0x75, 0xc9, 0xe2, 0x87, 0x77, 0xe7, 0x7f, 0xf5, 0xf9, 0xd6, 0xd4, 0x67, - 0x9f, 0x6f, 0x4d, 0xd9, 0x7f, 0x35, 0xe0, 0x46, 0xbb, 0xf0, 0x46, 0x9f, 0x25, 0x28, 0xfc, 0x7f, - 0x56, 0xdd, 0x3e, 0x34, 0x84, 0x32, 0x47, 0xe7, 0x79, 0xfd, 0x15, 0xf2, 0x7c, 0x5e, 0x89, 0xa9, - 0x03, 0xfb, 0xf7, 0x06, 0xac, 0xdf, 0x7b, 0x3a, 0xa0, 0x09, 0xc3, 0xe8, 0x7f, 0xd2, 0x24, 0x8e, - 0x61, 0x91, 0x54, 0xf0, 0x84, 0x55, 0xdb, 0xae, 0xed, 0x34, 0xf7, 0xde, 0x74, 0xd2, 0x26, 0xe6, - 0x14, 0xbd, 0x2d, 0x6b, 0x64, 0x4e, 0x55, 0xbb, 0x37, 0x2a, 0x7b, 0x77, 0xda, 0x32, 0xec, 0x3f, - 0x1a, 0x70, 0x53, 0xb9, 0xbf, 0x4b, 0x3c, 0xf2, 0x0c, 0xf1, 0xe0, 0x90, 0x44, 0xac, 0x2f, 0xbe, - 0xb1, 0x9d, 0x36, 0x2c, 0x06, 0x1a, 0xc9, 0x97, 0xcc, 0x47, 0x41, 0xa0, 0xed, 0xd4, 0x3c, 0x8a, - 0x78, 0xc6, 0xf6, 0x83, 0xc0, 0xdc, 0x81, 0x95, 0x92, 0x87, 0xab, 0x78, 0x2a, 0x37, 0x2b, 0xb6, - 0xa5, 0x9c, 0x4d, 0x47, 0x99, 0xd8, 0xff, 0x32, 0x60, 0xe5, 0x83, 0x90, 0x75, 0x50, 0x78, 0x1a, - 0x22, 0xd1, 0x53, 0xa9, 0x37, 0x54, 0xe1, 0xe1, 0x24, 0xab, 0x79, 0x6d, 0xde, 0xc4, 0xe1, 0x51, - 0x62, 0xba, 0x0b, 0xbd, 0x07, 0xab, 0x45, 0x15, 0x16, 0x59, 0xa0, 0x6f, 0x73, 0xb0, 0xf6, 0xfc, - 0xab, 0xad, 0xe5, 0x3c, 0xd9, 0xda, 0x3a, 0x23, 0x0e, 0xbd, 0x65, 0x3c, 0x42, 0x08, 0xcc, 0x16, - 0x34, 0x69, 0x07, 0xfb, 0x82, 0x3c, 0xf5, 0xa3, 0x41, 0x5f, 0x27, 0x50, 0xdd, 0x6b, 0xd0, 0x0e, - 0x3e, 0x25, 0x4f, 0x1f, 0x0e, 0xfa, 0xe6, 0x3b, 0xf0, 0x7a, 0x3e, 0x80, 0xfd, 0x04, 0x85, 0xbe, - 0x92, 0x57, 0xee, 0xe0, 0x3a, 0x9f, 0x16, 0xbc, 0xb5, 0xfc, 0xf4, 0x1c, 0x85, 0x4a, 0xd9, 0x7e, - 0x10, 0x70, 0xfb, 0xc5, 0x0c, 0xcc, 0x9e, 0x20, 0x8e, 0xfa, 0xc2, 0x3c, 0x83, 0x65, 0x49, 0xfa, - 0x71, 0x88, 0x24, 0xf1, 0xd3, 0x0e, 0x9f, 0xdd, 0xf4, 0x6d, 0xdd, 0xf9, 0xab, 0xc3, 0xd2, 0xa9, - 0x8c, 0xc7, 0x64, 0xd7, 0x69, 0x6b, 0xea, 0xa9, 0x44, 0x92, 0x78, 0x4b, 0x39, 0x46, 0x4a, 0x34, - 0xef, 0x80, 0x25, 0xf9, 0x40, 0xc8, 0xb2, 0xf7, 0x96, 0x4d, 0x27, 0x8d, 0xe5, 0xeb, 0xf9, 0x79, - 0xda, 0xae, 0x8a, 0x66, 0x73, 0x75, 0x9b, 0xad, 0x7d, 0x93, 0x36, 0x7b, 0x0a, 0x6b, 0x6a, 0x46, - 0x8d, 0x63, 0xd6, 0x27, 0xc7, 0x5c, 0x55, 0xf2, 0xa3, 0xa0, 0x1f, 0x83, 0x99, 0x08, 0x3c, 0x8e, - 0x39, 0xf3, 0x0a, 0x76, 0x26, 0x02, 0x8f, 0x42, 0x06, 0xb0, 0x29, 0x54, 0xf2, 0xf9, 0x7d, 0x22, - 0x75, 0xd3, 0x8e, 0x43, 0x12, 0x51, 0xd1, 0xcb, 0xc1, 0x67, 0x27, 0x07, 0xdf, 0xd0, 0x40, 0x1f, - 0x29, 0x1c, 0x2f, 0x87, 0xc9, 0xb4, 0xb4, 0xa1, 0x75, 0xb5, 0x96, 0x22, 0x40, 0x73, 0x3a, 0x40, - 0xdf, 0xba, 0x02, 0xa2, 0x88, 0x92, 0x80, 0xb7, 0x2a, 0xc3, 0x45, 0x55, 0xb5, 0xaf, 0x0b, 0xca, - 0xe7, 0xa4, 0xab, 0x3a, 0x30, 0x4a, 0xe7, 0x0c, 0x21, 0xc5, 0x80, 0xcc, 0xba, 0x87, 0x5a, 0x81, - 0x8a, 0xce, 0xd1, 0x66, 0x34, 0xca, 0xb6, 0x08, 0xbb, 0x9c, 0x41, 0x45, 0x8f, 0xf0, 0x2a, 0x58, - 0xef, 0x13, 0xa2, 0xaa, 0xb9, 0x32, 0x87, 0x48, 0xcc, 0x70, 0x4f, 0xcf, 0xc9, 0x9a, 0xb7, 0x54, - 0xcc, 0x9c, 0x7b, 0x8a, 0xfa, 0xa0, 0x3e, 0x3f, 0xbf, 0xd2, 0xb0, 0xbf, 0x07, 0x0d, 0x5d, 0xcc, - 0xfb, 0xf8, 0x42, 0x98, 0x9b, 0xd0, 0x50, 0x55, 0x41, 0x84, 0x20, 0xc2, 0x32, 0x74, 0x0f, 0x28, - 0x09, 0xb6, 0x84, 0x8d, 0xeb, 0xb6, 0x2d, 0x61, 0x3e, 0x86, 0xb9, 0x98, 0xe8, 0x55, 0x40, 0x0b, - 0x36, 0xf7, 0xde, 0x75, 0x26, 0xd8, 0x85, 0x9d, 0xeb, 0x00, 0xbd, 0x1c, 0xcd, 0xe6, 0xe5, 0x8e, - 0x37, 0x36, 0x6c, 0x84, 0x79, 0x3e, 0xae, 0xf4, 0x27, 0xaf, 0xa4, 0x74, 0x0c, 0xaf, 0xd4, 0xf9, - 0x36, 0x34, 0xf7, 0xd3, 0x6b, 0x7f, 0x48, 0x85, 0xbc, 0xec, 0x96, 0x85, 0xaa, 0x5b, 0x1e, 0xc0, - 0x52, 0x36, 0x38, 0xcf, 0x98, 0x6e, 0x48, 0xe6, 0xb7, 0x01, 0xb2, 0x89, 0xab, 0x1a, 0x59, 0xda, - 0xb2, 0x1b, 0x19, 0xe5, 0x28, 0x18, 0x99, 0x75, 0xd3, 0x23, 0xb3, 0xce, 0xf6, 0x60, 0xf9, 0x5c, - 0xe0, 0x9f, 0xe6, 0x5b, 0xd5, 0xa3, 0x58, 0x98, 0xaf, 0xc1, 0xac, 0xaa, 0xa1, 0x0c, 0xa8, 0xee, - 0xcd, 0x24, 0x02, 0x1f, 0xe9, 0xae, 0x5d, 0x6e, 0x6e, 0x2c, 0xf6, 0x69, 0x20, 0xac, 0xe9, 0xed, - 0xda, 0x4e, 0xdd, 0x5b, 0x1a, 0x94, 0xe2, 0x47, 0x81, 0xb0, 0x7f, 0x06, 0xcd, 0x0a, 0xa0, 0xb9, - 0x04, 0xd3, 0x05, 0xd6, 0x34, 0x0d, 0xcc, 0xbb, 0xb0, 0x51, 0x02, 0x8d, 0xb6, 0xe1, 0x14, 0xb1, - 0xe1, 0xdd, 0x28, 0x18, 0x46, 0x3a, 0xb1, 0xb0, 0x1f, 0xc1, 0xfa, 0x51, 0x59, 0xf4, 0x45, 0x93, - 0x1f, 0xb9, 0xa1, 0x31, 0x3a, 0xcd, 0x37, 0xa1, 0x51, 0xfc, 0x62, 0xd1, 0xb7, 0xaf, 0x7b, 0x25, - 0xc1, 0xee, 0xc3, 0xca, 0xb9, 0xc0, 0xa7, 0x24, 0x0a, 0x4a, 0xb0, 0x6b, 0x1c, 0x70, 0x30, 0x0e, - 0x34, 0xf1, 0xfa, 0x5b, 0xaa, 0x63, 0xb0, 0x71, 0x8e, 0x42, 0x1a, 0x20, 0xc9, 0xf8, 0x29, 0x91, - 0xe9, 0x00, 0x3e, 0x41, 0xf8, 0x82, 0x48, 0x61, 0x7a, 0x50, 0x0f, 0xa9, 0x90, 0x59, 0x66, 0xdd, - 0xb9, 0x36, 0xb3, 0x92, 0x5d, 0xe7, 0x3a, 0x90, 0x43, 0x24, 0x51, 0x56, 0xbb, 0x1a, 0xcb, 0xfe, - 0x2e, 0xac, 0x7d, 0x84, 0xe4, 0x80, 0x93, 0x60, 0x24, 0xc6, 0x2b, 0x50, 0x53, 0xf1, 0x33, 0x74, - 0xfc, 0xd4, 0xa3, 0xda, 0x07, 0xac, 0x7b, 0x9f, 0xc4, 0x8c, 0x4b, 0x12, 0x5c, 0xf2, 0xc8, 0x4b, - 0xdc, 0x7b, 0x01, 0x6b, 0xca, 0x59, 0x82, 0x44, 0x81, 0x5f, 0xdc, 0x33, 0x8d, 0x63, 0x73, 0xef, - 0xc7, 0x13, 0x55, 0xc7, 0xb8, 0xba, 0xec, 0x02, 0xab, 0xc9, 0x18, 0x5d, 0xd8, 0xbf, 0x35, 0xc0, - 0x3a, 0x26, 0xc3, 0x7d, 0x21, 0x68, 0x37, 0xea, 0x93, 0x48, 0xaa, 0x1e, 0x88, 0x30, 0x51, 0x8f, - 0xe6, 0x1b, 0xb0, 0x58, 0xcc, 0x5c, 0x3d, 0x6a, 0x0d, 0x3d, 0x6a, 0x17, 0x72, 0xa2, 0x2a, 0x30, - 0xf3, 0x2e, 0x40, 0xcc, 0x49, 0xe2, 0x63, 0xff, 0x82, 0x0c, 0xb3, 0x28, 0x6e, 0x56, 0x47, 0x68, - 0xfa, 0x7b, 0xd2, 0x39, 0x19, 0x74, 0x42, 0x8a, 0x8f, 0xc9, 0xd0, 0x9b, 0x57, 0xfc, 0xed, 0x63, - 0x32, 0x54, 0x3b, 0x51, 0xcc, 0x9e, 0x11, 0xae, 0xe7, 0x5e, 0xcd, 0x4b, 0x5f, 0xec, 0xdf, 0x19, - 0x70, 0xa3, 0x08, 0x47, 0x9e, 0xae, 0x27, 0x83, 0x8e, 0x92, 0x78, 0x89, 0xdf, 0x2e, 0x59, 0x3b, - 0x7d, 0x85, 0xb5, 0xef, 0xc1, 0x42, 0x51, 0x20, 0xca, 0xde, 0xda, 0x04, 0xf6, 0x36, 0x73, 0x89, - 0x63, 0x32, 0xb4, 0x7f, 0x59, 0xb1, 0xed, 0x60, 0x58, 0xe9, 0x7d, 0xfc, 0xbf, 0xd8, 0x56, 0xa8, - 0xad, 0xda, 0x86, 0xab, 0xf2, 0x97, 0x2e, 0x50, 0xbb, 0x7c, 0x01, 0xfb, 0x0f, 0x06, 0xac, 0x57, - 0xb5, 0x8a, 0x33, 0x76, 0xc2, 0x07, 0x11, 0x79, 0x99, 0xf6, 0xb2, 0xfc, 0xa6, 0xab, 0xe5, 0xf7, - 0x18, 0x96, 0x46, 0x8c, 0x12, 0x99, 0x37, 0x7e, 0x38, 0x51, 0x8e, 0x55, 0xba, 0xab, 0xb7, 0x58, - 0xbd, 0x87, 0xb0, 0xff, 0x64, 0xc0, 0x6a, 0x6e, 0x63, 0xe1, 0x2c, 0xf3, 0x07, 0x60, 0x16, 0xd7, - 0x2b, 0xb7, 0xb7, 0x34, 0xa5, 0x56, 0xf2, 0x93, 0x7c, 0x75, 0x2b, 0x53, 0x63, 0xba, 0x92, 0x1a, - 0xe6, 0x87, 0xb0, 0x56, 0x98, 0x1c, 0xeb, 0x00, 0x4d, 0x1c, 0xc5, 0x62, 0x3f, 0x2d, 0x48, 0xf6, - 0xaf, 0x8d, 0x72, 0x1c, 0xa6, 0xf3, 0x58, 0xec, 0x87, 0x61, 0xb6, 0xd4, 0x9b, 0x31, 0xcc, 0xa5, - 0x23, 0x5f, 0x64, 0xfd, 0x63, 0xf3, 0xca, 0xe1, 0x7e, 0x48, 0xb0, 0x9e, 0xef, 0x77, 0x54, 0x89, - 0xfd, 0xe5, 0xeb, 0xad, 0xb7, 0xbb, 0x54, 0xf6, 0x06, 0x1d, 0x07, 0xb3, 0xbe, 0x9b, 0x7d, 0x0f, - 0x49, 0xff, 0xdd, 0x16, 0xc1, 0x85, 0x2b, 0x87, 0x31, 0x11, 0xb9, 0x8c, 0xf8, 0xf3, 0x3f, 0xff, - 0xf6, 0x7d, 0xc3, 0xcb, 0xd5, 0xd8, 0xbf, 0x31, 0x60, 0xe5, 0x51, 0x2c, 0x49, 0x70, 0x14, 0x95, - 0x6e, 0x9b, 0xa8, 0x08, 0x6f, 0xc1, 0x82, 0x5e, 0x0d, 0xf2, 0x8f, 0x1a, 0xa9, 0xd3, 0x9a, 0x9a, - 0x96, 0x7d, 0xb0, 0xb8, 0xb2, 0xd6, 0xd4, 0x9c, 0xab, 0xf8, 0x31, 0x5d, 0xa5, 0x1b, 0x71, 0xee, - 0xa1, 0x83, 0xc7, 0x5f, 0x3c, 0x6f, 0x19, 0x5f, 0x3e, 0x6f, 0x19, 0xff, 0x78, 0xde, 0x32, 0x3e, - 0x7d, 0xd1, 0x9a, 0xfa, 0xf2, 0x45, 0x6b, 0xea, 0xef, 0x2f, 0x5a, 0x53, 0x3f, 0x7f, 0xf7, 0xf2, - 0x35, 0xcb, 0xac, 0xb9, 0x5d, 0x7c, 0x13, 0x4b, 0x7e, 0xe4, 0x7e, 0x32, 0xfa, 0xc5, 0x4d, 0x7b, - 0xa0, 0x33, 0xab, 0xfb, 0xfb, 0x3b, 0xff, 0x09, 0x00, 0x00, 0xff, 0xff, 0x5d, 0x12, 0x76, 0xb8, - 0xa2, 0x13, 0x00, 0x00, + // 1852 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4d, 0x6f, 0x1c, 0xc7, + 0xd1, 0xe6, 0x70, 0x97, 0x1f, 0x5b, 0xcb, 0xcf, 0x21, 0x6d, 0x0d, 0xf5, 0xf2, 0x5d, 0x52, 0xe3, + 0xd8, 0x61, 0xa2, 0x68, 0x26, 0xa4, 0x13, 0x40, 0x10, 0x62, 0x18, 0xe4, 0x52, 0xb6, 0x28, 0xda, + 0x12, 0x3d, 0x64, 0x28, 0x24, 0x39, 0x0c, 0x7a, 0x7b, 0x5a, 0xbb, 0x0d, 0xce, 0x4e, 0x8f, 0xba, + 0x7b, 0x47, 0xde, 0x4b, 0xce, 0xb9, 0x04, 0x70, 0x6e, 0x46, 0x2e, 0x71, 0x02, 0x04, 0x08, 0x72, + 0x49, 0x7e, 0x86, 0x8f, 0x3e, 0xe6, 0x64, 0x07, 0xd2, 0x21, 0x87, 0x5c, 0xf3, 0x03, 0x82, 0xee, + 0xf9, 0xdc, 0x25, 0xa9, 0xac, 0xe0, 0xe4, 0x42, 0xce, 0x54, 0x57, 0x3d, 0x55, 0xdd, 0x55, 0xf5, + 0x54, 0xef, 0xc0, 0x1e, 0x8d, 0x24, 0xe1, 0xb8, 0x87, 0x68, 0xe4, 0x0b, 0x82, 0x07, 0x9c, 0xca, + 0xa1, 0x8b, 0x71, 0xe2, 0xc6, 0x9c, 0x25, 0x34, 0x20, 0xdc, 0x4d, 0x76, 0x8b, 0x67, 0x27, 0xe6, + 0x4c, 0x32, 0xf3, 0xad, 0x2b, 0x6c, 0x1c, 0x8c, 0x13, 0xa7, 0xd0, 0x4b, 0x76, 0x6f, 0xbe, 0x7d, + 0x1d, 0x70, 0xb2, 0xeb, 0x3e, 0xa7, 0x9c, 0xa4, 0x58, 0x37, 0xd7, 0xbb, 0xac, 0xcb, 0xf4, 0xa3, + 0xab, 0x9e, 0x32, 0xe9, 0x56, 0x97, 0xb1, 0x6e, 0x48, 0x5c, 0xfd, 0xd6, 0x19, 0x3c, 0x75, 0x25, + 0xed, 0x13, 0x21, 0x51, 0x3f, 0xce, 0x14, 0x5a, 0xe3, 0x0a, 0xc1, 0x80, 0x23, 0x49, 0x59, 0x94, + 0x03, 0xd0, 0x0e, 0x76, 0x31, 0xe3, 0xc4, 0xc5, 0x21, 0x25, 0x91, 0x54, 0x5e, 0xd3, 0xa7, 0x4c, + 0xc1, 0x55, 0x0a, 0x21, 0xed, 0xf6, 0x64, 0x2a, 0x16, 0xae, 0x24, 0x51, 0x40, 0x78, 0x9f, 0xa6, + 0xca, 0xe5, 0x5b, 0x66, 0xb0, 0x59, 0x59, 0xc7, 0x7c, 0x18, 0x4b, 0xe6, 0x5e, 0x90, 0xa1, 0xc8, + 0x56, 0xdf, 0xc1, 0x4c, 0xf4, 0x99, 0x70, 0x89, 0xda, 0x7f, 0x84, 0x89, 0x9b, 0xec, 0x76, 0x88, + 0x44, 0xbb, 0x85, 0x20, 0x8f, 0x3b, 0xd3, 0xeb, 0x20, 0x51, 0xea, 0x60, 0x46, 0xf3, 0xb8, 0x57, + 0x51, 0x9f, 0x46, 0xcc, 0xd5, 0x7f, 0x53, 0x91, 0xfd, 0xaf, 0x59, 0xb0, 0xda, 0x2c, 0x12, 0x83, + 0x3e, 0xe1, 0xfb, 0x41, 0x40, 0xd5, 0x2e, 0x4f, 0x38, 0x8b, 0x99, 0x40, 0xa1, 0xb9, 0x0e, 0x33, + 0x92, 0xca, 0x90, 0x58, 0xc6, 0xb6, 0xb1, 0xd3, 0xf0, 0xd2, 0x17, 0x73, 0x1b, 0x9a, 0x01, 0x11, + 0x98, 0xd3, 0x58, 0x29, 0x5b, 0xd3, 0x7a, 0xad, 0x2a, 0x32, 0x37, 0x60, 0x3e, 0x4d, 0x0d, 0x0d, + 0xac, 0x9a, 0x5e, 0x9e, 0xd3, 0xef, 0x47, 0x81, 0xf9, 0x21, 0x2c, 0xd1, 0x88, 0x4a, 0x8a, 0x42, + 0xbf, 0x47, 0xd4, 0x01, 0x59, 0xf5, 0x6d, 0x63, 0xa7, 0xb9, 0x77, 0xd3, 0xa1, 0x1d, 0xec, 0xa8, + 0x33, 0x75, 0xb2, 0x93, 0x4c, 0x76, 0x9d, 0x07, 0x5a, 0xe3, 0xa0, 0xfe, 0xe5, 0xd7, 0x5b, 0x53, + 0xde, 0x62, 0x66, 0x97, 0x0a, 0xcd, 0x5b, 0xb0, 0xd0, 0x25, 0x11, 0x11, 0x54, 0xf8, 0x3d, 0x24, + 0x7a, 0xd6, 0xcc, 0xb6, 0xb1, 0xb3, 0xe0, 0x35, 0x33, 0xd9, 0x03, 0x24, 0x7a, 0xe6, 0x16, 0x34, + 0x3b, 0x34, 0x42, 0x7c, 0x98, 0x6a, 0xcc, 0x6a, 0x0d, 0x48, 0x45, 0x5a, 0xa1, 0x0d, 0x20, 0x62, + 0xf4, 0x3c, 0xf2, 0x55, 0x01, 0x58, 0x73, 0x59, 0x20, 0x69, 0xf2, 0x9d, 0x3c, 0xf9, 0xce, 0x59, + 0x5e, 0x1d, 0x07, 0xf3, 0x2a, 0x90, 0xcf, 0xbe, 0xd9, 0x32, 0xbc, 0x86, 0xb6, 0x53, 0x2b, 0xe6, + 0x23, 0x58, 0x19, 0x44, 0x1d, 0x16, 0x05, 0x34, 0xea, 0xfa, 0x31, 0xe1, 0x94, 0x05, 0xd6, 0xbc, + 0x86, 0xda, 0xb8, 0x04, 0x75, 0x98, 0xd5, 0x51, 0x8a, 0xf4, 0xb9, 0x42, 0x5a, 0x2e, 0x8c, 0x4f, + 0xb4, 0xad, 0xf9, 0x09, 0x98, 0x18, 0x27, 0x3a, 0x24, 0x36, 0x90, 0x39, 0x62, 0x63, 0x72, 0xc4, + 0x15, 0x8c, 0x93, 0xb3, 0xd4, 0x3a, 0x83, 0xfc, 0x05, 0xdc, 0x90, 0x1c, 0x45, 0xe2, 0x29, 0xe1, + 0xe3, 0xb8, 0x30, 0x39, 0xee, 0x1b, 0x39, 0xc6, 0x28, 0xf8, 0x03, 0xd8, 0xc6, 0x59, 0x01, 0xf9, + 0x9c, 0x04, 0x54, 0x48, 0x4e, 0x3b, 0x03, 0x65, 0xeb, 0x3f, 0xe5, 0x08, 0xeb, 0x1a, 0x69, 0xea, + 0x22, 0x68, 0xe5, 0x7a, 0xde, 0x88, 0xda, 0x07, 0x99, 0x96, 0xf9, 0x18, 0xbe, 0xd3, 0x09, 0x19, + 0xbe, 0x10, 0x2a, 0x38, 0x7f, 0x04, 0x49, 0xbb, 0xee, 0x53, 0x21, 0x14, 0xda, 0xc2, 0xb6, 0xb1, + 0x53, 0xf3, 0x6e, 0xa5, 0xba, 0x27, 0x84, 0x1f, 0x56, 0x34, 0xcf, 0x2a, 0x8a, 0xe6, 0x1d, 0x30, + 0x7b, 0x54, 0x48, 0xc6, 0x29, 0x46, 0xa1, 0x4f, 0x22, 0xc9, 0x29, 0x11, 0xd6, 0xa2, 0x36, 0x5f, + 0x2d, 0x57, 0xee, 0xa7, 0x0b, 0xe6, 0x43, 0xb8, 0x75, 0xad, 0x53, 0x1f, 0xf7, 0x50, 0x14, 0x91, + 0xd0, 0x5a, 0xd2, 0x5b, 0xd9, 0x0a, 0xae, 0xf1, 0xd9, 0x4e, 0xd5, 0xcc, 0x35, 0x98, 0x91, 0x2c, + 0xf6, 0x1f, 0x59, 0xcb, 0xdb, 0xc6, 0xce, 0xa2, 0x57, 0x97, 0x2c, 0x7e, 0x74, 0x6f, 0xfe, 0x57, + 0x5f, 0x6c, 0x4d, 0x7d, 0xfe, 0xc5, 0xd6, 0x94, 0xfd, 0x17, 0x03, 0x6e, 0xb4, 0x8b, 0xd3, 0xe8, + 0xb3, 0x04, 0x85, 0xff, 0xcb, 0xae, 0xdb, 0x87, 0x86, 0x50, 0xe1, 0xe8, 0x3a, 0xaf, 0xbf, 0x46, + 0x9d, 0xcf, 0x2b, 0x33, 0xb5, 0x60, 0xff, 0xce, 0x80, 0xf5, 0xfb, 0xcf, 0x06, 0x34, 0x61, 0x18, + 0xfd, 0x57, 0x48, 0xe2, 0x18, 0x16, 0x49, 0x05, 0x4f, 0x58, 0xb5, 0xed, 0xda, 0x4e, 0x73, 0xef, + 0x6d, 0x27, 0x25, 0x31, 0xa7, 0xe0, 0xb6, 0x8c, 0xc8, 0x9c, 0xaa, 0x77, 0x6f, 0xd4, 0xf6, 0xde, + 0xb4, 0x65, 0xd8, 0x7f, 0x30, 0xe0, 0xa6, 0x3a, 0xfe, 0x2e, 0xf1, 0xc8, 0x73, 0xc4, 0x83, 0x43, + 0x12, 0xb1, 0xbe, 0xf8, 0xd6, 0x71, 0xda, 0xb0, 0x18, 0x68, 0x24, 0x5f, 0x32, 0x1f, 0x05, 0x81, + 0x8e, 0x53, 0xeb, 0x28, 0xe1, 0x19, 0xdb, 0x0f, 0x02, 0x73, 0x07, 0x56, 0x4a, 0x1d, 0xae, 0xf2, + 0xa9, 0x8e, 0x59, 0xa9, 0x2d, 0xe5, 0x6a, 0x3a, 0xcb, 0xc4, 0xfe, 0xa7, 0x01, 0x2b, 0x1f, 0x86, + 0xac, 0x83, 0xc2, 0xd3, 0x10, 0x89, 0x9e, 0x2a, 0xbd, 0xa1, 0x4a, 0x0f, 0x27, 0x59, 0xcf, 0xeb, + 0xf0, 0x26, 0x4e, 0x8f, 0x32, 0xd3, 0x2c, 0xf4, 0x3e, 0xac, 0x16, 0x5d, 0x58, 0x54, 0x81, 0xde, + 0xcd, 0xc1, 0xda, 0x8b, 0xaf, 0xb7, 0x96, 0xf3, 0x62, 0x6b, 0xeb, 0x8a, 0x38, 0xf4, 0x96, 0xf1, + 0x88, 0x20, 0x30, 0x5b, 0xd0, 0xa4, 0x1d, 0xec, 0x0b, 0xf2, 0xcc, 0x8f, 0x06, 0x7d, 0x5d, 0x40, + 0x75, 0xaf, 0x41, 0x3b, 0xf8, 0x94, 0x3c, 0x7b, 0x34, 0xe8, 0x9b, 0xef, 0xc2, 0x9b, 0xf9, 0x00, + 0xf6, 0x13, 0x14, 0xfa, 0xca, 0x5e, 0x1d, 0x07, 0xd7, 0xf5, 0xb4, 0xe0, 0xad, 0xe5, 0xab, 0xe7, + 0x28, 0x54, 0xce, 0xf6, 0x83, 0x80, 0xdb, 0x2f, 0x67, 0x60, 0xf6, 0x04, 0x71, 0xd4, 0x17, 0xe6, + 0x19, 0x2c, 0x4b, 0xd2, 0x8f, 0x43, 0x24, 0x89, 0x9f, 0x32, 0x7c, 0xb6, 0xd3, 0xdb, 0x9a, 0xf9, + 0xab, 0xc3, 0xd2, 0xa9, 0x8c, 0xc7, 0x64, 0xd7, 0x69, 0x6b, 0xe9, 0xa9, 0x44, 0x92, 0x78, 0x4b, + 0x39, 0x46, 0x2a, 0x34, 0xef, 0x82, 0x25, 0xf9, 0x40, 0xc8, 0x92, 0x7b, 0x4b, 0xd2, 0x49, 0x73, + 0xf9, 0x66, 0xbe, 0x9e, 0xd2, 0x55, 0x41, 0x36, 0x57, 0xd3, 0x6c, 0xed, 0xdb, 0xd0, 0xec, 0x29, + 0xac, 0xa9, 0x19, 0x35, 0x8e, 0x59, 0x9f, 0x1c, 0x73, 0x55, 0xd9, 0x8f, 0x82, 0x7e, 0x02, 0x66, + 0x22, 0xf0, 0x38, 0xe6, 0xcc, 0x6b, 0xc4, 0x99, 0x08, 0x3c, 0x0a, 0x19, 0xc0, 0xa6, 0x50, 0xc5, + 0xe7, 0xf7, 0x89, 0xd4, 0xa4, 0x1d, 0x87, 0x24, 0xa2, 0xa2, 0x97, 0x83, 0xcf, 0x4e, 0x0e, 0xbe, + 0xa1, 0x81, 0x3e, 0x56, 0x38, 0x5e, 0x0e, 0x93, 0x79, 0x69, 0x43, 0xeb, 0x6a, 0x2f, 0x45, 0x82, + 0xe6, 0x74, 0x82, 0xfe, 0xef, 0x0a, 0x88, 0x22, 0x4b, 0x02, 0xde, 0xa9, 0x0c, 0x17, 0xd5, 0xd5, + 0xbe, 0x6e, 0x28, 0x9f, 0x93, 0xae, 0x62, 0x60, 0x94, 0xce, 0x19, 0x42, 0x8a, 0x01, 0x99, 0xb1, + 0x87, 0xba, 0x02, 0x15, 0xcc, 0xd1, 0x66, 0x34, 0xca, 0x6e, 0x11, 0x76, 0x39, 0x83, 0x0a, 0x8e, + 0xf0, 0x2a, 0x58, 0x1f, 0x10, 0xa2, 0xba, 0xb9, 0x32, 0x87, 0x48, 0xcc, 0x70, 0x4f, 0xcf, 0xc9, + 0x9a, 0xb7, 0x54, 0xcc, 0x9c, 0xfb, 0x4a, 0xfa, 0xb0, 0x3e, 0x3f, 0xbf, 0xd2, 0xb0, 0xbf, 0x07, + 0x0d, 0xdd, 0xcc, 0xfb, 0xf8, 0x42, 0x98, 0x9b, 0xd0, 0x50, 0x5d, 0x41, 0x84, 0x20, 0xc2, 0x32, + 0x34, 0x07, 0x94, 0x02, 0x5b, 0xc2, 0xc6, 0x75, 0xb7, 0x2d, 0x61, 0x3e, 0x81, 0xb9, 0x98, 0xe8, + 0xab, 0x80, 0x36, 0x6c, 0xee, 0xbd, 0xe7, 0x4c, 0x70, 0x17, 0x76, 0xae, 0x03, 0xf4, 0x72, 0x34, + 0x9b, 0x97, 0x77, 0xbc, 0xb1, 0x61, 0x23, 0xcc, 0xf3, 0x71, 0xa7, 0x3f, 0x79, 0x2d, 0xa7, 0x63, + 0x78, 0xa5, 0xcf, 0xdb, 0xd0, 0xdc, 0x4f, 0xb7, 0xfd, 0x11, 0x15, 0xf2, 0xf2, 0xb1, 0x2c, 0x54, + 0x8f, 0xe5, 0x21, 0x2c, 0x65, 0x83, 0xf3, 0x8c, 0x69, 0x42, 0x32, 0xff, 0x1f, 0x20, 0x9b, 0xb8, + 0x8a, 0xc8, 0x52, 0xca, 0x6e, 0x64, 0x92, 0xa3, 0x60, 0x64, 0xd6, 0x4d, 0x8f, 0xcc, 0x3a, 0xdb, + 0x83, 0xe5, 0x73, 0x81, 0x7f, 0x9a, 0xdf, 0xaa, 0x1e, 0xc7, 0xc2, 0x7c, 0x03, 0x66, 0x55, 0x0f, + 0x65, 0x40, 0x75, 0x6f, 0x26, 0x11, 0xf8, 0x48, 0xb3, 0x76, 0x79, 0x73, 0x63, 0xb1, 0x4f, 0x03, + 0x61, 0x4d, 0x6f, 0xd7, 0x76, 0xea, 0xde, 0xd2, 0xa0, 0x34, 0x3f, 0x0a, 0x84, 0xfd, 0x33, 0x68, + 0x56, 0x00, 0xcd, 0x25, 0x98, 0x2e, 0xb0, 0xa6, 0x69, 0x60, 0xde, 0x83, 0x8d, 0x12, 0x68, 0x94, + 0x86, 0x53, 0xc4, 0x86, 0x77, 0xa3, 0x50, 0x18, 0x61, 0x62, 0x61, 0x3f, 0x86, 0xf5, 0xa3, 0xb2, + 0xe9, 0x0b, 0x92, 0x1f, 0xd9, 0xa1, 0x31, 0x3a, 0xcd, 0x37, 0xa1, 0x51, 0xfc, 0x62, 0xd1, 0xbb, + 0xaf, 0x7b, 0xa5, 0xc0, 0xee, 0xc3, 0xca, 0xb9, 0xc0, 0xa7, 0x24, 0x0a, 0x4a, 0xb0, 0x6b, 0x0e, + 0xe0, 0x60, 0x1c, 0x68, 0xe2, 0xeb, 0x6f, 0xe9, 0x8e, 0xc1, 0xc6, 0x39, 0x0a, 0x69, 0x80, 0x24, + 0xe3, 0xa7, 0x44, 0xa6, 0x03, 0xf8, 0x04, 0xe1, 0x0b, 0x22, 0x85, 0xe9, 0x41, 0x3d, 0xa4, 0x42, + 0x66, 0x95, 0x75, 0xf7, 0xda, 0xca, 0x4a, 0x76, 0x9d, 0xeb, 0x40, 0x0e, 0x91, 0x44, 0x59, 0xef, + 0x6a, 0x2c, 0xfb, 0xbb, 0xb0, 0xf6, 0x31, 0x92, 0x03, 0x4e, 0x82, 0x91, 0x1c, 0xaf, 0x40, 0x4d, + 0xe5, 0xcf, 0xd0, 0xf9, 0x53, 0x8f, 0xea, 0x3e, 0x60, 0xdd, 0xff, 0x34, 0x66, 0x5c, 0x92, 0xe0, + 0xd2, 0x89, 0xbc, 0xe2, 0x78, 0x2f, 0x60, 0x4d, 0x1d, 0x96, 0x20, 0x51, 0xe0, 0x17, 0xfb, 0x4c, + 0xf3, 0xd8, 0xdc, 0xfb, 0xf1, 0x44, 0xdd, 0x31, 0xee, 0x2e, 0xdb, 0xc0, 0x6a, 0x32, 0x26, 0x17, + 0xf6, 0x6f, 0x0c, 0xb0, 0x8e, 0xc9, 0x70, 0x5f, 0x08, 0xda, 0x8d, 0xfa, 0x24, 0x92, 0x8a, 0x03, + 0x11, 0x26, 0xea, 0xd1, 0x7c, 0x0b, 0x16, 0x8b, 0x99, 0xab, 0x47, 0xad, 0xa1, 0x47, 0xed, 0x42, + 0x2e, 0x54, 0x0d, 0x66, 0xde, 0x03, 0x88, 0x39, 0x49, 0x7c, 0xec, 0x5f, 0x90, 0x61, 0x96, 0xc5, + 0xcd, 0xea, 0x08, 0x4d, 0x7f, 0x4f, 0x3a, 0x27, 0x83, 0x4e, 0x48, 0xf1, 0x31, 0x19, 0x7a, 0xf3, + 0x4a, 0xbf, 0x7d, 0x4c, 0x86, 0xea, 0x4e, 0x14, 0xb3, 0xe7, 0x84, 0xeb, 0xb9, 0x57, 0xf3, 0xd2, + 0x17, 0xfb, 0xb7, 0x06, 0xdc, 0x28, 0xd2, 0x91, 0x97, 0xeb, 0xc9, 0xa0, 0xa3, 0x2c, 0x5e, 0x71, + 0x6e, 0x97, 0xa2, 0x9d, 0xbe, 0x22, 0xda, 0xf7, 0x61, 0xa1, 0x68, 0x10, 0x15, 0x6f, 0x6d, 0x82, + 0x78, 0x9b, 0xb9, 0xc5, 0x31, 0x19, 0xda, 0xbf, 0xac, 0xc4, 0x76, 0x30, 0xac, 0x70, 0x1f, 0xff, + 0x0f, 0xb1, 0x15, 0x6e, 0xab, 0xb1, 0xe1, 0xaa, 0xfd, 0xa5, 0x0d, 0xd4, 0x2e, 0x6f, 0xc0, 0xfe, + 0xbd, 0x01, 0xeb, 0x55, 0xaf, 0xe2, 0x8c, 0x9d, 0xf0, 0x41, 0x44, 0x5e, 0xe5, 0xbd, 0x6c, 0xbf, + 0xe9, 0x6a, 0xfb, 0x3d, 0x81, 0xa5, 0x91, 0xa0, 0x44, 0x76, 0x1a, 0x3f, 0x9c, 0xa8, 0xc6, 0x2a, + 0xec, 0xea, 0x2d, 0x56, 0xf7, 0x21, 0xec, 0x3f, 0x1a, 0xb0, 0x9a, 0xc7, 0x58, 0x1c, 0x96, 0xf9, + 0x03, 0x30, 0x8b, 0xed, 0x95, 0xb7, 0xb7, 0xb4, 0xa4, 0x56, 0xf2, 0x95, 0xfc, 0xea, 0x56, 0x96, + 0xc6, 0x74, 0xa5, 0x34, 0xcc, 0x8f, 0x60, 0xad, 0x08, 0x39, 0xd6, 0x09, 0x9a, 0x38, 0x8b, 0xc5, + 0xfd, 0xb4, 0x10, 0xd9, 0xbf, 0x36, 0xca, 0x71, 0x98, 0xce, 0x63, 0xb1, 0x1f, 0x86, 0xd9, 0xa5, + 0xde, 0x8c, 0x61, 0x2e, 0x1d, 0xf9, 0x22, 0xe3, 0x8f, 0xcd, 0x2b, 0x87, 0xfb, 0x21, 0xc1, 0x7a, + 0xbe, 0xdf, 0x55, 0x2d, 0xf6, 0xe7, 0x6f, 0xb6, 0x6e, 0x77, 0xa9, 0xec, 0x0d, 0x3a, 0x0e, 0x66, + 0x7d, 0x37, 0xfb, 0x1e, 0x92, 0xfe, 0xbb, 0x23, 0x82, 0x0b, 0x57, 0x0e, 0x63, 0x22, 0x72, 0x1b, + 0xf1, 0xa7, 0x7f, 0xfc, 0xf5, 0xfb, 0x86, 0x97, 0xbb, 0x39, 0x78, 0xf2, 0xe5, 0x8b, 0x96, 0xf1, + 0xd5, 0x8b, 0x96, 0xf1, 0xf7, 0x17, 0x2d, 0xe3, 0xb3, 0x97, 0xad, 0xa9, 0xaf, 0x5e, 0xb6, 0xa6, + 0xfe, 0xf6, 0xb2, 0x35, 0xf5, 0xf3, 0xf7, 0x2e, 0x83, 0x96, 0x39, 0xba, 0x53, 0x7c, 0x81, 0x4a, + 0x7e, 0xe4, 0x7e, 0x3a, 0xfa, 0x7d, 0x4b, 0xfb, 0xeb, 0xcc, 0x6a, 0x36, 0x7d, 0xf7, 0xdf, 0x01, + 0x00, 0x00, 0xff, 0xff, 0x8f, 0xff, 0x87, 0xe4, 0x10, 0x13, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -2897,53 +2819,6 @@ func (m *ConsumerRewardsAllocation) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } -func (m *OptedInValidator) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *OptedInValidator) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *OptedInValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.PublicKey) > 0 { - i -= len(m.PublicKey) - copy(dAtA[i:], m.PublicKey) - i = encodeVarintProvider(dAtA, i, uint64(len(m.PublicKey))) - i-- - dAtA[i] = 0x22 - } - if m.Power != 0 { - i = encodeVarintProvider(dAtA, i, uint64(m.Power)) - i-- - dAtA[i] = 0x18 - } - if m.BlockHeight != 0 { - i = encodeVarintProvider(dAtA, i, uint64(m.BlockHeight)) - i-- - dAtA[i] = 0x10 - } - if len(m.ProviderAddr) > 0 { - i -= len(m.ProviderAddr) - copy(dAtA[i:], m.ProviderAddr) - i = encodeVarintProvider(dAtA, i, uint64(len(m.ProviderAddr))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func encodeVarintProvider(dAtA []byte, offset int, v uint64) int { offset -= sovProvider(v) base := offset @@ -3453,29 +3328,6 @@ func (m *ConsumerRewardsAllocation) Size() (n int) { return n } -func (m *OptedInValidator) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.ProviderAddr) - if l > 0 { - n += 1 + l + sovProvider(uint64(l)) - } - if m.BlockHeight != 0 { - n += 1 + sovProvider(uint64(m.BlockHeight)) - } - if m.Power != 0 { - n += 1 + sovProvider(uint64(m.Power)) - } - l = len(m.PublicKey) - if l > 0 { - n += 1 + l + sovProvider(uint64(l)) - } - return n -} - func sovProvider(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -7011,162 +6863,6 @@ func (m *ConsumerRewardsAllocation) Unmarshal(dAtA []byte) error { } return nil } -func (m *OptedInValidator) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProvider - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: OptedInValidator: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: OptedInValidator: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ProviderAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProvider - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthProvider - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthProvider - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.ProviderAddr = append(m.ProviderAddr[:0], dAtA[iNdEx:postIndex]...) - if m.ProviderAddr == nil { - m.ProviderAddr = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field BlockHeight", wireType) - } - m.BlockHeight = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProvider - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.BlockHeight |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Power", wireType) - } - m.Power = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProvider - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Power |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PublicKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProvider - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthProvider - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthProvider - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.PublicKey = append(m.PublicKey[:0], dAtA[iNdEx:postIndex]...) - if m.PublicKey == nil { - m.PublicKey = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipProvider(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthProvider - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipProvider(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 016224219fc0a2f9453cc5c22e1ec0fc9a889b48 Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 13 Mar 2024 12:58:34 +0100 Subject: [PATCH 02/14] nit change --- x/ccv/provider/keeper/relay.go | 8 +++----- x/ccv/provider/keeper/relay_test.go | 6 ++++++ x/ccv/provider/keeper/validator_set_update.go | 9 +++++---- x/ccv/provider/types/keys_test.go | 1 - 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 14c4e9c712..761c73614d 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -225,17 +225,15 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { var nextValidators []providertypes.ConsumerValidator + k.SetTopN(ctx, chain.ChainId, 100) if k.IsTopN(ctx, chain.ChainId) { topN, _ := k.GetTopN(ctx, chain.ChainId) threshold := sdk.NewDec(int64(topN)).QuoInt64(100) k.OptInValidators(ctx, chain.ChainId, threshold, bondedValidators) } - if k.IsOptIn(ctx, chain.ChainId) || k.IsTopN(ctx, chain.ChainId) { - //nextValidators = k.ComputeNextEpochOptedInConsumerValSet(ctx, chain.ChainId) - } else { - nextValidators = k.ComputeNextEpochConsumerValSet(ctx, chain.ChainId, bondedValidators) - } + nextValidators = k.ComputeNextEpochOptedInConsumerValSet(ctx, chain.ChainId) + // nextValidators = k.ComputeNextEpochConsumerValSet(ctx, chain.ChainId, bondedValidators) valUpdates := DiffValidators(currentValidators, nextValidators) k.SetConsumerValSet(ctx, chain.ChainId, nextValidators) diff --git a/x/ccv/provider/keeper/relay_test.go b/x/ccv/provider/keeper/relay_test.go index b7e89b3dc2..b193e76514 100644 --- a/x/ccv/provider/keeper/relay_test.go +++ b/x/ccv/provider/keeper/relay_test.go @@ -672,6 +672,12 @@ func TestEndBlockVSU(t *testing.T) { mocks.MockStakingKeeper.EXPECT().GetLastValidators(gomock.Any()).Return(lastValidators).AnyTimes() mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), gomock.Any()).Return(int64(2)).AnyTimes() + for _, val := range lastValidators { + consAddr, _ := val.GetConsAddr() + // FIXME: probably `consAddr` is not needed + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(gomock.Any(), consAddr).Return(val, true).AnyTimes() + } + // set a sample client for a consumer chain so that `GetAllConsumerChains` in `QueueVSCPackets` iterates at least once providerKeeper.SetConsumerClientId(ctx, "chainID", "clientID") diff --git a/x/ccv/provider/keeper/validator_set_update.go b/x/ccv/provider/keeper/validator_set_update.go index ae6d6fbf5d..47bcdd17ef 100644 --- a/x/ccv/provider/keeper/validator_set_update.go +++ b/x/ccv/provider/keeper/validator_set_update.go @@ -271,18 +271,19 @@ func (k Keeper) ComputePowerThreshold(ctx sdk.Context, totalPower = totalPower + power } - // sort by powers ascending + // sort by powers descending sort.SliceStable(powers, func(i, j int) bool { - return powers[i] < powers[j] + return powers[i] > powers[j] }) powerSum := sdk.ZeroDec() for _, power := range powers { powerSum = powerSum.Add(sdk.NewDecFromInt(sdk.NewInt(power))) - if powerSum.Quo(sdk.NewDecFromInt(sdk.NewInt(totalPower))).GT(threshold) { + // FIXME: problematic with equal power + if powerSum.Quo(sdk.NewDecFromInt(sdk.NewInt(totalPower))).GTE(threshold) { return power } } - panic("UpdateSoftOptOutThresholdPower should not reach this point. Incorrect logic!") + panic("...") } diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index f24c6ec476..d7cfd10fd4 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -58,7 +58,6 @@ func getAllKeyPrefixes() []byte { providertypes.ProposedConsumerChainByteKey, providertypes.ConsumerValidatorBytePrefix, providertypes.TopNBytePrefix, - providertypes.OptedInBytePrefix, providertypes.ToBeOptedInBytePrefix, providertypes.ToBeOptedOutBytePrefix, providertypes.ConsumerRewardsAllocationBytePrefix, From 7ad2993b731da7e5290be548b70dd1c9cd203d02 Mon Sep 17 00:00:00 2001 From: insumity Date: Tue, 19 Mar 2024 13:50:04 +0100 Subject: [PATCH 03/14] cleaning up --- tests/mbt/driver/setup.go | 3 +- testutil/ibc_testing/generic_setup.go | 9 +- x/ccv/provider/keeper/keeper.go | 40 +++- x/ccv/provider/keeper/key_assignment_test.go | 5 +- x/ccv/provider/keeper/partial_set_security.go | 97 ++++++++ x/ccv/provider/keeper/proposal.go | 20 +- x/ccv/provider/keeper/relay.go | 23 +- x/ccv/provider/keeper/relay_test.go | 31 ++- x/ccv/provider/keeper/validator_set_update.go | 207 ++++-------------- .../keeper/validator_set_update_test.go | 186 +++++++++------- 10 files changed, 360 insertions(+), 261 deletions(-) diff --git a/tests/mbt/driver/setup.go b/tests/mbt/driver/setup.go index 6408f0e873..deeae01f6b 100644 --- a/tests/mbt/driver/setup.go +++ b/tests/mbt/driver/setup.go @@ -373,7 +373,8 @@ func (s *Driver) ConfigureNewPath(consumerChain, providerChain *ibctesting.TestC stakingValidators = append(stakingValidators, v) } - nextValidators := s.providerKeeper().ComputeNextEpochConsumerValSet(s.providerCtx(), string(consumerChainId), stakingValidators) + considerAll := func(validator stakingtypes.Validator) bool { return true } + nextValidators := s.providerKeeper().ComputeNextEpochConsumerValSet(s.providerCtx(), string(consumerChainId), stakingValidators, considerAll) s.providerKeeper().SetConsumerValSet(s.providerCtx(), string(consumerChainId), nextValidators) err = s.providerKeeper().SetConsumerGenesis( diff --git a/testutil/ibc_testing/generic_setup.go b/testutil/ibc_testing/generic_setup.go index da00d76b85..7a87e05ab1 100644 --- a/testutil/ibc_testing/generic_setup.go +++ b/testutil/ibc_testing/generic_setup.go @@ -140,6 +140,11 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp]( prop := testkeeper.GetTestConsumerAdditionProp() prop.ChainId = chainID prop.Top_N = consumerTopNParams[index] // isn't used in CreateConsumerClient + + // set the consumer TopN here since the test suite setup only used the consumer addition prop + // to create the consumer genesis, see BeginBlockInit in /x/ccv/provider/keeper/proposal.go. + providerKeeper.SetTopN(providerChain.GetContext(), chainID, prop.Top_N) + // 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} @@ -149,10 +154,6 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp]( ) s.Require().NoError(err) - // set the consumer TopN here since the test suite setup only used the consumer addition prop - // to create the consumer genesis, see BeginBlockInit in /x/ccv/provider/keeper/proposal.go. - providerKeeper.SetTopN(providerChain.GetContext(), chainID, prop.Top_N) - // commit the state on the provider chain coordinator.CommitBlock(providerChain) diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 154307dfef..a020082783 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -1174,13 +1174,13 @@ func (k Keeper) GetTopN( return binary.BigEndian.Uint32(buf), true } -// IsTopN returns true if chain with `chainID` is a Top N chain (i.e., enforces at least one validator to validate chain `chainID`) +// IsTopN returns true if chain with `chainID` is a Top-N chain (i.e., enforces at least one validator to validate chain `chainID`) func (k Keeper) IsTopN(ctx sdk.Context, chainID string) bool { topN, found := k.GetTopN(ctx, chainID) return found && topN > 0 } -// IsOptIn returns true if chain with `chainID` is an Opt In chain (i.e., no validator is forced to validate chain `chainID`) +// IsOptIn returns true if chain with `chainID` is an Opt-In chain (i.e., no validator is forced to validate chain `chainID`) func (k Keeper) IsOptIn(ctx sdk.Context, chainID string) bool { topN, found := k.GetTopN(ctx, chainID) return !found || topN == 0 @@ -1326,6 +1326,24 @@ func (k Keeper) DeleteToBeOptedIn( store.Delete(types.ToBeOptedInKey(chainID, providerAddr)) } +// DeleteAllToBeOptedIn FIXME +func (k Keeper) DeleteAllToBeOptedIn( + ctx sdk.Context, + chainID string) { + store := ctx.KVStore(k.storeKey) + key := types.ChainIdWithLenKey(types.ToBeOptedInBytePrefix, 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) + } +} + func (k Keeper) IsToBeOptedIn( ctx sdk.Context, chainID string, @@ -1371,6 +1389,24 @@ func (k Keeper) DeleteToBeOptedOut( store.Delete(types.ToBeOptedOutKey(chainID, providerAddr)) } +// DeleteAllToBeOptedOut FIXME +func (k Keeper) DeleteAllToBeOptedOut( + ctx sdk.Context, + chainID string) { + store := ctx.KVStore(k.storeKey) + key := types.ChainIdWithLenKey(types.ToBeOptedOutBytePrefix, 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) + } +} + func (k Keeper) IsToBeOptedOut( ctx sdk.Context, chainID string, diff --git a/x/ccv/provider/keeper/key_assignment_test.go b/x/ccv/provider/keeper/key_assignment_test.go index 7cb222a3be..c603e6658b 100644 --- a/x/ccv/provider/keeper/key_assignment_test.go +++ b/x/ccv/provider/keeper/key_assignment_test.go @@ -766,7 +766,10 @@ func TestSimulatedAssignmentsAndUpdateApplication(t *testing.T) { }) } - nextValidators := k.ComputeNextEpochConsumerValSet(ctx, CHAINID, bondedValidators) + nextValidators := k.ComputeNextEpochConsumerValSet(ctx, CHAINID, bondedValidators, + func(validator stakingtypes.Validator) bool { + return true + }) updates = providerkeeper.DiffValidators(k.GetConsumerValSet(ctx, CHAINID), nextValidators) k.SetConsumerValSet(ctx, CHAINID, nextValidators) diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go index bf76e7d7c8..6b8fdf07f2 100644 --- a/x/ccv/provider/keeper/partial_set_security.go +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -2,11 +2,15 @@ package keeper import ( errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" + "sort" ) +// HandleOptIn prepares validator `providerAddr` to opt in to `chainID` with an optional `consumerKey` consumer public key. +// Note that the validator only opts in at the end of an epoch. func (k Keeper) HandleOptIn(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress, consumerKey *string) error { if !k.IsConsumerProposedOrRegistered(ctx, chainID) { return errorsmod.Wrapf( @@ -42,6 +46,8 @@ func (k Keeper) HandleOptIn(ctx sdk.Context, chainID string, providerAddr types. return nil } +// HandleOptOut prepares validator `providerAddr` to opt out from running `chainID`. +// Note that the validator only opts out at the end of an epoch. func (k Keeper) HandleOptOut(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress) error { if _, found := k.GetConsumerClientId(ctx, chainID); !found { // A validator can only opt out from a running chain. We check this by checking the consumer client id, because @@ -61,3 +67,94 @@ func (k Keeper) HandleOptOut(ctx sdk.Context, chainID string, providerAddr types return nil } + +// OptInToBeOptedInValidators opts in all the to-be-opted-in validators at the end of an epoch. +// Note that validators that are not currently active can still opt in but are filtered out at a later stage +// (e.g., `ComputeNextEpochConsumerValSet`) before we send the validator set to a consumer chain. +func (k Keeper) OptInToBeOptedInValidators(ctx sdk.Context, chainID string) { + for _, providerConsAddress := range k.GetAllToBeOptedIn(ctx, chainID) { + stakingValidator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerConsAddress.ToSdkConsAddr()) + if !found { + // this should never happen but is recoverable if we do not opt in this validator + k.Logger(ctx).Error("could not get validator by consensus address", + "consensus address", providerConsAddress.ToSdkConsAddr()) + continue + } + + consumerValidator, err := k.CreateConsumerValidator(ctx, chainID, stakingValidator) + if err != nil { + k.Logger(ctx).Error("could not create consumer validator", + "validator", stakingValidator.GetOperator().String(), + "error", err) + continue + } + + // if validator already exists it gets overwritten + k.SetOptedIn(ctx, chainID, consumerValidator) + } + + k.DeleteAllToBeOptedIn(ctx, chainID) +} + +// OptInToBeOptedInValidators opts out all the to-be-opted-out validators at the end of an epoch. +func (k Keeper) OptOutToBeOptedOutValidators(ctx sdk.Context, chainID string) { + for _, providerConsAddress := range k.GetAllToBeOptedOut(ctx, chainID) { + k.DeleteOptedIn(ctx, chainID, providerConsAddress) + } + + k.DeleteAllToBeOptedOut(ctx, chainID) +} + +// OptInTopNValidators opts in to `chainID` all the active validators that have power greater or equal to `powerThreshold` +func (k Keeper) OptInTopNValidators(ctx sdk.Context, chainID string, powerThreshold int64) { + for _, val := range k.stakingKeeper.GetLastValidators(ctx) { + power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()) + if power >= powerThreshold { + consumerValidator, err := k.CreateConsumerValidator(ctx, chainID, val) + if err != nil { + k.Logger(ctx).Error("could not create consumer validator", + "validator", val.GetOperator().String(), + "error", err) + continue + } + // if validator already exists it gets overwritten + k.SetOptedIn(ctx, chainID, consumerValidator) + } else { + // validators that do not belong to the top N validators but were opted in, remain opted in + } + } +} + +// ComputePowerThreshold returns the Nth percentile based on the given `threshold` +func (k Keeper) ComputePowerThreshold(ctx sdk.Context, topN math.LegacyDec) int64 { + totalPower := sdk.ZeroDec() + var powers []int64 + + k.stakingKeeper.IterateLastValidatorPowers(ctx, func(addr sdk.ValAddress, power int64) (stop bool) { + powers = append(powers, power) + totalPower = totalPower.Add(sdk.NewDecFromInt(sdk.NewInt(power))) + return false + }) + + // sort by powers descending + sort.Slice(powers, func(i, j int) bool { + return powers[i] > powers[j] + }) + + powerSum := sdk.ZeroDec() + for _, power := range powers { + powerSum = powerSum.Add(sdk.NewDecFromInt(sdk.NewInt(power))) + if powerSum.Quo(totalPower).GTE(topN) { + return power + } + } + + // We should never reach this point because the topN can be up to 1.0 (100%) and in the above `for` loop we + // perform an equal comparison as well (`GTE`). In any case, we do not have to panic here because we can return + // the smallest possible power. + k.Logger(ctx).Error("should never reach this point", + "topN", topN, + "totalPower", totalPower, + "powerSum", powerSum) + return powers[len(powers)-1] +} diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index e8d50133ba..c89109b7be 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -289,7 +289,25 @@ func (k Keeper) MakeConsumerGenesis( bondedValidators = append(bondedValidators, val) } - nextValidators := k.ComputeNextEpochConsumerValSet(ctx, chainID, bondedValidators) + var nextValidators []types.ConsumerValidator + + k.OptInToBeOptedInValidators(ctx, chainID) + + considerOnlyOptIn := func(validator stakingtypes.Validator) bool { + consAddr, err := validator.GetConsAddr() + if err != nil { + return false + } + return k.IsOptedIn(ctx, chainID, types.NewProviderConsAddress(consAddr)) + } + + if topN, found := k.GetTopN(ctx, chainID); found { + // in a Top-N chain, we automatically opt in all validators that belong to the top N + threshold := sdk.NewDec(int64(topN)).QuoInt64(100) + k.OptInTopNValidators(ctx, chainID, threshold) + } + + nextValidators = k.ComputeNextEpochConsumerValSet(ctx, chainID, bondedValidators, considerOnlyOptIn) k.SetConsumerValSet(ctx, chainID, nextValidators) // get the initial updates with the latest set consumer public keys diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 761c73614d..f1403ca21d 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -221,19 +221,28 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { bondedValidators := k.stakingKeeper.GetLastValidators(ctx) for _, chain := range k.GetAllConsumerChains(ctx) { - currentValidators := k.GetConsumerValSet(ctx, chain.ChainId) + // FIXME: add comment that this should take place before `OptInTopNValidators` + k.OptOutToBeOptedOutValidators(ctx, chain.ChainId) + k.OptInToBeOptedInValidators(ctx, chain.ChainId) + currentValidators := k.GetConsumerValSet(ctx, chain.ChainId) var nextValidators []providertypes.ConsumerValidator - k.SetTopN(ctx, chain.ChainId, 100) - if k.IsTopN(ctx, chain.ChainId) { - topN, _ := k.GetTopN(ctx, chain.ChainId) + considerOnlyOptIn := func(validator stakingtypes.Validator) bool { + consAddr, err := validator.GetConsAddr() + if err != nil { + return false + } + return k.IsOptedIn(ctx, chain.ChainId, providertypes.NewProviderConsAddress(consAddr)) + } + + if topN, found := k.GetTopN(ctx, chain.ChainId); found { + // in a Top-N chain, we automatically opt in all validators that belong to the top N threshold := sdk.NewDec(int64(topN)).QuoInt64(100) - k.OptInValidators(ctx, chain.ChainId, threshold, bondedValidators) + k.OptInTopNValidators(ctx, chain.ChainId, threshold) } - nextValidators = k.ComputeNextEpochOptedInConsumerValSet(ctx, chain.ChainId) - // nextValidators = k.ComputeNextEpochConsumerValSet(ctx, chain.ChainId, bondedValidators) + nextValidators = k.ComputeNextEpochConsumerValSet(ctx, chain.ChainId, bondedValidators, considerOnlyOptIn) valUpdates := DiffValidators(currentValidators, nextValidators) k.SetConsumerValSet(ctx, chain.ChainId, nextValidators) diff --git a/x/ccv/provider/keeper/relay_test.go b/x/ccv/provider/keeper/relay_test.go index b193e76514..c4f4423aff 100644 --- a/x/ccv/provider/keeper/relay_test.go +++ b/x/ccv/provider/keeper/relay_test.go @@ -659,6 +659,10 @@ func TestEndBlockVSU(t *testing.T) { providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() + chainID := "chainID" + + providerKeeper.SetTopN(ctx, chainID, 100) + // 10 blocks constitute an epoch params := providertypes.DefaultParams() params.BlocksPerEpoch = 10 @@ -666,9 +670,24 @@ func TestEndBlockVSU(t *testing.T) { // create 4 sample lastValidators var lastValidators []stakingtypes.Validator + var valAddresses []sdk.ValAddress + var powers []int64 for i := 0; i < 4; i++ { - lastValidators = append(lastValidators, crypto.NewCryptoIdentityFromIntSeed(i).SDKStakingValidator()) + validator := crypto.NewCryptoIdentityFromIntSeed(i).SDKStakingValidator() + lastValidators = append(lastValidators, validator) + valAddresses = append(valAddresses, validator.GetOperator()) + powers = append(powers, int64(i+1)) } + + mocks.MockStakingKeeper.EXPECT().IterateLastValidatorPowers(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ sdk.Context, cb func(addr sdk.ValAddress, power int64) (stop bool)) { + for i, addr := range valAddresses { + if cb(addr, powers[i]) { + break + } + } + }, + ).AnyTimes() mocks.MockStakingKeeper.EXPECT().GetLastValidators(gomock.Any()).Return(lastValidators).AnyTimes() mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), gomock.Any()).Return(int64(2)).AnyTimes() @@ -679,27 +698,27 @@ func TestEndBlockVSU(t *testing.T) { } // set a sample client for a consumer chain so that `GetAllConsumerChains` in `QueueVSCPackets` iterates at least once - providerKeeper.SetConsumerClientId(ctx, "chainID", "clientID") + providerKeeper.SetConsumerClientId(ctx, chainID, "clientID") // with block height of 1 we do not expect any queueing of VSC packets ctx = ctx.WithBlockHeight(1) providerKeeper.EndBlockVSU(ctx) - require.Equal(t, 0, len(providerKeeper.GetPendingVSCPackets(ctx, "chainID"))) + require.Equal(t, 0, len(providerKeeper.GetPendingVSCPackets(ctx, chainID))) // with block height of 5 we do not expect any queueing of VSC packets ctx = ctx.WithBlockHeight(5) providerKeeper.EndBlockVSU(ctx) - require.Equal(t, 0, len(providerKeeper.GetPendingVSCPackets(ctx, "chainID"))) + require.Equal(t, 0, len(providerKeeper.GetPendingVSCPackets(ctx, chainID))) // with block height of 10 we expect the queueing of one VSC packet ctx = ctx.WithBlockHeight(10) providerKeeper.EndBlockVSU(ctx) - require.Equal(t, 1, len(providerKeeper.GetPendingVSCPackets(ctx, "chainID"))) + require.Equal(t, 1, len(providerKeeper.GetPendingVSCPackets(ctx, chainID))) // With block height of 15 we expect no additional queueing of a VSC packet. // Note that the pending VSC packet is still there because `SendVSCPackets` does not send the packet. We // need to mock channels, etc. for this to work, and it's out of scope for this test. ctx = ctx.WithBlockHeight(15) providerKeeper.EndBlockVSU(ctx) - require.Equal(t, 1, len(providerKeeper.GetPendingVSCPackets(ctx, "chainID"))) + require.Equal(t, 1, len(providerKeeper.GetPendingVSCPackets(ctx, chainID))) } diff --git a/x/ccv/provider/keeper/validator_set_update.go b/x/ccv/provider/keeper/validator_set_update.go index 47bcdd17ef..aa04b4b4b3 100644 --- a/x/ccv/provider/keeper/validator_set_update.go +++ b/x/ccv/provider/keeper/validator_set_update.go @@ -1,13 +1,11 @@ package keeper import ( - "cosmossdk.io/math" "fmt" abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" - "sort" ) // SetConsumerValidator sets provided consumer `validator` on the consumer chain with `chainID` @@ -25,6 +23,15 @@ func (k Keeper) SetConsumerValidator( store.Set(types.ConsumerValidatorKey(chainID, validator.ProviderConsAddr), bz) } +// SetConsumerValSet resets the current consumer validators with the `nextValidators` computed by +// `ComputeNextEpochConsumerValSet` and hence this method should only be called after `ComputeNextEpochConsumerValSet` has completed. +func (k Keeper) SetConsumerValSet(ctx sdk.Context, chainID string, nextValidators []types.ConsumerValidator) { + k.DeleteConsumerValSet(ctx, chainID) + for _, val := range nextValidators { + k.SetConsumerValidator(ctx, chainID, val) + } +} + // DeleteConsumerValidator removes consumer validator with `providerAddr` address func (k Keeper) DeleteConsumerValidator( ctx sdk.Context, @@ -36,9 +43,7 @@ func (k Keeper) DeleteConsumerValidator( } // DeleteConsumerValSet deletes all the stored consumer validators for chain `chainID` -func (k Keeper) DeleteConsumerValSet( - ctx sdk.Context, - chainID string) { +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) @@ -55,19 +60,13 @@ func (k Keeper) DeleteConsumerValSet( // 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 { +func (k Keeper) IsConsumerValidator(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress) bool { store := ctx.KVStore(k.storeKey) return store.Get(types.ConsumerValidatorKey(chainID, providerAddr.ToSdkConsAddr())) != nil } // GetConsumerValSet returns all the consumer validators for chain `chainID` -func (k Keeper) GetConsumerValSet( - ctx sdk.Context, - chainID string) (validators []types.ConsumerValidator) { +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) @@ -85,51 +84,6 @@ func (k Keeper) GetConsumerValSet( return validators } -// ComputeNextEpochConsumerValSet returns the next validator set that is responsible for validating consumer -// chain `chainID`, based on the bonded validators. -func (k Keeper) ComputeNextEpochConsumerValSet( - ctx sdk.Context, - chainID string, - bondedValidators []stakingtypes.Validator, -) []types.ConsumerValidator { - var nextValidators []types.ConsumerValidator - for _, val := range bondedValidators { - // get next voting power and the next consumer public key - nextPower := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()) - consAddr, err := val.GetConsAddr() - if err != nil { - // this should never happen but is recoverable if we exclude this validator from the `nextValidators` - k.Logger(ctx).Error("could not get consensus address of validator", - "validator", val.GetOperator().String(), - "error", err) - continue - } - nextConsumerPublicKey, foundConsumerPublicKey := k.GetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(consAddr)) - if !foundConsumerPublicKey { - // if no consumer key assigned then use the validator's key itself - k.Logger(ctx).Info("could not retrieve public key for validator on consumer chain because"+ - " the validator did not assign a new consumer key", - "validator", val.GetOperator().String(), - "chainID", chainID) - nextConsumerPublicKey, err = val.TmConsPublicKey() - if err != nil { - // this should never happen and might not be recoverable because without the public key - // we cannot generate a validator update - panic(fmt.Errorf("could not retrieve validator's (%+v) public key: %w", val, err)) - } - } - - nextValidator := types.ConsumerValidator{ - ProviderConsAddr: consAddr, - Power: nextPower, - ConsumerPublicKey: &nextConsumerPublicKey, - } - nextValidators = append(nextValidators, nextValidator) - } - - return nextValidators -} - // DiffValidators compares the current and the next epoch's consumer validators and returns the `ValidatorUpdate` diff // needed by CometBFT to update the validator set on a chain. func DiffValidators( @@ -170,120 +124,53 @@ func DiffValidators( return updates } -// SetConsumerValSet resets the current consumer validators with the `nextValidators` computed by -// `ComputeNextEpochConsumerValSet` and hence this method should only be called after `ComputeNextEpochConsumerValSet` has completed. -func (k Keeper) SetConsumerValSet(ctx sdk.Context, chainID string, nextValidators []types.ConsumerValidator) { - k.DeleteConsumerValSet(ctx, chainID) - for _, val := range nextValidators { - k.SetConsumerValidator(ctx, chainID, val) +// CreateConsumerValidator creates a consumer validator for `chainID` from the given staking `validator` +func (k Keeper) CreateConsumerValidator(ctx sdk.Context, chainID string, validator stakingtypes.Validator) (types.ConsumerValidator, error) { + power := k.stakingKeeper.GetLastValidatorPower(ctx, validator.GetOperator()) + consAddr, err := validator.GetConsAddr() + if err != nil { + return types.ConsumerValidator{}, fmt.Errorf("could not retrieve validator's (%+v) consensus address: %w", + validator, err) } -} - -// ComputeNextEpochOptedInConsumerValSet returns the next validator set that is responsible for validating consumer -// chain `chainID`, based on the bonded validators. -func (k Keeper) ComputeNextEpochOptedInConsumerValSet( - ctx sdk.Context, - chainID string, -) []types.ConsumerValidator { - var nextValidators []types.ConsumerValidator - for _, val := range k.GetAllOptedIn(ctx, chainID) { - // get next voting power and the next consumer public key - providerAddress := types.ProviderConsAddress{Address: val.ProviderConsAddr} - stakingValidator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddress.ToSdkConsAddr()) - if !found { - // this should never happen but is recoverable if we exclude this validator from the `nextValidators` - k.Logger(ctx).Error("could not get consensus address of validator", - "validator", stakingValidator.GetOperator().String()) - fmt.Println("..") - } - nextPower := k.stakingKeeper.GetLastValidatorPower(ctx, stakingValidator.GetOperator()) - nextConsumerPublicKey, foundConsumerPublicKey := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddress) - if !foundConsumerPublicKey { - // if no consumer key assigned then use the validator's key itself - k.Logger(ctx).Info("could not retrieve public key for validator on consumer chain because"+ - " the validator did not assign a new consumer key", - "validator", stakingValidator.GetOperator().String(), - "chainID", chainID) - var err error - nextConsumerPublicKey, err = stakingValidator.TmConsPublicKey() - if err != nil { - // this should never happen and might not be recoverable because without the public key - // we cannot generate a validator update - panic(fmt.Errorf("could not retrieve validator's (%+v) public key: %w", val, err)) - } - } - - nextValidator := types.ConsumerValidator{ - ProviderConsAddr: val.ProviderConsAddr, - Power: nextPower, - ConsumerPublicKey: &nextConsumerPublicKey, + consumerPublicKey, foundConsumerPublicKey := k.GetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(consAddr)) + if !foundConsumerPublicKey { + consumerPublicKey, err = validator.TmConsPublicKey() + if err != nil { + return types.ConsumerValidator{}, fmt.Errorf("could not retrieve validator's (%+v) public key: %w", validator, err) } - nextValidators = append(nextValidators, nextValidator) } - return nextValidators + return types.ConsumerValidator{ + ProviderConsAddr: consAddr, + Power: power, + ConsumerPublicKey: &consumerPublicKey, + }, nil } -func (k Keeper) OptInValidators(ctx sdk.Context, chainID string, threshold math.LegacyDec, bondedValidators []stakingtypes.Validator) { - powerStop := k.ComputePowerThreshold(ctx, bondedValidators, threshold) - +// ComputeNextEpochConsumerValSet returns the next validator set that is responsible for validating consumer +// chain `chainID`. The next validator set corresponds to the `bondedValidator`s that `shouldConsider` evaluates to `true`. +func (k Keeper) ComputeNextEpochConsumerValSet( + ctx sdk.Context, + chainID string, + bondedValidators []stakingtypes.Validator, + shouldConsider func(validator stakingtypes.Validator) bool, +) []types.ConsumerValidator { + var nextValidators []types.ConsumerValidator for _, val := range bondedValidators { - power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()) - if power >= powerStop { - consAddr, err := val.GetConsAddr() - _ = err // fIXME - nextConsumerPublicKey, foundConsumerPublicKey := k.GetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(consAddr)) - if !foundConsumerPublicKey { - // if no consumer key assigned then use the validator's key itself - k.Logger(ctx).Info("could not retrieve public key for validator on consumer chain because"+ - " the validator did not assign a new consumer key", + if shouldConsider(val) { + nextValidator, err := k.CreateConsumerValidator(ctx, chainID, 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(), - "chainID", chainID) - nextConsumerPublicKey, err = val.TmConsPublicKey() - if err != nil { - // this should never happen and might not be recoverable because without the public key - // we cannot generate a validator update - panic(fmt.Errorf("could not retrieve validator's (%+v) public key: %w", val, err)) - } + "error", err) + continue } - // if validator already exists it gets overwritten - k.SetOptedIn(ctx, chainID, types.ConsumerValidator{ - ProviderConsAddr: consAddr, - Power: power, - ConsumerPublicKey: &nextConsumerPublicKey, - }) - } else { - // validators that are not on the top N and habe opte din remain opted in, - } - } -} - -func (k Keeper) ComputePowerThreshold(ctx sdk.Context, - bondedValidators []stakingtypes.Validator, - threshold math.LegacyDec, -) int64 { - totalPower := int64(0) - var powers []int64 - for _, val := range bondedValidators { - power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()) - powers = append(powers, power) - totalPower = totalPower + power - } - // sort by powers descending - sort.SliceStable(powers, func(i, j int) bool { - return powers[i] > powers[j] - }) - - powerSum := sdk.ZeroDec() - for _, power := range powers { - powerSum = powerSum.Add(sdk.NewDecFromInt(sdk.NewInt(power))) - // FIXME: problematic with equal power - if powerSum.Quo(sdk.NewDecFromInt(sdk.NewInt(totalPower))).GTE(threshold) { - return power + nextValidators = append(nextValidators, nextValidator) } } - panic("...") + return nextValidators } diff --git a/x/ccv/provider/keeper/validator_set_update_test.go b/x/ccv/provider/keeper/validator_set_update_test.go index 40a22704fa..7380d53f8d 100644 --- a/x/ccv/provider/keeper/validator_set_update_test.go +++ b/x/ccv/provider/keeper/validator_set_update_test.go @@ -107,65 +107,25 @@ func createConsumerValidator(index int, power int64, seed int) (types.ConsumerVa }, publicKey } -func TestComputeNextEpochConsumerValSet(t *testing.T) { - providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - chainID := "chainID" - - // helper function to generate a validator with the given power and with a provider address based on index - createStakingValidator := func(ctx sdk.Context, mocks testkeeper.MockedKeepers, index int, power int64) stakingtypes.Validator { - providerConsPubKey := ed25519.GenPrivKeyFromSecret([]byte{byte(index)}).PubKey() - consAddr := sdk.ConsAddress(providerConsPubKey.Address()) - providerAddr := types.NewProviderConsAddress(consAddr) - pk, _ := cryptocodec.FromTmPubKeyInterface(providerConsPubKey) - pkAny, _ := codectypes.NewAnyWithValue(pk) - - var providerValidatorAddr sdk.ValAddress - providerValidatorAddr = providerAddr.Address.Bytes() - - mocks.MockStakingKeeper.EXPECT(). - GetLastValidatorPower(ctx, providerValidatorAddr).Return(power).AnyTimes() - - return stakingtypes.Validator{ - OperatorAddress: providerValidatorAddr.String(), - ConsensusPubkey: pkAny, - } +// 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) + + var providerValidatorAddr sdk.ValAddress + providerValidatorAddr = providerAddr.Address.Bytes() + + mocks.MockStakingKeeper.EXPECT(). + GetLastValidatorPower(ctx, providerValidatorAddr).Return(power).AnyTimes() + + return stakingtypes.Validator{ + OperatorAddress: providerValidatorAddr.String(), + ConsensusPubkey: pkAny, } - - // no consumer validators returned if we have no bonded validators - require.Empty(t, providerKeeper.ComputeNextEpochConsumerValSet(ctx, chainID, []stakingtypes.Validator{})) - - var expectedValidators []types.ConsumerValidator - - // create a staking validator A that has not set a consumer public key - valA := createStakingValidator(ctx, mocks, 1, 1) - // because validator A has no consumer key set, the `ConsumerPublicKey` we expect is the key on the provider chain - valAConsAddr, _ := valA.GetConsAddr() - valAPublicKey, _ := valA.TmConsPublicKey() - expectedValidators = append(expectedValidators, types.ConsumerValidator{ - ProviderConsAddr: types.NewProviderConsAddress(valAConsAddr).Address.Bytes(), - Power: 1, - ConsumerPublicKey: &valAPublicKey, - }) - - // create a staking validator B that has set a consumer public key - valB := createStakingValidator(ctx, mocks, 2, 2) - // validator B has set a consumer key, the `ConsumerPublicKey` we expect is the key set by `SetValidatorConsumerPubKey` - valBConsumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() - valBConsAddr, _ := valB.GetConsAddr() - providerKeeper.SetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(valBConsAddr), valBConsumerKey) - expectedValidators = append(expectedValidators, types.ConsumerValidator{ - ProviderConsAddr: types.NewProviderConsAddress(valBConsAddr).Address.Bytes(), - Power: 2, - ConsumerPublicKey: &valBConsumerKey, - }) - - bondedValidators := []stakingtypes.Validator{valA, valB} - actualValidators := providerKeeper.ComputeNextEpochConsumerValSet(ctx, chainID, bondedValidators) - require.Equal(t, expectedValidators, actualValidators) } - func TestDiff(t *testing.T) { // In what follows we create 6 validators: A, B, C, D, E, and F where currentValidators = {A, B, C, D, E} // and nextValidators = {B, C, D, E, F}. For the validators {B, C, D, E} in the intersection we have: @@ -354,39 +314,61 @@ func TestSetConsumerValSet(t *testing.T) { require.Equal(t, nextValidators, nextCurrentValidators) } -func TestComputeNextEpochOptedInConsumerValSet(t *testing.T) { +func TestComputeNextEpochConsumerValSetConsiderAll(t *testing.T) { providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() chainID := "chainID" - // helper function to generate a validator with the given power and with a provider address based on index - createStakingValidator := func(ctx sdk.Context, mocks testkeeper.MockedKeepers, index int, power int64) stakingtypes.Validator { - providerConsPubKey := ed25519.GenPrivKeyFromSecret([]byte{byte(index)}).PubKey() - consAddr := sdk.ConsAddress(providerConsPubKey.Address()) - providerAddr := types.NewProviderConsAddress(consAddr) - pk, _ := cryptocodec.FromTmPubKeyInterface(providerConsPubKey) - pkAny, _ := codectypes.NewAnyWithValue(pk) + // no consumer validators returned if we have no bonded validators + considerAll := func(validator stakingtypes.Validator) bool { return true } + require.Empty(t, providerKeeper.ComputeNextEpochConsumerValSet(ctx, chainID, []stakingtypes.Validator{}, considerAll)) - var providerValidatorAddr sdk.ValAddress - providerValidatorAddr = providerAddr.Address.Bytes() + var expectedValidators []types.ConsumerValidator + + // create a staking validator A that has not set a consumer public key + valA := createStakingValidator(ctx, mocks, 1, 1) + // because validator A has no consumer key set, the `ConsumerPublicKey` we expect is the key on the provider chain + valAConsAddr, _ := valA.GetConsAddr() + valAPublicKey, _ := valA.TmConsPublicKey() + expectedValidators = append(expectedValidators, types.ConsumerValidator{ + ProviderConsAddr: types.NewProviderConsAddress(valAConsAddr).Address.Bytes(), + Power: 1, + ConsumerPublicKey: &valAPublicKey, + }) - mocks.MockStakingKeeper.EXPECT(). - GetLastValidatorPower(ctx, providerValidatorAddr).Return(power).AnyTimes() + // create a staking validator B that has set a consumer public key + valB := createStakingValidator(ctx, mocks, 2, 2) + // validator B has set a consumer key, the `ConsumerPublicKey` we expect is the key set by `SetValidatorConsumerPubKey` + valBConsumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() + valBConsAddr, _ := valB.GetConsAddr() + providerKeeper.SetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(valBConsAddr), valBConsumerKey) + expectedValidators = append(expectedValidators, types.ConsumerValidator{ + ProviderConsAddr: types.NewProviderConsAddress(valBConsAddr).Address.Bytes(), + Power: 2, + ConsumerPublicKey: &valBConsumerKey, + }) - stakingValidator := stakingtypes.Validator{ - OperatorAddress: providerValidatorAddr.String(), - ConsensusPubkey: pkAny, - } + bondedValidators := []stakingtypes.Validator{valA, valB} + actualValidators := providerKeeper.ComputeNextEpochConsumerValSet(ctx, chainID, bondedValidators, considerAll) + require.Equal(t, expectedValidators, actualValidators) +} - mocks.MockStakingKeeper.EXPECT(). - GetValidatorByConsAddr(ctx, consAddr).Return(stakingValidator, true).AnyTimes() +func TestComputeNextEpochConsumerValSetConsiderOnlyOptIn(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() - return stakingValidator - } + chainID := "chainID" // no consumer validators returned if we have no opted-in validators - require.Empty(t, providerKeeper.ComputeNextEpochOptedInConsumerValSet(ctx, chainID)) + considerOnlyOptIn := func(validator stakingtypes.Validator) bool { + consAddr, err := validator.GetConsAddr() + if err != nil { + return false + } + return providerKeeper.IsOptedIn(ctx, chainID, types.NewProviderConsAddress(consAddr)) + } + require.Empty(t, providerKeeper.ComputeNextEpochConsumerValSet(ctx, chainID, []stakingtypes.Validator{}, considerOnlyOptIn)) var expectedValidators []types.ConsumerValidator @@ -419,7 +401,8 @@ func TestComputeNextEpochOptedInConsumerValSet(t *testing.T) { providerKeeper.SetOptedIn(ctx, chainID, types.ConsumerValidator{ProviderConsAddr: valBConsAddr, Power: 0, ConsumerPublicKey: nil}) // the expected actual validators are the opted-in validators but with the correct power and consumer public keys set - actualValidators := providerKeeper.ComputeNextEpochOptedInConsumerValSet(ctx, "chainID") + bondedValidators := []stakingtypes.Validator{valA, valB} + actualValidators := providerKeeper.ComputeNextEpochConsumerValSet(ctx, "chainID", bondedValidators, considerOnlyOptIn) // sort validators first to be able to compare sortValidators := func(validators []types.ConsumerValidator) { @@ -431,4 +414,49 @@ func TestComputeNextEpochOptedInConsumerValSet(t *testing.T) { sortValidators(actualValidators) sortValidators(expectedValidators) 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) + bondedValidators = []stakingtypes.Validator{valA, valB, valC} + actualValidators = providerKeeper.ComputeNextEpochConsumerValSet(ctx, "chainID", bondedValidators, considerOnlyOptIn) + + sortValidators(actualValidators) + sortValidators(expectedValidators) + require.Equal(t, expectedValidators, actualValidators) +} + +func TestCreateConsumerValidator(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + chainID := "chainID" + + // create a validator which has set a consumer public key + valA := createStakingValidator(ctx, mocks, 0, 1) + valAConsumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() + valAConsAddr, _ := valA.GetConsAddr() + valAProviderConsAddr := types.NewProviderConsAddress(valAConsAddr) + providerKeeper.SetValidatorConsumerPubKey(ctx, chainID, valAProviderConsAddr, valAConsumerKey) + actualConsumerValidatorA, err := providerKeeper.CreateConsumerValidator(ctx, chainID, valA) + expectedConsumerValidatorA := types.ConsumerValidator{ + ProviderConsAddr: valAProviderConsAddr.ToSdkConsAddr(), + Power: 1, + ConsumerPublicKey: &valAConsumerKey, + } + require.Equal(t, expectedConsumerValidatorA, actualConsumerValidatorA) + require.NoError(t, err) + + // create a validator which has not set a consumer public key + valB := createStakingValidator(ctx, mocks, 1, 2) + valBConsAddr, _ := valB.GetConsAddr() + valBProviderConsAddr := types.NewProviderConsAddress(valBConsAddr) + valBPublicKey, _ := valB.TmConsPublicKey() + actualConsumerValidatorB, err := providerKeeper.CreateConsumerValidator(ctx, chainID, valB) + expectedConsumerValidatorB := types.ConsumerValidator{ + ProviderConsAddr: valBProviderConsAddr.ToSdkConsAddr(), + Power: 2, + ConsumerPublicKey: &valBPublicKey, + } + require.Equal(t, expectedConsumerValidatorB, actualConsumerValidatorB) + require.NoError(t, err) } From 5cd5ba5b00e35abb1c6eb5fa4973fccda030a167 Mon Sep 17 00:00:00 2001 From: insumity Date: Tue, 19 Mar 2024 15:43:33 +0100 Subject: [PATCH 04/14] clean up --- x/ccv/provider/keeper/distribution_test.go | 6 +- x/ccv/provider/keeper/hooks.go | 11 +- x/ccv/provider/keeper/hooks_test.go | 10 +- x/ccv/provider/keeper/keeper.go | 152 +------------- x/ccv/provider/keeper/keeper_test.go | 196 ++---------------- x/ccv/provider/keeper/partial_set_security.go | 86 ++------ .../keeper/partial_set_security_test.go | 131 +++++++++--- x/ccv/provider/keeper/proposal.go | 6 +- x/ccv/provider/keeper/relay.go | 8 +- .../keeper/validator_set_update_test.go | 4 +- x/ccv/provider/types/keys.go | 27 +-- x/ccv/provider/types/keys_test.go | 3 +- 12 files changed, 170 insertions(+), 470 deletions(-) diff --git a/x/ccv/provider/keeper/distribution_test.go b/x/ccv/provider/keeper/distribution_test.go index 64359e4fe4..c47dfd99e9 100644 --- a/x/ccv/provider/keeper/distribution_test.go +++ b/x/ccv/provider/keeper/distribution_test.go @@ -38,11 +38,7 @@ func TestComputeConsumerTotalVotingPower(t *testing.T) { keeper.SetOptedIn( ctx, chainID, - types.ConsumerValidator{ - ProviderConsAddr: val.Address, - Power: val.VotingPower, - //ConsumerPublicKey: val., FIXME - }, + types.NewProviderConsAddress(val.Address.Bytes()), ) validatorsVotes = append( diff --git a/x/ccv/provider/keeper/hooks.go b/x/ccv/provider/keeper/hooks.go index fcf8339187..bf5b718fe5 100644 --- a/x/ccv/provider/keeper/hooks.go +++ b/x/ccv/provider/keeper/hooks.go @@ -223,12 +223,13 @@ func (h Hooks) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr s return } - // in the validator is already to-be-opted in, the `SetToBeOptedIn` is a no-op - h.k.SetToBeOptedIn(ctx, chainID, providertypes.NewProviderConsAddress(consAddr)) + // in the validator is already opted in, the `SetOptedIn` is a no-op + h.k.SetOptedIn(ctx, chainID, providertypes.NewProviderConsAddress(consAddr)) } else { - // if vote is not a YES vote with 100% weight, we delete the validator from to-be-opted in - // if the validator is not to-be-opted in, the `DeleteToBeOptedIn` is a no-op - h.k.DeleteToBeOptedIn(ctx, chainID, providertypes.NewProviderConsAddress(consAddr)) + // If vote is not a YES vote with 100% weight, we opt out the validator. + // Note that a validator still gets opted out even if before it manually opted in with a `MsgOptIn` message. + // This is because if a validator wants to opt in, there's no reason to not vote YES with 100% weight. + h.k.DeleteOptedIn(ctx, chainID, providertypes.NewProviderConsAddress(consAddr)) } } diff --git a/x/ccv/provider/keeper/hooks_test.go b/x/ccv/provider/keeper/hooks_test.go index 140bb8a4ce..57a0113da7 100644 --- a/x/ccv/provider/keeper/hooks_test.go +++ b/x/ccv/provider/keeper/hooks_test.go @@ -249,9 +249,9 @@ func TestAfterProposalVoteWithYesVote(t *testing.T) { ), ) - require.False(t, k.IsToBeOptedIn(ctx, "chainID", providerAddr)) + require.False(t, k.IsOptedIn(ctx, "chainID", providerAddr)) k.Hooks().AfterProposalVote(ctx, 1, sdk.AccAddress{}) - require.True(t, k.IsToBeOptedIn(ctx, "chainID", providerAddr)) + require.True(t, k.IsOptedIn(ctx, "chainID", providerAddr)) } func TestAfterProposalVoteWithNoVote(t *testing.T) { @@ -304,10 +304,10 @@ func TestAfterProposalVoteWithNoVote(t *testing.T) { tc.setup(ctx, tc.options, mocks, pkAny) // set the validator to-be-opted in first to assert that a NO vote removes the validator from to-be-opted in - k.SetToBeOptedIn(ctx, "chainID", providerAddr) - require.True(t, k.IsToBeOptedIn(ctx, "chainID", providerAddr)) + k.SetOptedIn(ctx, "chainID", providerAddr) + require.True(t, k.IsOptedIn(ctx, "chainID", providerAddr)) k.Hooks().AfterProposalVote(ctx, 1, sdk.AccAddress{}) - require.False(t, k.IsToBeOptedIn(ctx, "chainID", providerAddr)) + require.False(t, k.IsOptedIn(ctx, "chainID", providerAddr)) }) } } diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index a020082783..789b20b7c1 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -1189,15 +1189,10 @@ func (k Keeper) IsOptIn(ctx sdk.Context, chainID string) bool { func (k Keeper) SetOptedIn( ctx sdk.Context, chainID string, - validator types.ConsumerValidator, + providerConsAddress types.ProviderConsAddress, ) { store := ctx.KVStore(k.storeKey) - bz, err := validator.Marshal() - if err != nil { - panic(fmt.Errorf("failed to marshal OptedInValidator: %w", err)) - } - - store.Set(types.ConsumerValidatorKey(chainID, validator.ProviderConsAddr), bz) + store.Set(types.OptedInKey(chainID, providerConsAddress), []byte{}) } func (k Keeper) DeleteOptedIn( @@ -1206,7 +1201,7 @@ func (k Keeper) DeleteOptedIn( providerAddr types.ProviderConsAddress, ) { store := ctx.KVStore(k.storeKey) - store.Delete(types.ConsumerValidatorKey(chainID, providerAddr.ToSdkConsAddr())) + store.Delete(types.OptedInKey(chainID, providerAddr)) } func (k Keeper) IsOptedIn( @@ -1215,37 +1210,23 @@ func (k Keeper) IsOptedIn( providerAddr types.ProviderConsAddress, ) bool { store := ctx.KVStore(k.storeKey) - return store.Get(types.ConsumerValidatorKey(chainID, providerAddr.ToSdkConsAddr())) != nil + return store.Get(types.OptedInKey(chainID, providerAddr)) != nil } // GetAllOptedIn returns all the opted-in validators on chain `chainID` func (k Keeper) GetAllOptedIn( ctx sdk.Context, - chainID string) (consumerValidators []types.ConsumerValidator) { + chainID string) (providerConsAddresses []types.ProviderConsAddress) { store := ctx.KVStore(k.storeKey) - key := types.ChainIdWithLenKey(types.ConsumerValidatorBytePrefix, chainID) + key := types.ChainIdWithLenKey(types.OptedInBytePrefix, chainID) iterator := sdk.KVStorePrefixIterator(store, key) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { - iterator.Value() - var consumerValidator types.ConsumerValidator - if err := consumerValidator.Unmarshal(iterator.Value()); err != nil { - panic(fmt.Errorf("failed to unmarshal ConsumerValidator: %w", err)) - } - consumerValidators = append(consumerValidators, consumerValidator) + providerConsAddresses = append(providerConsAddresses, types.NewProviderConsAddress(iterator.Key()[len(key):])) } - return consumerValidators -} - -func (k Keeper) SetToBeOptedIn( - ctx sdk.Context, - chainID string, - providerAddr types.ProviderConsAddress, -) { - store := ctx.KVStore(k.storeKey) - store.Set(types.ToBeOptedInKey(chainID, providerAddr), []byte{}) + return providerConsAddresses } // SetConsumerCommissionRate sets a per-consumer chain commission rate @@ -1316,120 +1297,3 @@ func (k Keeper) DeleteConsumerCommissionRate( store := ctx.KVStore(k.storeKey) store.Delete(types.ConsumerCommissionRateKey(chainID, providerAddr)) } - -func (k Keeper) DeleteToBeOptedIn( - ctx sdk.Context, - chainID string, - providerAddr types.ProviderConsAddress, -) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.ToBeOptedInKey(chainID, providerAddr)) -} - -// DeleteAllToBeOptedIn FIXME -func (k Keeper) DeleteAllToBeOptedIn( - ctx sdk.Context, - chainID string) { - store := ctx.KVStore(k.storeKey) - key := types.ChainIdWithLenKey(types.ToBeOptedInBytePrefix, 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) - } -} - -func (k Keeper) IsToBeOptedIn( - ctx sdk.Context, - chainID string, - providerAddr types.ProviderConsAddress, -) bool { - store := ctx.KVStore(k.storeKey) - return store.Get(types.ToBeOptedInKey(chainID, providerAddr)) != nil -} - -// GetAllToBeOptedIn returns all the to-be-opted-in validators on chain `chainID` -func (k Keeper) GetAllToBeOptedIn( - ctx sdk.Context, - chainID string) (addresses []types.ProviderConsAddress) { - - store := ctx.KVStore(k.storeKey) - key := types.ChainIdWithLenKey(types.ToBeOptedInBytePrefix, chainID) - iterator := sdk.KVStorePrefixIterator(store, key) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - providerAddr := types.NewProviderConsAddress(iterator.Key()[len(key):]) - addresses = append(addresses, providerAddr) - } - - return addresses -} - -func (k Keeper) SetToBeOptedOut( - ctx sdk.Context, - chainID string, - providerAddr types.ProviderConsAddress, -) { - store := ctx.KVStore(k.storeKey) - store.Set(types.ToBeOptedOutKey(chainID, providerAddr), []byte{}) -} - -func (k Keeper) DeleteToBeOptedOut( - ctx sdk.Context, - chainID string, - providerAddr types.ProviderConsAddress, -) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.ToBeOptedOutKey(chainID, providerAddr)) -} - -// DeleteAllToBeOptedOut FIXME -func (k Keeper) DeleteAllToBeOptedOut( - ctx sdk.Context, - chainID string) { - store := ctx.KVStore(k.storeKey) - key := types.ChainIdWithLenKey(types.ToBeOptedOutBytePrefix, 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) - } -} - -func (k Keeper) IsToBeOptedOut( - ctx sdk.Context, - chainID string, - providerAddr types.ProviderConsAddress, -) bool { - store := ctx.KVStore(k.storeKey) - return store.Get(types.ToBeOptedOutKey(chainID, providerAddr)) != nil -} - -// GetAllToBeOptedOut returns all the to-be-opted-out validators on chain `chainID` -func (k Keeper) GetAllToBeOptedOut( - ctx sdk.Context, - chainID string) (addresses []types.ProviderConsAddress) { - - store := ctx.KVStore(k.storeKey) - key := types.ChainIdWithLenKey(types.ToBeOptedOutBytePrefix, chainID) - iterator := sdk.KVStorePrefixIterator(store, key) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - providerAddr := types.NewProviderConsAddress(iterator.Key()[len(key):]) - addresses = append(addresses, providerAddr) - } - - return addresses -} diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index 8525f4ed74..7cc777f287 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -666,53 +666,22 @@ func TestGetAllOptedIn(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - expectedOptedInValidators := []types.ConsumerValidator{ - { - ProviderConsAddr: []byte("providerAddr1"), - Power: 2, - ConsumerPublicKey: &tmprotocrypto.PublicKey{ - Sum: &tmprotocrypto.PublicKey_Ed25519{ - Ed25519: []byte{3}, - }, - }, - }, - { - ProviderConsAddr: []byte("providerAddr2"), - Power: 3, - ConsumerPublicKey: &tmprotocrypto.PublicKey{ - Sum: &tmprotocrypto.PublicKey_Ed25519{ - Ed25519: []byte{4}, - }, - }, - }, - { - ProviderConsAddr: []byte("providerAddr3"), - Power: 4, - ConsumerPublicKey: &tmprotocrypto.PublicKey{ - Sum: &tmprotocrypto.PublicKey_Ed25519{ - Ed25519: []byte{5}, - }, - }, - }, - } + expectedOptedInValidators := []types.ProviderConsAddress{ + types.NewProviderConsAddress([]byte("providerAddr1")), + types.NewProviderConsAddress([]byte("providerAddr2")), + types.NewProviderConsAddress([]byte("providerAddr3"))} for _, expectedOptedInValidator := range expectedOptedInValidators { - providerKeeper.SetOptedIn(ctx, "chainID", - types.ConsumerValidator{ - ProviderConsAddr: expectedOptedInValidator.ProviderConsAddr, - Power: expectedOptedInValidator.Power, - ConsumerPublicKey: expectedOptedInValidator.ConsumerPublicKey, - }) + providerKeeper.SetOptedIn(ctx, "chainID", expectedOptedInValidator) + } actualOptedInValidators := providerKeeper.GetAllOptedIn(ctx, "chainID") // sort validators first to be able to compare - sortOptedInValidators := func(optedInValidators []types.ConsumerValidator) { - sort.Slice(optedInValidators, func(i int, j int) bool { - a := optedInValidators[i] - b := optedInValidators[j] - return bytes.Compare(a.ProviderConsAddr, b.ProviderConsAddr) < 0 + sortOptedInValidators := func(addressess []types.ProviderConsAddress) { + sort.Slice(addressess, func(i int, j int) bool { + return bytes.Compare(addressess[i].ToSdkConsAddr(), addressess[j].ToSdkConsAddr()) < 0 }) } sortOptedInValidators(expectedOptedInValidators) @@ -725,150 +694,13 @@ func TestOptedIn(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - optedInValidator := types.ConsumerValidator{ProviderConsAddr: []byte("providerAddr"), - Power: 2, - ConsumerPublicKey: &tmprotocrypto.PublicKey{ - Sum: &tmprotocrypto.PublicKey_Ed25519{ - Ed25519: []byte{3}, - }, - }, - } + optedInValidator := types.NewProviderConsAddress([]byte("providerAddr")) - require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderConsAddr))) + require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", optedInValidator)) providerKeeper.SetOptedIn(ctx, "chainID", optedInValidator) - require.True(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderConsAddr))) - providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderConsAddr)) - require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(optedInValidator.ProviderConsAddr))) -} - -func TestGetAllToBeOptedIn(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - expectedAddresses := []types.ProviderConsAddress{ - types.NewProviderConsAddress([]byte("providerAddr1")), - types.NewProviderConsAddress([]byte("providerAddr2")), - types.NewProviderConsAddress([]byte("providerAddr3"))} - - for _, addr := range expectedAddresses { - providerKeeper.SetToBeOptedIn(ctx, "chainID", addr) - } - - actualAddresses := providerKeeper.GetAllToBeOptedIn(ctx, "chainID") - - // sort addresses first to be able to compare - sortAddresses := func(addresses []types.ProviderConsAddress) { - sort.Slice(addresses, func(i int, j int) bool { - a := addresses[i] - b := addresses[j] - return bytes.Compare(a.Address.Bytes(), b.Address.Bytes()) < 0 - }) - } - sortAddresses(expectedAddresses) - sortAddresses(actualAddresses) - require.Equal(t, expectedAddresses, actualAddresses) - - for _, addr := range expectedAddresses { - require.True(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", addr)) - providerKeeper.DeleteToBeOptedIn(ctx, "chainID", addr) - require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", addr)) - } -} - -func TestBeOptedIn(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - expectedAddresses := []types.ProviderConsAddress{ - types.NewProviderConsAddress([]byte("providerAddr1")), - types.NewProviderConsAddress([]byte("providerAddr2")), - types.NewProviderConsAddress([]byte("providerAddr3"))} - - for _, addr := range expectedAddresses { - providerKeeper.SetToBeOptedIn(ctx, "chainID", addr) - } - - actualAddresses := providerKeeper.GetAllToBeOptedIn(ctx, "chainID") - - // sort addresses first to be able to compare - sortAddresses := func(addresses []types.ProviderConsAddress) { - sort.Slice(addresses, func(i int, j int) bool { - a := addresses[i] - b := addresses[j] - return bytes.Compare(a.Address.Bytes(), b.Address.Bytes()) < 0 - }) - } - sortAddresses(expectedAddresses) - sortAddresses(actualAddresses) - require.Equal(t, expectedAddresses, actualAddresses) - - for _, addr := range expectedAddresses { - require.True(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", addr)) - providerKeeper.DeleteToBeOptedIn(ctx, "chainID", addr) - require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", addr)) - } -} - -// TestToBeOptedIn tests the `SetToBeOptedIn`, `IsToBeOptedIn`, and `DeleteToBeOptedIn` methods -func TestToBeOptedIn(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - providerAddr := types.NewProviderConsAddress([]byte("providerAddr1")) - - require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) - providerKeeper.SetToBeOptedIn(ctx, "chainID", providerAddr) - require.True(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) - providerKeeper.DeleteToBeOptedIn(ctx, "chainID", providerAddr) - require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) -} - -func TestGetAllToBeOptedOut(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - expectedAddresses := []types.ProviderConsAddress{ - types.NewProviderConsAddress([]byte("providerAddr1")), - types.NewProviderConsAddress([]byte("providerAddr2")), - types.NewProviderConsAddress([]byte("providerAddr3"))} - - for _, addr := range expectedAddresses { - providerKeeper.SetToBeOptedOut(ctx, "chainID", addr) - } - - actualAddresses := providerKeeper.GetAllToBeOptedOut(ctx, "chainID") - - // sort addresses first to be able to compare - sortAddresses := func(addresses []types.ProviderConsAddress) { - sort.Slice(addresses, func(i int, j int) bool { - a := addresses[i] - b := addresses[j] - return bytes.Compare(a.Address.Bytes(), b.Address.Bytes()) < 0 - }) - } - sortAddresses(expectedAddresses) - sortAddresses(actualAddresses) - require.Equal(t, expectedAddresses, actualAddresses) - - for _, addr := range expectedAddresses { - require.True(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", addr)) - providerKeeper.DeleteToBeOptedOut(ctx, "chainID", addr) - require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", addr)) - } -} - -// TestToBeOptedOut tests the `SetToBeOptedOut`, `IsToBeOptedOut`, and `DeleteToBeOptedOut` methods -func TestToBeOptedOut(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - providerAddr := types.NewProviderConsAddress([]byte("providerAddr1")) - - require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) - providerKeeper.SetToBeOptedOut(ctx, "chainID", providerAddr) - require.True(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) - providerKeeper.DeleteToBeOptedOut(ctx, "chainID", providerAddr) - require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) + require.True(t, providerKeeper.IsOptedIn(ctx, "chainID", optedInValidator)) + providerKeeper.DeleteOptedIn(ctx, "chainID", optedInValidator) + require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", optedInValidator)) } // TestToBeOptedOut tests the `SetConsumerCommissionRate`, `GetConsumerCommissionRate`, and `DeleteConsumerCommissionRate` methods diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go index 6b8fdf07f2..4fe167d511 100644 --- a/x/ccv/provider/keeper/partial_set_security.go +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -2,7 +2,6 @@ package keeper import ( errorsmod "cosmossdk.io/errors" - "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" @@ -18,13 +17,7 @@ func (k Keeper) HandleOptIn(ctx sdk.Context, chainID string, providerAddr types. "opting in to an unknown consumer chain, with id: %s", chainID) } - if k.IsToBeOptedOut(ctx, chainID, providerAddr) { - // a validator to be opted in cancels out with a validator to be opted out - k.DeleteToBeOptedOut(ctx, chainID, providerAddr) - } else if !k.IsToBeOptedIn(ctx, chainID, providerAddr) && !k.IsOptedIn(ctx, chainID, providerAddr) { - // a validator can only be set for opt in if it is not opted in and not already set for opt in - k.SetToBeOptedIn(ctx, chainID, providerAddr) - } + k.SetOptedIn(ctx, chainID, providerAddr) if consumerKey != nil { consumerTMPublicKey, err := k.ParseConsumerKey(*consumerKey) @@ -57,94 +50,53 @@ func (k Keeper) HandleOptOut(ctx sdk.Context, chainID string, providerAddr types "opting out of an unknown or not running consumer chain, with id: %s", chainID) } - if k.IsToBeOptedIn(ctx, chainID, providerAddr) { - // a validator to be opted out cancels out a validator to be opted in - k.DeleteToBeOptedIn(ctx, chainID, providerAddr) - } else if !k.IsToBeOptedOut(ctx, chainID, providerAddr) && k.IsOptedIn(ctx, chainID, providerAddr) { - // a validator can only be set for opt out if it is opted in and not already set for opt out - k.SetToBeOptedOut(ctx, chainID, providerAddr) - } - + k.DeleteOptedIn(ctx, chainID, providerAddr) return nil } -// OptInToBeOptedInValidators opts in all the to-be-opted-in validators at the end of an epoch. -// Note that validators that are not currently active can still opt in but are filtered out at a later stage -// (e.g., `ComputeNextEpochConsumerValSet`) before we send the validator set to a consumer chain. -func (k Keeper) OptInToBeOptedInValidators(ctx sdk.Context, chainID string) { - for _, providerConsAddress := range k.GetAllToBeOptedIn(ctx, chainID) { - stakingValidator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerConsAddress.ToSdkConsAddr()) - if !found { - // this should never happen but is recoverable if we do not opt in this validator - k.Logger(ctx).Error("could not get validator by consensus address", - "consensus address", providerConsAddress.ToSdkConsAddr()) - continue - } - - consumerValidator, err := k.CreateConsumerValidator(ctx, chainID, stakingValidator) - if err != nil { - k.Logger(ctx).Error("could not create consumer validator", - "validator", stakingValidator.GetOperator().String(), - "error", err) - continue - } - - // if validator already exists it gets overwritten - k.SetOptedIn(ctx, chainID, consumerValidator) - } - - k.DeleteAllToBeOptedIn(ctx, chainID) -} - -// OptInToBeOptedInValidators opts out all the to-be-opted-out validators at the end of an epoch. -func (k Keeper) OptOutToBeOptedOutValidators(ctx sdk.Context, chainID string) { - for _, providerConsAddress := range k.GetAllToBeOptedOut(ctx, chainID) { - k.DeleteOptedIn(ctx, chainID, providerConsAddress) - } - - k.DeleteAllToBeOptedOut(ctx, chainID) -} - -// OptInTopNValidators opts in to `chainID` all the active validators that have power greater or equal to `powerThreshold` -func (k Keeper) OptInTopNValidators(ctx sdk.Context, chainID string, powerThreshold int64) { - for _, val := range k.stakingKeeper.GetLastValidators(ctx) { +// 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) { + for _, val := range bondedValidators { power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()) - if power >= powerThreshold { - consumerValidator, err := k.CreateConsumerValidator(ctx, chainID, val) + if power >= minPowerToOptIn { + consAddr, err := val.GetConsAddr() if err != nil { - k.Logger(ctx).Error("could not create consumer validator", - "validator", val.GetOperator().String(), + k.Logger(ctx).Error("could not retrieve validators consensus address", + "validator", val, "error", err) continue } + // if validator already exists it gets overwritten - k.SetOptedIn(ctx, chainID, consumerValidator) + k.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(consAddr)) } else { // validators that do not belong to the top N validators but were opted in, remain opted in } } } -// ComputePowerThreshold returns the Nth percentile based on the given `threshold` -func (k Keeper) ComputePowerThreshold(ctx sdk.Context, topN math.LegacyDec) int64 { +// ComputeMinPowerToOptIn returns the minimum power needed for a validator (from the bonded validators) +// to belong to the `topN` validators +func (k Keeper) ComputeMinPowerToOptIn(ctx sdk.Context, bondedValidators []stakingtypes.Validator, topN uint32) int64 { totalPower := sdk.ZeroDec() var powers []int64 - k.stakingKeeper.IterateLastValidatorPowers(ctx, func(addr sdk.ValAddress, power int64) (stop bool) { + for _, val := range bondedValidators { + power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator()) powers = append(powers, power) totalPower = totalPower.Add(sdk.NewDecFromInt(sdk.NewInt(power))) - return false - }) + } // sort by powers descending sort.Slice(powers, func(i, j int) bool { return powers[i] > powers[j] }) + topNThreshold := sdk.NewDecFromInt(sdk.NewInt(int64(topN))).QuoInt64(int64(100)) powerSum := sdk.ZeroDec() for _, power := range powers { powerSum = powerSum.Add(sdk.NewDecFromInt(sdk.NewInt(power))) - if powerSum.Quo(totalPower).GTE(topN) { + if powerSum.Quo(totalPower).GTE(topNThreshold) { return power } } diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go index 82a987018b..f0b3bf6a53 100644 --- a/x/ccv/provider/keeper/partial_set_security_test.go +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -1,7 +1,8 @@ package keeper_test import ( - "github.com/cometbft/cometbft/proto/tendermint/crypto" + "bytes" + "sort" "testing" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -24,24 +25,10 @@ func TestHandleOptIn(t *testing.T) { // trying to opt in to a non-proposed and non-registered chain returns an error require.Error(t, providerKeeper.HandleOptIn(ctx, "unknownChainID", providerAddr, nil)) - // if validator (`providerAddr`) is to be opted out, then we cancel that the validator is about - // to be opted out and do not consider the validator to opt in - providerKeeper.SetToBeOptedOut(ctx, "chainID", providerAddr) providerKeeper.SetProposedConsumerChain(ctx, "chainID", 1) - require.True(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) + require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", providerAddr)) providerKeeper.HandleOptIn(ctx, "chainID", providerAddr, nil) - require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) - require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) - - // if validator (`providerAddr`) is already opted in, then the validator cannot be opted in - providerKeeper.SetOptedIn(ctx, "chainID", - types.ConsumerValidator{ProviderConsAddr: providerAddr.ToSdkConsAddr(), Power: 1, ConsumerPublicKey: &crypto.PublicKey{ - Sum: &crypto.PublicKey_Ed25519{ - Ed25519: []byte{1}, - }, - }}) - providerKeeper.HandleOptIn(ctx, "chainID", providerAddr, nil) - require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) + require.True(t, providerKeeper.IsOptedIn(ctx, "chainID", providerAddr)) } func TestHandleOptInWithConsumerKey(t *testing.T) { @@ -100,21 +87,14 @@ func TestHandleOptOut(t *testing.T) { // trying to opt out from a not running chain returns an error require.Error(t, providerKeeper.HandleOptOut(ctx, "unknownChainID", providerAddr)) - // if validator (`providerAddr`) is to be opted in, then we cancel that the validator is about - // to be opted out and do not consider the validator to opt out - providerKeeper.SetToBeOptedIn(ctx, "chainID", providerAddr) + // set a consumer client so that the chain is considered running providerKeeper.SetConsumerClientId(ctx, "chainID", "clientID") - require.True(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) - err := providerKeeper.HandleOptOut(ctx, "chainID", providerAddr) - require.NoError(t, err) - require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) - require.False(t, providerKeeper.IsToBeOptedIn(ctx, "chainID", providerAddr)) - // if validator (`providerAddr`) is not opted in, then the validator cannot be opted out - providerKeeper.DeleteOptedIn(ctx, "chainID", providerAddr) - err = providerKeeper.HandleOptOut(ctx, "chainID", providerAddr) - require.NoError(t, err) - require.False(t, providerKeeper.IsToBeOptedOut(ctx, "chainID", providerAddr)) + // if validator (`providerAddr`) is already opted in, then an opt-out would remove this validator + providerKeeper.SetOptedIn(ctx, "chainID", providerAddr) + require.True(t, providerKeeper.IsOptedIn(ctx, "chainID", providerAddr)) + providerKeeper.HandleOptOut(ctx, "chainID", providerAddr) + require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", providerAddr)) } func TestHandleSetConsumerCommissionRate(t *testing.T) { @@ -141,3 +121,94 @@ func TestHandleSetConsumerCommissionRate(t *testing.T) { require.Equal(t, sdk.OneDec(), cr) require.True(t, found) } + +func TestOptInTopNValidators(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // create 3 validators with powers 1, 2, and 3 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() + + // opts in all validators with power >= 1 + providerKeeper.OptInTopNValidators(ctx, "chainID", []stakingtypes.Validator{valA, valB, valC}, 1) + expectedOptedInValidators := []types.ProviderConsAddress{ + types.NewProviderConsAddress(valAConsAddr), + types.NewProviderConsAddress(valBConsAddr), + types.NewProviderConsAddress(valCConsAddr)} + actualOptedInValidators := providerKeeper.GetAllOptedIn(ctx, "chainID") + + // sort validators first to be able to compare + sortUpdates := func(addresses []types.ProviderConsAddress) { + sort.Slice(addresses, func(i, j int) bool { + return bytes.Compare(addresses[i].ToSdkConsAddr(), addresses[j].ToSdkConsAddr()) < 0 + }) + } + + sortUpdates(expectedOptedInValidators) + sortUpdates(actualOptedInValidators) + require.Equal(t, expectedOptedInValidators, actualOptedInValidators) + + // reset state for the upcoming checks + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valAConsAddr)) + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valBConsAddr)) + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valCConsAddr)) + + // opts 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}, 2) + expectedOptedInValidators = []types.ProviderConsAddress{ + types.NewProviderConsAddress(valBConsAddr), + types.NewProviderConsAddress(valCConsAddr)} + actualOptedInValidators = providerKeeper.GetAllOptedIn(ctx, "chainID") + + // sort validators first to be able to compare + sortUpdates(expectedOptedInValidators) + sortUpdates(actualOptedInValidators) + require.Equal(t, expectedOptedInValidators, actualOptedInValidators) + + // reset state for the upcoming checks + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valAConsAddr)) + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valBConsAddr)) + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valCConsAddr)) + + // opts 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}, 4) + require.Empty(t, providerKeeper.GetAllOptedIn(ctx, "chainID")) +} + +func TestComputeMinPowerToOptIn(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // create 5 validators with powers 1, 3, 5, 6, 10 (not in that order) with total power of 25 (= 1 + 3 + 5 + 6 + 10) + // such that: + // validator power => cumulative share + // 10 => 40% + // 6 => 64% + // 5 => 84% + // 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), + } + + require.Equal(t, int64(1), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 100)) + require.Equal(t, int64(1), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 97)) + require.Equal(t, int64(3), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 96)) + require.Equal(t, int64(3), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 85)) + require.Equal(t, int64(5), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 84)) + require.Equal(t, int64(5), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 65)) + require.Equal(t, int64(6), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 64)) + require.Equal(t, int64(6), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 41)) + require.Equal(t, int64(10), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 40)) + require.Equal(t, int64(10), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 1)) +} diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index c89109b7be..31a2c02741 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -291,8 +291,6 @@ func (k Keeper) MakeConsumerGenesis( var nextValidators []types.ConsumerValidator - k.OptInToBeOptedInValidators(ctx, chainID) - considerOnlyOptIn := func(validator stakingtypes.Validator) bool { consAddr, err := validator.GetConsAddr() if err != nil { @@ -303,8 +301,8 @@ func (k Keeper) MakeConsumerGenesis( if topN, found := k.GetTopN(ctx, chainID); found { // in a Top-N chain, we automatically opt in all validators that belong to the top N - threshold := sdk.NewDec(int64(topN)).QuoInt64(100) - k.OptInTopNValidators(ctx, chainID, threshold) + minPower := k.ComputeMinPowerToOptIn(ctx, bondedValidators, topN) + k.OptInTopNValidators(ctx, chainID, bondedValidators, minPower) } nextValidators = k.ComputeNextEpochConsumerValSet(ctx, chainID, bondedValidators, considerOnlyOptIn) diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index f1403ca21d..bbefb035b5 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -221,10 +221,6 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { bondedValidators := k.stakingKeeper.GetLastValidators(ctx) for _, chain := range k.GetAllConsumerChains(ctx) { - // FIXME: add comment that this should take place before `OptInTopNValidators` - k.OptOutToBeOptedOutValidators(ctx, chain.ChainId) - k.OptInToBeOptedInValidators(ctx, chain.ChainId) - currentValidators := k.GetConsumerValSet(ctx, chain.ChainId) var nextValidators []providertypes.ConsumerValidator @@ -238,8 +234,8 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { if topN, found := k.GetTopN(ctx, chain.ChainId); found { // in a Top-N chain, we automatically opt in all validators that belong to the top N - threshold := sdk.NewDec(int64(topN)).QuoInt64(100) - k.OptInTopNValidators(ctx, chain.ChainId, threshold) + minPower := k.ComputeMinPowerToOptIn(ctx, bondedValidators, topN) + k.OptInTopNValidators(ctx, chain.ChainId, bondedValidators, minPower) } nextValidators = k.ComputeNextEpochConsumerValSet(ctx, chain.ChainId, bondedValidators, considerOnlyOptIn) diff --git a/x/ccv/provider/keeper/validator_set_update_test.go b/x/ccv/provider/keeper/validator_set_update_test.go index 7380d53f8d..968d4e0ced 100644 --- a/x/ccv/provider/keeper/validator_set_update_test.go +++ b/x/ccv/provider/keeper/validator_set_update_test.go @@ -397,8 +397,8 @@ func TestComputeNextEpochConsumerValSetConsiderOnlyOptIn(t *testing.T) { expectedValidators = append(expectedValidators, expectedValBConsumerValidator) // opt in validators A and B with 0 power and no consumer public keys - providerKeeper.SetOptedIn(ctx, chainID, types.ConsumerValidator{ProviderConsAddr: valAConsAddr, Power: 0, ConsumerPublicKey: nil}) - providerKeeper.SetOptedIn(ctx, chainID, types.ConsumerValidator{ProviderConsAddr: valBConsAddr, Power: 0, ConsumerPublicKey: nil}) + providerKeeper.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(valAConsAddr)) + providerKeeper.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(valBConsAddr)) // the expected actual validators are the opted-in validators but with the correct power and consumer public keys set bondedValidators := []stakingtypes.Validator{valA, valB} diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index edc5c43c86..1e2516ba07 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -147,21 +147,18 @@ const ( // ProposedConsumerChainByteKey is the byte prefix storing the consumer chainId in consumerAddition gov proposal submitted before voting finishes ProposedConsumerChainByteKey - // ConsumerValidatorBytePrefix is the byte prefix used when storing for each consumer chain all the consumer validators in this epoch + // OptedInBytePrefix is the byte prefix used when storing for each consumer chain the validators that + // are opted in but not necessarily the ones that are validating. + OptedInBytePrefix + + // ConsumerValidatorBytePrefix is the byte prefix used when storing for each consumer chain all the consumer + // validators in this epoch that are validating the consumer chain ConsumerValidatorBytePrefix // TopNBytePrefix is the byte prefix storing the mapping from a consumer chain to the N value of this chain, // that corresponds to the N% of the top validators that have to validate this consumer chain TopNBytePrefix - // ToBeOptedInBytePrefix is the byte prefix used when storing for each consumer chain the validators that - // are about to be opted in - ToBeOptedInBytePrefix - - // ToBeOptedOutBytePrefix is the byte prefix used when storing for each consumer chain the validators that - // are about to be opted out - ToBeOptedOutBytePrefix - // ConsumerRewardsAllocationBytePrefix is the byte prefix used when storing for each consumer the rewards // it allocated to the consumer rewards pool ConsumerRewardsAllocationBytePrefix @@ -547,15 +544,9 @@ func TopNKey(chainID string) []byte { return ChainIdWithLenKey(TopNBytePrefix, chainID) } -// ToBeOptedInKey returns the key of consumer chain `chainID` and validator with `providerAddr` -func ToBeOptedInKey(chainID string, providerAddr ProviderConsAddress) []byte { - prefix := ChainIdWithLenKey(ToBeOptedInBytePrefix, chainID) - return append(prefix, providerAddr.ToSdkConsAddr().Bytes()...) -} - -// ToBeOptedOutKey returns the key of consumer chain `chainID` and validator with `providerAddr` -func ToBeOptedOutKey(chainID string, providerAddr ProviderConsAddress) []byte { - prefix := ChainIdWithLenKey(ToBeOptedOutBytePrefix, chainID) +// OptedInKey returns the key of consumer chain `chainID` and validator with `providerAddr` +func OptedInKey(chainID string, providerAddr ProviderConsAddress) []byte { + prefix := ChainIdWithLenKey(OptedInBytePrefix, chainID) return append(prefix, providerAddr.ToSdkConsAddr().Bytes()...) } diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index d7cfd10fd4..d37482e50e 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -56,10 +56,9 @@ func getAllKeyPrefixes() []byte { providertypes.VSCMaturedHandledThisBlockBytePrefix, providertypes.EquivocationEvidenceMinHeightBytePrefix, providertypes.ProposedConsumerChainByteKey, + providertypes.OptedInBytePrefix, providertypes.ConsumerValidatorBytePrefix, providertypes.TopNBytePrefix, - providertypes.ToBeOptedInBytePrefix, - providertypes.ToBeOptedOutBytePrefix, providertypes.ConsumerRewardsAllocationBytePrefix, providertypes.ConsumerCommissionRatePrefix, } From 03af237c800211642e81759b5f2e23bdcbad1113 Mon Sep 17 00:00:00 2001 From: insumity Date: Tue, 19 Mar 2024 15:58:53 +0100 Subject: [PATCH 05/14] fix distribution test --- x/ccv/provider/keeper/distribution_test.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/x/ccv/provider/keeper/distribution_test.go b/x/ccv/provider/keeper/distribution_test.go index bd83eefcc9..1e37b1112e 100644 --- a/x/ccv/provider/keeper/distribution_test.go +++ b/x/ccv/provider/keeper/distribution_test.go @@ -44,16 +44,9 @@ func TestComputeConsumerTotalVotingPower(t *testing.T) { keeper.SetConsumerValidator( ctx, chainID, - types.NewProviderConsAddress(val.Address.Bytes()), - ) - - validatorsVotes = append( - validatorsVotes, - abci.VoteInfo{ - Validator: abci.Validator{ - Address: val.Address, - Power: val.VotingPower, - }, + types.ConsumerValidator{ + ProviderConsAddr: val.Address, + Power: val.VotingPower, }, ) From 07429fa79b0b378b102a6cf89042289196f8d19a Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 20 Mar 2024 10:05:18 +0100 Subject: [PATCH 06/14] Update x/ccv/provider/keeper/hooks.go Co-authored-by: Simon Noetzlin --- x/ccv/provider/keeper/hooks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/hooks.go b/x/ccv/provider/keeper/hooks.go index a793654a19..d9025a1f98 100644 --- a/x/ccv/provider/keeper/hooks.go +++ b/x/ccv/provider/keeper/hooks.go @@ -225,7 +225,7 @@ func (h Hooks) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr s return } - // in the validator is already opted in, the `SetOptedIn` is a no-op + // If the validator is already opted in, the `SetOptedIn` is a no-op h.k.SetOptedIn(ctx, chainID, providertypes.NewProviderConsAddress(consAddr)) } else { // If vote is not a YES vote with 100% weight, we opt out the validator. From 0ab0b32b795f1ba340175e597cd102ebf0ba7a83 Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 20 Mar 2024 10:12:49 +0100 Subject: [PATCH 07/14] took into Simon's comments --- testutil/ibc_testing/generic_setup.go | 8 ++++---- x/ccv/provider/keeper/proposal.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/testutil/ibc_testing/generic_setup.go b/testutil/ibc_testing/generic_setup.go index 7a87e05ab1..1c12485fc8 100644 --- a/testutil/ibc_testing/generic_setup.go +++ b/testutil/ibc_testing/generic_setup.go @@ -141,10 +141,6 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp]( prop.ChainId = chainID prop.Top_N = consumerTopNParams[index] // isn't used in CreateConsumerClient - // set the consumer TopN here since the test suite setup only used the consumer addition prop - // to create the consumer genesis, see BeginBlockInit in /x/ccv/provider/keeper/proposal.go. - providerKeeper.SetTopN(providerChain.GetContext(), chainID, prop.Top_N) - // 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} @@ -154,6 +150,10 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp]( ) s.Require().NoError(err) + // set the consumer TopN here since the test suite setup only used the consumer addition prop + // to create the consumer genesis, see BeginBlockInit in /x/ccv/provider/keeper/proposal.go. + providerKeeper.SetTopN(providerChain.GetContext(), chainID, prop.Top_N) + // commit the state on the provider chain coordinator.CommitBlock(providerChain) diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 31a2c02741..42e3f889a9 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -299,9 +299,9 @@ func (k Keeper) MakeConsumerGenesis( return k.IsOptedIn(ctx, chainID, types.NewProviderConsAddress(consAddr)) } - if topN, found := k.GetTopN(ctx, chainID); found { + if prop.Top_N > 0 { // in a Top-N chain, we automatically opt in all validators that belong to the top N - minPower := k.ComputeMinPowerToOptIn(ctx, bondedValidators, topN) + minPower := k.ComputeMinPowerToOptIn(ctx, bondedValidators, prop.Top_N) k.OptInTopNValidators(ctx, chainID, bondedValidators, minPower) } From 1a0fa9e514b724e7e8b6dd1f811def91f614bf48 Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 20 Mar 2024 11:29:13 +0100 Subject: [PATCH 08/14] took into rest of the comments --- x/ccv/provider/keeper/partial_set_security.go | 15 ++++-- .../keeper/partial_set_security_test.go | 48 +++++++++++++++---- x/ccv/provider/keeper/proposal.go | 15 ++---- x/ccv/provider/keeper/relay.go | 14 ++---- x/ccv/provider/keeper/relay_test.go | 19 +------- .../keeper/validator_set_update_test.go | 22 +++++---- 6 files changed, 73 insertions(+), 60 deletions(-) diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go index 01b61bc811..676cea8a81 100644 --- a/x/ccv/provider/keeper/partial_set_security.go +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -104,11 +104,20 @@ func (k Keeper) ComputeMinPowerToOptIn(ctx sdk.Context, bondedValidators []staki } // We should never reach this point because the topN can be up to 1.0 (100%) and in the above `for` loop we - // perform an equal comparison as well (`GTE`). In any case, we do not have to panic here because we can return - // the smallest possible power. + // perform an equal comparison as well (`GTE`). In any case, we do not have to panic here because we can return 0 + // as the smallest possible power. k.Logger(ctx).Error("should never reach this point", "topN", topN, "totalPower", totalPower, "powerSum", powerSum) - return powers[len(powers)-1] + return 0 +} + +// ShouldConsiderOnlyOptIn returns true if `validator` is opted in, in `chainID. +func (k Keeper) ShouldConsiderOnlyOptIn(ctx sdk.Context, chainID string, validator stakingtypes.Validator) bool { + consAddr, err := validator.GetConsAddr() + if err != nil { + return false + } + return k.IsOptedIn(ctx, chainID, types.NewProviderConsAddress(consAddr)) } diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go index ed1d311ee7..4598bc56d7 100644 --- a/x/ccv/provider/keeper/partial_set_security_test.go +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -128,20 +128,23 @@ func TestOptInTopNValidators(t *testing.T) { providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - // create 3 validators with powers 1, 2, and 3 respectively + // 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() - // opts in all validators with power >= 1 - providerKeeper.OptInTopNValidators(ctx, "chainID", []stakingtypes.Validator{valA, valB, valC}, 1) + // Start Test 1: opt in all validators with power >= 0 + providerKeeper.OptInTopNValidators(ctx, "chainID", []stakingtypes.Validator{valA, valB, valC, valD}, 0) expectedOptedInValidators := []types.ProviderConsAddress{ types.NewProviderConsAddress(valAConsAddr), types.NewProviderConsAddress(valBConsAddr), - types.NewProviderConsAddress(valCConsAddr)} + types.NewProviderConsAddress(valCConsAddr), + types.NewProviderConsAddress(valDConsAddr)} actualOptedInValidators := providerKeeper.GetAllOptedIn(ctx, "chainID") // sort validators first to be able to compare @@ -159,9 +162,23 @@ func TestOptInTopNValidators(t *testing.T) { providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valAConsAddr)) providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valBConsAddr)) providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valCConsAddr)) + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valDConsAddr)) + + // 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) + actualOptedInValidators = providerKeeper.GetAllOptedIn(ctx, "chainID") + sortUpdates(actualOptedInValidators) + require.Equal(t, expectedOptedInValidators, actualOptedInValidators) + + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valAConsAddr)) + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valBConsAddr)) + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valCConsAddr)) + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valDConsAddr)) - // opts 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}, 2) + // 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) expectedOptedInValidators = []types.ProviderConsAddress{ types.NewProviderConsAddress(valBConsAddr), types.NewProviderConsAddress(valCConsAddr)} @@ -176,9 +193,10 @@ func TestOptInTopNValidators(t *testing.T) { providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valAConsAddr)) providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valBConsAddr)) providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valCConsAddr)) + providerKeeper.DeleteOptedIn(ctx, "chainID", types.NewProviderConsAddress(valDConsAddr)) - // opts 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}, 4) + // 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) require.Empty(t, providerKeeper.GetAllOptedIn(ctx, "chainID")) } @@ -214,3 +232,17 @@ func TestComputeMinPowerToOptIn(t *testing.T) { require.Equal(t, int64(10), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 40)) require.Equal(t, int64(10), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 1)) } + +// TestShouldConsiderOnlyOptIn returns true if `validator` is opted in, in `chainID. +func TestShouldConsiderOnlyOptIn(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + validator := createStakingValidator(ctx, mocks, 0, 1) + consAddr, _ := validator.GetConsAddr() + + require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(consAddr))) + providerKeeper.SetOptedIn(ctx, "chainID", types.NewProviderConsAddress(consAddr)) + require.True(t, providerKeeper.IsOptedIn(ctx, "chainID", types.NewProviderConsAddress(consAddr))) + +} diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 42e3f889a9..bab1690f8b 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -289,23 +289,16 @@ func (k Keeper) MakeConsumerGenesis( bondedValidators = append(bondedValidators, val) } - var nextValidators []types.ConsumerValidator - - considerOnlyOptIn := func(validator stakingtypes.Validator) bool { - consAddr, err := validator.GetConsAddr() - if err != nil { - return false - } - return k.IsOptedIn(ctx, chainID, types.NewProviderConsAddress(consAddr)) - } - if prop.Top_N > 0 { // in a Top-N chain, we automatically opt in all validators that belong to the top N minPower := k.ComputeMinPowerToOptIn(ctx, bondedValidators, prop.Top_N) k.OptInTopNValidators(ctx, chainID, bondedValidators, minPower) } - nextValidators = k.ComputeNextEpochConsumerValSet(ctx, chainID, bondedValidators, considerOnlyOptIn) + nextValidators := k.ComputeNextEpochConsumerValSet(ctx, chainID, bondedValidators, + func(validator stakingtypes.Validator) bool { + return k.ShouldConsiderOnlyOptIn(ctx, chainID, validator) + }) k.SetConsumerValSet(ctx, chainID, nextValidators) // get the initial updates with the latest set consumer public keys diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index bbefb035b5..9728d7f59f 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -222,15 +222,6 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { for _, chain := range k.GetAllConsumerChains(ctx) { currentValidators := k.GetConsumerValSet(ctx, chain.ChainId) - var nextValidators []providertypes.ConsumerValidator - - considerOnlyOptIn := func(validator stakingtypes.Validator) bool { - consAddr, err := validator.GetConsAddr() - if err != nil { - return false - } - return k.IsOptedIn(ctx, chain.ChainId, providertypes.NewProviderConsAddress(consAddr)) - } if topN, found := k.GetTopN(ctx, chain.ChainId); found { // in a Top-N chain, we automatically opt in all validators that belong to the top N @@ -238,7 +229,10 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { k.OptInTopNValidators(ctx, chain.ChainId, bondedValidators, minPower) } - nextValidators = k.ComputeNextEpochConsumerValSet(ctx, chain.ChainId, bondedValidators, considerOnlyOptIn) + nextValidators := k.ComputeNextEpochConsumerValSet(ctx, chain.ChainId, bondedValidators, + func(validator stakingtypes.Validator) bool { + return k.ShouldConsiderOnlyOptIn(ctx, chain.ChainId, validator) + }) valUpdates := DiffValidators(currentValidators, nextValidators) k.SetConsumerValSet(ctx, chain.ChainId, nextValidators) diff --git a/x/ccv/provider/keeper/relay_test.go b/x/ccv/provider/keeper/relay_test.go index c235e917f2..a3c88a66ab 100644 --- a/x/ccv/provider/keeper/relay_test.go +++ b/x/ccv/provider/keeper/relay_test.go @@ -671,31 +671,14 @@ func TestEndBlockVSU(t *testing.T) { // create 4 sample lastValidators var lastValidators []stakingtypes.Validator var valAddresses []sdk.ValAddress - var powers []int64 for i := 0; i < 4; i++ { validator := crypto.NewCryptoIdentityFromIntSeed(i).SDKStakingValidator() lastValidators = append(lastValidators, validator) valAddresses = append(valAddresses, validator.GetOperator()) - powers = append(powers, int64(i+1)) + mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), validator.GetOperator()).Return(int64(i + 1)).AnyTimes() } - mocks.MockStakingKeeper.EXPECT().IterateLastValidatorPowers(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ sdk.Context, cb func(addr sdk.ValAddress, power int64) (stop bool)) { - for i, addr := range valAddresses { - if cb(addr, powers[i]) { - break - } - } - }, - ).AnyTimes() mocks.MockStakingKeeper.EXPECT().GetLastValidators(gomock.Any()).Return(lastValidators).AnyTimes() - mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), gomock.Any()).Return(int64(2)).AnyTimes() - - for _, val := range lastValidators { - consAddr, _ := val.GetConsAddr() - // FIXME: probably `consAddr` is not needed - mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(gomock.Any(), consAddr).Return(val, true).AnyTimes() - } // set a sample client for a consumer chain so that `GetAllConsumerChains` in `QueueVSCPackets` iterates at least once providerKeeper.SetConsumerClientId(ctx, chainID, "clientID") diff --git a/x/ccv/provider/keeper/validator_set_update_test.go b/x/ccv/provider/keeper/validator_set_update_test.go index b549bb8b45..1de54fbbde 100644 --- a/x/ccv/provider/keeper/validator_set_update_test.go +++ b/x/ccv/provider/keeper/validator_set_update_test.go @@ -364,14 +364,10 @@ func TestComputeNextEpochConsumerValSetConsiderOnlyOptIn(t *testing.T) { chainID := "chainID" // no consumer validators returned if we have no opted-in validators - considerOnlyOptIn := func(validator stakingtypes.Validator) bool { - consAddr, err := validator.GetConsAddr() - if err != nil { - return false - } - return providerKeeper.IsOptedIn(ctx, chainID, types.NewProviderConsAddress(consAddr)) - } - require.Empty(t, providerKeeper.ComputeNextEpochConsumerValSet(ctx, chainID, []stakingtypes.Validator{}, considerOnlyOptIn)) + require.Empty(t, providerKeeper.ComputeNextEpochConsumerValSet(ctx, chainID, []stakingtypes.Validator{}, + func(validator stakingtypes.Validator) bool { + return providerKeeper.ShouldConsiderOnlyOptIn(ctx, chainID, validator) + })) var expectedValidators []types.ConsumerValidator @@ -405,7 +401,10 @@ func TestComputeNextEpochConsumerValSetConsiderOnlyOptIn(t *testing.T) { // the expected actual validators are the opted-in validators but with the correct power and consumer public keys set bondedValidators := []stakingtypes.Validator{valA, valB} - actualValidators := providerKeeper.ComputeNextEpochConsumerValSet(ctx, "chainID", bondedValidators, considerOnlyOptIn) + actualValidators := providerKeeper.ComputeNextEpochConsumerValSet(ctx, "chainID", bondedValidators, + func(validator stakingtypes.Validator) bool { + return providerKeeper.ShouldConsiderOnlyOptIn(ctx, "chainID", validator) + }) // sort validators first to be able to compare sortValidators := func(validators []types.ConsumerValidator) { @@ -421,7 +420,10 @@ func TestComputeNextEpochConsumerValSetConsiderOnlyOptIn(t *testing.T) { // create a staking validator C that is not opted in, hence `expectedValidators` remains the same valC := createStakingValidator(ctx, mocks, 3, 3) bondedValidators = []stakingtypes.Validator{valA, valB, valC} - actualValidators = providerKeeper.ComputeNextEpochConsumerValSet(ctx, "chainID", bondedValidators, considerOnlyOptIn) + actualValidators = providerKeeper.ComputeNextEpochConsumerValSet(ctx, "chainID", bondedValidators, + func(validator stakingtypes.Validator) bool { + return providerKeeper.ShouldConsiderOnlyOptIn(ctx, "chainID", validator) + }) sortValidators(actualValidators) sortValidators(expectedValidators) From 56ca38d7563b14122a42ad07df8e5ca040b39e1c Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 20 Mar 2024 11:30:49 +0100 Subject: [PATCH 09/14] nit change --- testutil/ibc_testing/generic_setup.go | 1 - 1 file changed, 1 deletion(-) diff --git a/testutil/ibc_testing/generic_setup.go b/testutil/ibc_testing/generic_setup.go index 1c12485fc8..da00d76b85 100644 --- a/testutil/ibc_testing/generic_setup.go +++ b/testutil/ibc_testing/generic_setup.go @@ -140,7 +140,6 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp]( prop := testkeeper.GetTestConsumerAdditionProp() prop.ChainId = chainID prop.Top_N = consumerTopNParams[index] // isn't used in CreateConsumerClient - // NOTE: the initial height passed to CreateConsumerClient // must be the height on the consumer when InitGenesis is called prop.InitialHeight = clienttypes.Height{RevisionNumber: 0, RevisionHeight: 3} From c614fd2a4bde1f32d1ddedd873b1758a0d5d85f5 Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 20 Mar 2024 12:25:03 +0100 Subject: [PATCH 10/14] return an error if validator cannot opt out from a Top N chain --- x/ccv/provider/keeper/msg_server.go | 30 ++++++++--- x/ccv/provider/keeper/partial_set_security.go | 20 +++++++- .../keeper/partial_set_security_test.go | 50 +++++++++++++++++++ x/ccv/provider/types/errors.go | 1 + 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index a595b5fbc2..7bbf270ef9 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -140,19 +140,27 @@ func (k msgServer) SubmitConsumerDoubleVoting(goCtx context.Context, msg *types. func (k msgServer) OptIn(goCtx context.Context, msg *types.MsgOptIn) (*types.MsgOptInResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - valAddress, err := sdk.ConsAddressFromBech32(msg.ProviderAddr) + valAddress, err := sdk.ValAddressFromBech32(msg.ProviderAddr) if err != nil { return nil, err } - providerAddr := types.NewProviderConsAddress(valAddress) + + // validator must already be registered + validator, found := k.stakingKeeper.GetValidator(ctx, valAddress) + if !found { + return nil, stakingtypes.ErrNoValidatorFound + } + + consAddrTmp, err := validator.GetConsAddr() if err != nil { return nil, err } + providerConsAddr := types.NewProviderConsAddress(consAddrTmp) if msg.ConsumerKey != "" { - err = k.Keeper.HandleOptIn(ctx, msg.ChainId, providerAddr, &msg.ConsumerKey) + err = k.Keeper.HandleOptIn(ctx, msg.ChainId, providerConsAddr, &msg.ConsumerKey) } else { - err = k.Keeper.HandleOptIn(ctx, msg.ChainId, providerAddr, nil) + err = k.Keeper.HandleOptIn(ctx, msg.ChainId, providerConsAddr, nil) } if err != nil { @@ -173,16 +181,24 @@ func (k msgServer) OptIn(goCtx context.Context, msg *types.MsgOptIn) (*types.Msg func (k msgServer) OptOut(goCtx context.Context, msg *types.MsgOptOut) (*types.MsgOptOutResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - valAddress, err := sdk.ConsAddressFromBech32(msg.ProviderAddr) + valAddress, err := sdk.ValAddressFromBech32(msg.ProviderAddr) if err != nil { return nil, err } - providerAddr := types.NewProviderConsAddress(valAddress) + + // validator must already be registered + validator, found := k.stakingKeeper.GetValidator(ctx, valAddress) + if !found { + return nil, stakingtypes.ErrNoValidatorFound + } + + consAddrTmp, err := validator.GetConsAddr() if err != nil { return nil, err } + providerConsAddr := types.NewProviderConsAddress(consAddrTmp) - err = k.Keeper.HandleOptOut(ctx, msg.ChainId, providerAddr) + err = k.Keeper.HandleOptOut(ctx, msg.ChainId, providerConsAddr) if err != nil { return nil, err } diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go index 676cea8a81..754a782f48 100644 --- a/x/ccv/provider/keeper/partial_set_security.go +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -2,7 +2,6 @@ package keeper import ( errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -52,6 +51,25 @@ func (k Keeper) HandleOptOut(ctx sdk.Context, chainID string, providerAddr types "opting out of an unknown or not running consumer chain, with id: %s", chainID) } + if topN, found := k.GetTopN(ctx, chainID); found { + // a validator cannot opt out from a Top N chain if the validator is in the Top N validators + validator, validatorFound := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) + if !validatorFound { + return errorsmod.Wrapf( + stakingtypes.ErrNoValidatorFound, + "validator with consensus address %s could not be found", providerAddr.ToSdkConsAddr()) + } + power := k.stakingKeeper.GetLastValidatorPower(ctx, validator.GetOperator()) + minPowerToOptIn := k.ComputeMinPowerToOptIn(ctx, k.stakingKeeper.GetLastValidators(ctx), topN) + + if power >= minPowerToOptIn { + return errorsmod.Wrapf( + types.ErrCannotOptOutFromTopN, + "validator with power (%d) cannot opt out from Top N chain because all validators"+ + "with at least %d power have to validate", power, minPowerToOptIn) + } + } + k.DeleteOptedIn(ctx, chainID, providerAddr) return nil } diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go index 4598bc56d7..f17bd71a50 100644 --- a/x/ccv/provider/keeper/partial_set_security_test.go +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -99,6 +99,56 @@ func TestHandleOptOut(t *testing.T) { require.False(t, providerKeeper.IsOptedIn(ctx, "chainID", providerAddr)) } +func TestHandleOptOutFromTopNChain(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + chainID := "chainID" + + // set a consumer client so that the chain is considered running + providerKeeper.SetConsumerClientId(ctx, chainID, "clientID") + + // 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() + + // opt in all validators + providerKeeper.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(valAConsAddr)) + providerKeeper.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(valBConsAddr)) + providerKeeper.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(valCConsAddr)) + providerKeeper.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(valDConsAddr)) + + // validators A and B can opt out because they belong the bottom 30% of validators + require.NoError(t, providerKeeper.HandleOptOut(ctx, chainID, types.NewProviderConsAddress(valAConsAddr))) + require.NoError(t, providerKeeper.HandleOptOut(ctx, chainID, types.NewProviderConsAddress(valBConsAddr))) + + // validators C and D cannot opt out because C has 30% of the voting power and D has 40% of the voting power + // and hence both are needed to keep validating a Top 50 chain + require.Error(t, providerKeeper.HandleOptOut(ctx, chainID, types.NewProviderConsAddress(valCConsAddr))) + 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) + notFoundValidatorConsAddr, _ := notFoundValidator.GetConsAddr() + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, notFoundValidatorConsAddr). + Return(stakingtypes.Validator{}, false) + require.Error(t, providerKeeper.HandleOptOut(ctx, chainID, types.NewProviderConsAddress(notFoundValidatorConsAddr))) +} + func TestHandleSetConsumerCommissionRate(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() diff --git a/x/ccv/provider/types/errors.go b/x/ccv/provider/types/errors.go index 271ea90329..1e7dd7dd3d 100644 --- a/x/ccv/provider/types/errors.go +++ b/x/ccv/provider/types/errors.go @@ -25,4 +25,5 @@ var ( ErrDuplicateConsumerChain = errorsmod.Register(ModuleName, 17, "consumer chain already exists") ErrConsumerChainNotFound = errorsmod.Register(ModuleName, 18, "consumer chain not found") ErrInvalidConsumerCommissionRate = errorsmod.Register(ModuleName, 19, "consumer commission rate is invalid") + ErrCannotOptOutFromTopN = errorsmod.Register(ModuleName, 20, "cannot opt out from a Top N chain") ) From 6503f9bbee9982852b8807f0a438e228ee5bfc87 Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 20 Mar 2024 12:29:00 +0100 Subject: [PATCH 11/14] removed automatic opt-in for validators that vote Yes on proposals --- testutil/keeper/mocks.go | 29 ---------- x/ccv/provider/keeper/hooks.go | 57 ------------------ x/ccv/provider/keeper/hooks_test.go | 89 ----------------------------- x/ccv/types/expected_keepers.go | 2 - 4 files changed, 177 deletions(-) diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 78c0fbedc9..e588f51e30 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -1270,32 +1270,3 @@ func (mr *MockGovKeeperMockRecorder) GetProposal(ctx, proposalID interface{}) *g mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProposal", reflect.TypeOf((*MockGovKeeper)(nil).GetProposal), ctx, proposalID) } - -// GetProposals mocks base method. -func (m *MockGovKeeper) GetProposals(ctx types0.Context) v1.Proposals { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetProposals", ctx) - ret0, _ := ret[0].(v1.Proposals) - return ret0 -} - -// GetProposals indicates an expected call of GetProposals. -func (mr *MockGovKeeperMockRecorder) GetProposals(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProposals", reflect.TypeOf((*MockGovKeeper)(nil).GetProposals), ctx) -} - -// GetVote mocks base method. -func (m *MockGovKeeper) GetVote(ctx types0.Context, proposalID uint64, voterAddr types0.AccAddress) (v1.Vote, bool) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetVote", ctx, proposalID, voterAddr) - ret0, _ := ret[0].(v1.Vote) - ret1, _ := ret[1].(bool) - return ret0, ret1 -} - -// GetVote indicates an expected call of GetVote. -func (mr *MockGovKeeperMockRecorder) GetVote(ctx, proposalID, voterAddr interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVote", reflect.TypeOf((*MockGovKeeper)(nil).GetVote), ctx, proposalID, voterAddr) -} diff --git a/x/ccv/provider/keeper/hooks.go b/x/ccv/provider/keeper/hooks.go index d9025a1f98..88590a9875 100644 --- a/x/ccv/provider/keeper/hooks.go +++ b/x/ccv/provider/keeper/hooks.go @@ -3,8 +3,6 @@ package keeper import ( "fmt" - "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" sdkgov "github.com/cosmos/cosmos-sdk/x/gov/types" v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" @@ -177,62 +175,7 @@ func (h Hooks) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64 func (h Hooks) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) { } -// AfterProposalVote opts in validators that vote YES (with 100% weight) on a `ConsumerAdditionProposal`. If a -// validator votes multiple times, only the last vote would be considered on whether the validator is opted in or not. func (h Hooks) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { - validator, found := h.k.stakingKeeper.GetValidator(ctx, voterAddr.Bytes()) - if !found { - return - } - - consAddr, err := validator.GetConsAddr() - if err != nil { - h.k.Logger(ctx).Error("could not extract validator's consensus address", - "error", err.Error(), - "validator acc addr", voterAddr, - ) - return - } - - chainID, found := h.k.GetProposedConsumerChain(ctx, proposalID) - if !found { - return - } - - vote, found := h.k.govKeeper.GetVote(ctx, proposalID, voterAddr) - if !found { - h.k.Logger(ctx).Error("could not find vote for validator", - "validator acc addr", voterAddr, - "proposalID", proposalID, - ) - return - } - - if len(vote.Options) == 1 && vote.Options[0].Option == v1.VoteOption_VOTE_OPTION_YES { - // only consider votes that vote YES with 100% weight - weight, err := sdk.NewDecFromStr(vote.Options[0].Weight) - if err != nil { - h.k.Logger(ctx).Error("could not extract decimal value from vote's weight", - "vote", vote, - "error", err.Error(), - ) - return - } - if !weight.Equal(math.LegacyNewDec(1)) { - h.k.Logger(ctx).Error("single vote does not have a weight of 1", - "vote", vote, - ) - return - } - - // If the validator is already opted in, the `SetOptedIn` is a no-op - h.k.SetOptedIn(ctx, chainID, providertypes.NewProviderConsAddress(consAddr)) - } else { - // If vote is not a YES vote with 100% weight, we opt out the validator. - // Note that a validator still gets opted out even if before it manually opted in with a `MsgOptIn` message. - // This is because if a validator wants to opt in, there's no reason to not vote YES with 100% weight. - h.k.DeleteOptedIn(ctx, chainID, providertypes.NewProviderConsAddress(consAddr)) - } } func (h Hooks) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) { diff --git a/x/ccv/provider/keeper/hooks_test.go b/x/ccv/provider/keeper/hooks_test.go index 87589b52a1..a9cf69b350 100644 --- a/x/ccv/provider/keeper/hooks_test.go +++ b/x/ccv/provider/keeper/hooks_test.go @@ -9,19 +9,14 @@ import ( "cosmossdk.io/math" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - cryptotestutil "github.com/cosmos/interchain-security/v4/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v4/testutil/keeper" providerkeeper "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" - "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ) func TestValidatorConsensusKeyInUse(t *testing.T) { @@ -229,87 +224,3 @@ func TestGetConsumerAdditionLegacyPropFromProp(t *testing.T) { }) } } - -func TestAfterProposalVoteWithYesVote(t *testing.T) { - k, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - providerConsPubKey := ed25519.GenPrivKeyFromSecret([]byte{1}).PubKey() - pkAny, _ := codectypes.NewAnyWithValue(providerConsPubKey) - providerAddr := types.NewProviderConsAddress(providerConsPubKey.Address().Bytes()) - - options := []*v1.WeightedVoteOption{{Option: v1.OptionYes, Weight: "1"}} - k.SetProposedConsumerChain(ctx, "chainID", 1) - - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetValidator(ctx, gomock.Any()).Return( - stakingtypes.Validator{ConsensusPubkey: pkAny}, true), - mocks.MockGovKeeper.EXPECT().GetVote(ctx, gomock.Any(), gomock.Any()).Return( - v1.Vote{ProposalId: 1, Voter: "voter", Options: options}, true, - ), - ) - - require.False(t, k.IsOptedIn(ctx, "chainID", providerAddr)) - k.Hooks().AfterProposalVote(ctx, 1, sdk.AccAddress{}) - require.True(t, k.IsOptedIn(ctx, "chainID", providerAddr)) -} - -func TestAfterProposalVoteWithNoVote(t *testing.T) { - testCases := []struct { - name string - options []*v1.WeightedVoteOption - setup func(sdk.Context, []*v1.WeightedVoteOption, testkeeper.MockedKeepers, *codectypes.Any) - }{ - { - "Weighted vote with 100% NO", - []*v1.WeightedVoteOption{{Option: v1.OptionNo, Weight: "1"}}, - func(ctx sdk.Context, options []*v1.WeightedVoteOption, - mocks testkeeper.MockedKeepers, pubKey *codectypes.Any, - ) { - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetValidator(ctx, gomock.Any()).Return( - stakingtypes.Validator{ConsensusPubkey: pubKey}, true), - mocks.MockGovKeeper.EXPECT().GetVote(ctx, gomock.Any(), gomock.Any()).Return( - v1.Vote{ProposalId: 1, Voter: "voter", Options: options}, true, - ), - ) - }, - }, - { - "Weighted vote with 99.9% YES and 0.1% NO", - []*v1.WeightedVoteOption{{Option: v1.OptionYes, Weight: "0.999"}, {Option: v1.OptionNo, Weight: "0.001"}}, - func(ctx sdk.Context, options []*v1.WeightedVoteOption, - mocks testkeeper.MockedKeepers, pubKey *codectypes.Any, - ) { - gomock.InOrder( - mocks.MockStakingKeeper.EXPECT().GetValidator(ctx, gomock.Any()).Return( - stakingtypes.Validator{ConsensusPubkey: pubKey}, true), - mocks.MockGovKeeper.EXPECT().GetVote(ctx, gomock.Any(), gomock.Any()).Return( - v1.Vote{ProposalId: 1, Voter: "voter", Options: options}, true, - ), - ) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - k, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - providerConsPubKey := ed25519.GenPrivKeyFromSecret([]byte{1}).PubKey() - pkAny, _ := codectypes.NewAnyWithValue(providerConsPubKey) - providerAddr := types.NewProviderConsAddress(providerConsPubKey.Address().Bytes()) - - k.SetProposedConsumerChain(ctx, "chainID", 1) - - tc.setup(ctx, tc.options, mocks, pkAny) - - // set the validator to-be-opted in first to assert that a NO vote removes the validator from to-be-opted in - k.SetOptedIn(ctx, "chainID", providerAddr) - require.True(t, k.IsOptedIn(ctx, "chainID", providerAddr)) - k.Hooks().AfterProposalVote(ctx, 1, sdk.AccAddress{}) - require.False(t, k.IsOptedIn(ctx, "chainID", providerAddr)) - }) - } -} diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index aaff34d878..73566926ec 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -156,6 +156,4 @@ type ScopedKeeper interface { type GovKeeper interface { GetProposal(ctx sdk.Context, proposalID uint64) (v1.Proposal, bool) - GetProposals(ctx sdk.Context) (proposals v1.Proposals) - GetVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) (vote v1.Vote, found bool) } From 330c085024997377d6d01e2c3159f9562a0d913a Mon Sep 17 00:00:00 2001 From: insumity Date: Wed, 20 Mar 2024 13:58:06 +0100 Subject: [PATCH 12/14] tiny fix for E2E tests --- tests/e2e/actions.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 5fea89ec12..e49c37ae71 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -278,6 +278,7 @@ func (tr TestConfig) submitConsumerAdditionProposal( UnbondingPeriod: params.UnbondingPeriod, Deposit: fmt.Sprint(action.Deposit) + `stake`, DistributionTransmissionChannel: action.DistributionChannel, + TopN: 100, } bz, err := json.Marshal(prop) From 629cfd1a400d08ead396e4e6dc5050f18ef85665 Mon Sep 17 00:00:00 2001 From: insumity Date: Thu, 21 Mar 2024 13:36:12 +0100 Subject: [PATCH 13/14] nit change to remove unecessary else --- x/ccv/provider/keeper/partial_set_security.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go index 754a782f48..3e8d1c1a8c 100644 --- a/x/ccv/provider/keeper/partial_set_security.go +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -89,9 +89,7 @@ func (k Keeper) OptInTopNValidators(ctx sdk.Context, chainID string, bondedValid // if validator already exists it gets overwritten k.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(consAddr)) - } else { - // validators that do not belong to the top N validators but were opted in, remain opted in - } + } // else validators that do not belong to the top N validators but were opted in, remain opted in } } From daaeeabf802d07141764a5e3614e01dd725c1d3f Mon Sep 17 00:00:00 2001 From: insumity Date: Mon, 25 Mar 2024 14:23:22 +0100 Subject: [PATCH 14/14] fixed topN == 0 issue --- x/ccv/provider/keeper/partial_set_security.go | 18 ++++++++++---- .../keeper/partial_set_security_test.go | 24 +++++++++++-------- x/ccv/provider/keeper/proposal.go | 2 +- x/ccv/provider/keeper/relay.go | 4 ++-- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go index 3e8d1c1a8c..c34885e117 100644 --- a/x/ccv/provider/keeper/partial_set_security.go +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -4,8 +4,8 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" + "math" "sort" ) @@ -51,7 +51,7 @@ func (k Keeper) HandleOptOut(ctx sdk.Context, chainID string, providerAddr types "opting out of an unknown or not running consumer chain, with id: %s", chainID) } - if topN, found := k.GetTopN(ctx, chainID); found { + if topN, found := k.GetTopN(ctx, chainID); found && topN > 0 { // a validator cannot opt out from a Top N chain if the validator is in the Top N validators validator, validatorFound := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) if !validatorFound { @@ -60,7 +60,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 := k.ComputeMinPowerToOptIn(ctx, k.stakingKeeper.GetLastValidators(ctx), topN) + minPowerToOptIn := k.ComputeMinPowerToOptIn(ctx, chainID, k.stakingKeeper.GetLastValidators(ctx), topN) if power >= minPowerToOptIn { return errorsmod.Wrapf( @@ -94,8 +94,16 @@ func (k Keeper) OptInTopNValidators(ctx sdk.Context, chainID string, bondedValid } // ComputeMinPowerToOptIn returns the minimum power needed for a validator (from the bonded validators) -// to belong to the `topN` validators -func (k Keeper) ComputeMinPowerToOptIn(ctx sdk.Context, bondedValidators []stakingtypes.Validator, topN uint32) int64 { +// 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 { + if topN == 0 { + // This should never happen but because `ComputeMinPowerToOptIn` is called during an `EndBlock` we do want + // to `panic` here. Instead, we log an error and return the maximum possible `int64`. + k.Logger(ctx).Error("trying to compute minimum power to opt in for a non-Top-N chain", + "chainID", chainID) + return math.MaxInt64 + } + totalPower := sdk.ZeroDec() var powers []int64 diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go index f17bd71a50..303bdd633f 100644 --- a/x/ccv/provider/keeper/partial_set_security_test.go +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "bytes" + "math" "sort" "testing" @@ -271,16 +272,19 @@ func TestComputeMinPowerToOptIn(t *testing.T) { createStakingValidator(ctx, mocks, 5, 6), } - require.Equal(t, int64(1), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 100)) - require.Equal(t, int64(1), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 97)) - require.Equal(t, int64(3), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 96)) - require.Equal(t, int64(3), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 85)) - require.Equal(t, int64(5), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 84)) - require.Equal(t, int64(5), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 65)) - require.Equal(t, int64(6), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 64)) - require.Equal(t, int64(6), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 41)) - require.Equal(t, int64(10), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 40)) - require.Equal(t, int64(10), providerKeeper.ComputeMinPowerToOptIn(ctx, bondedValidators, 1)) + require.Equal(t, int64(1), providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 100)) + require.Equal(t, int64(1), providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 97)) + require.Equal(t, int64(3), providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 96)) + require.Equal(t, int64(3), providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 85)) + require.Equal(t, int64(5), providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 84)) + require.Equal(t, int64(5), providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 65)) + require.Equal(t, int64(6), providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 64)) + require.Equal(t, int64(6), providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 41)) + require.Equal(t, int64(10), providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 40)) + require.Equal(t, int64(10), providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 1)) + + // exceptional case when we erroneously call with `topN == 0` + require.Equal(t, int64(math.MaxInt64), providerKeeper.ComputeMinPowerToOptIn(ctx, "chainID", bondedValidators, 0)) } // TestShouldConsiderOnlyOptIn returns true if `validator` is opted in, in `chainID. diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index bab1690f8b..360ff8f9d2 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -291,7 +291,7 @@ func (k Keeper) MakeConsumerGenesis( if prop.Top_N > 0 { // in a Top-N chain, we automatically opt in all validators that belong to the top N - minPower := k.ComputeMinPowerToOptIn(ctx, bondedValidators, prop.Top_N) + minPower := k.ComputeMinPowerToOptIn(ctx, chainID, bondedValidators, prop.Top_N) k.OptInTopNValidators(ctx, chainID, bondedValidators, minPower) } diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 9728d7f59f..32506297bb 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -223,9 +223,9 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { for _, chain := range k.GetAllConsumerChains(ctx) { currentValidators := k.GetConsumerValSet(ctx, chain.ChainId) - if topN, found := k.GetTopN(ctx, chain.ChainId); found { + 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 := k.ComputeMinPowerToOptIn(ctx, bondedValidators, topN) + minPower := k.ComputeMinPowerToOptIn(ctx, chain.ChainId, bondedValidators, topN) k.OptInTopNValidators(ctx, chain.ChainId, bondedValidators, minPower) }