Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat!: compute partial sets #1702

Merged
merged 15 commits into from
Mar 25, 2024
13 changes: 0 additions & 13 deletions proto/interchain_security/ccv/provider/v1/provider.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
9 changes: 4 additions & 5 deletions x/ccv/provider/keeper/distribution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
)

Expand Down
22 changes: 11 additions & 11 deletions x/ccv/provider/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1189,15 +1189,15 @@
func (k Keeper) SetOptedIn(
ctx sdk.Context,
chainID string,
validator types.OptedInValidator,
validator types.ConsumerValidator,
) {
store := ctx.KVStore(k.storeKey)
bz, err := validator.Marshal()
if err != nil {
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(
Expand All @@ -1206,7 +1206,7 @@
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(
Expand All @@ -1215,28 +1215,28 @@
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))
Fixed Show fixed Hide fixed
}
optedInValidators = append(optedInValidators, optedInValidator)
consumerValidators = append(consumerValidators, consumerValidator)
}

return optedInValidators
return consumerValidators
}

func (k Keeper) SetToBeOptedIn(
Expand Down
68 changes: 39 additions & 29 deletions x/ccv/provider/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) {
Expand Down
8 changes: 6 additions & 2 deletions x/ccv/provider/keeper/partial_set_security_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper_test

import (
"github.com/cometbft/cometbft/proto/tendermint/crypto"
"testing"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
Expand Down Expand Up @@ -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))
}
Expand Down
13 changes: 12 additions & 1 deletion x/ccv/provider/keeper/relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,18 @@ 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

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)
}

nextValidators = k.ComputeNextEpochOptedInConsumerValSet(ctx, chain.ChainId)
// nextValidators = k.ComputeNextEpochConsumerValSet(ctx, chain.ChainId, bondedValidators)
valUpdates := DiffValidators(currentValidators, nextValidators)
k.SetConsumerValSet(ctx, chain.ChainId, nextValidators)

Expand Down
6 changes: 6 additions & 0 deletions x/ccv/provider/keeper/relay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
insumity marked this conversation as resolved.
Show resolved Hide resolved
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")

Expand Down
111 changes: 111 additions & 0 deletions x/ccv/provider/keeper/validator_set_update.go
Original file line number Diff line number Diff line change
@@ -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`
Expand Down Expand Up @@ -176,3 +178,112 @@
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))
Fixed Show fixed Hide fixed
}
}

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))
Fixed Show fixed Hide fixed
}
}
// 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
}
}

panic("...")
Fixed Show fixed Hide fixed
}
Loading
Loading