Skip to content

Commit

Permalink
feat: PSS - Add minimum power in top N & power shaping params to cons…
Browse files Browse the repository at this point in the history
…umer chain list (#1863)

* Add minimum power in top N to the list-consumer-chains query

* Add test for MinPowerInTop_N

* Add changelog entry

* Update x/ccv/provider/keeper/keeper_test.go

Co-authored-by: insumity <[email protected]>

* Add other validator shaping params to consumer chain list

* Add power shaping params to query test

* Adjust changelog for extra fields

* Add changelog entry for API breaking

---------

Co-authored-by: insumity <[email protected]>
  • Loading branch information
p-offtermatt and insumity authored May 13, 2024
1 parent 8329a27 commit 78dad0c
Show file tree
Hide file tree
Showing 7 changed files with 507 additions and 123 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Changes the `list-consumer-chains` query to include a `min_power_in_top_N` field, as well as fields for all power shaping parameters of the consumer.
([\#1863](https://github.com/cosmos/interchain-security/pull/1863))
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Changes the `list-consumer-chains` query to include a `min_power_in_top_N` field, as well as fields for all power shaping parameters of the consumer.
([\#1863](https://github.com/cosmos/interchain-security/pull/1863))
13 changes: 13 additions & 0 deletions proto/interchain_security/ccv/provider/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,19 @@ message Chain {
string client_id = 2;
// If chain with `chainID` is a Top-N chain, i.e., enforces at least one validator to validate chain `chainID`
uint32 top_N = 3;
// If the chain is a Top-N chain, this is the minimum power required to be in the top N.
// Otherwise, this is -1.
int64 min_power_in_top_N = 4;
// Corresponds to the maximum power (percentage-wise) a validator can have on the consumer chain.
uint32 validators_power_cap = 5;
// Corresponds to the maximum number of validators that can validate a consumer chain.
// Only applicable to Opt In chains. Setting `validator_set_cap` on a Top N chain is a no-op.
uint32 validator_set_cap = 6;
// Corresponds to a list of provider consensus addresses of validators that are the ONLY ones that can validate
// the consumer chain.
repeated string allowlist = 7;
// Corresponds to a list of provider consensus addresses of validators that CANNOT validate the consumer chain.
repeated string denylist = 8;
}

message QueryValidatorConsumerAddrRequest {
Expand Down
Empty file modified scripts/protocgen.sh
100644 → 100755
Empty file.
74 changes: 70 additions & 4 deletions x/ccv/provider/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,44 @@ func (k Keeper) GetAllConsumerChains(ctx sdk.Context) (chains []types.Chain) {
chainID := string(iterator.Key()[1:])
clientID := string(iterator.Value())

topN, _ := k.GetTopN(ctx, chainID)
topN, found := k.GetTopN(ctx, chainID)

var minPowerInTopN int64
if found && topN > 0 {
res, err := k.ComputeMinPowerToOptIn(ctx, chainID, k.stakingKeeper.GetLastValidators(ctx), topN)
if err != nil {
k.Logger(ctx).Error("failed to compute min power to opt in for chain", "chain", chainID, "error", err)
minPowerInTopN = -1
} else {
minPowerInTopN = res
}
} else {
minPowerInTopN = -1
}

validatorSetCap, _ := k.GetValidatorSetCap(ctx, chainID)
validatorsPowerCap, _ := k.GetValidatorsPowerCap(ctx, chainID)
allowlist := k.GetAllowList(ctx, chainID)
strAllowlist := make([]string, len(allowlist))
for i, addr := range allowlist {
strAllowlist[i] = addr.String()
}

denylist := k.GetDenyList(ctx, chainID)
strDenylist := make([]string, len(denylist))
for i, addr := range denylist {
strDenylist[i] = addr.String()
}

chains = append(chains, types.Chain{
ChainId: chainID,
ClientId: clientID,
Top_N: topN,
ChainId: chainID,
ClientId: clientID,
Top_N: topN,
MinPowerInTop_N: minPowerInTopN,
ValidatorSetCap: validatorSetCap,
ValidatorsPowerCap: validatorsPowerCap,
Allowlist: strAllowlist,
Denylist: strDenylist,
})
}

Expand Down Expand Up @@ -1446,6 +1478,23 @@ func (k Keeper) SetAllowlist(
store.Set(types.AllowlistCapKey(chainID, providerAddr), []byte{})
}

// GetAllowList returns all allowlisted validators
func (k Keeper) GetAllowList(
ctx sdk.Context,
chainID string,
) (providerConsAddresses []types.ProviderConsAddress) {
store := ctx.KVStore(k.storeKey)
key := types.ChainIdWithLenKey(types.AllowlistPrefix, chainID)
iterator := sdk.KVStorePrefixIterator(store, key)
defer iterator.Close()

for ; iterator.Valid(); iterator.Next() {
providerConsAddresses = append(providerConsAddresses, types.NewProviderConsAddress(iterator.Key()[len(key):]))
}

return providerConsAddresses
}

// IsAllowlisted returns `true` if validator with `providerAddr` has been allowlisted on chain `chainID`
func (k Keeper) IsAllowlisted(
ctx sdk.Context,
Expand Down Expand Up @@ -1496,6 +1545,23 @@ func (k Keeper) SetDenylist(
store.Set(types.DenylistCapKey(chainID, providerAddr), []byte{})
}

// GetDenyList returns all denylisted validators
func (k Keeper) GetDenyList(
ctx sdk.Context,
chainID string,
) (providerConsAddresses []types.ProviderConsAddress) {
store := ctx.KVStore(k.storeKey)
key := types.ChainIdWithLenKey(types.DenylistPrefix, chainID)
iterator := sdk.KVStorePrefixIterator(store, key)
defer iterator.Close()

for ; iterator.Valid(); iterator.Next() {
providerConsAddresses = append(providerConsAddresses, types.NewProviderConsAddress(iterator.Key()[len(key):]))
}

return providerConsAddresses
}

// IsDenylisted returns `true` if validator with `providerAddr` has been denylisted on chain `chainID`
func (k Keeper) IsDenylisted(
ctx sdk.Context,
Expand Down
77 changes: 74 additions & 3 deletions x/ccv/provider/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

ibctesting "github.com/cosmos/ibc-go/v7/testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"

cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
Expand All @@ -20,6 +21,8 @@ import (
testkeeper "github.com/cosmos/interchain-security/v4/testutil/keeper"
"github.com/cosmos/interchain-security/v4/x/ccv/provider/types"
ccv "github.com/cosmos/interchain-security/v4/x/ccv/types"

stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

const consumer = "consumer"
Expand Down Expand Up @@ -395,17 +398,85 @@ func TestVscSendTimestamp(t *testing.T) {

// TestGetAllConsumerChains tests GetAllConsumerChains behaviour correctness
func TestGetAllConsumerChains(t *testing.T) {
pk, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t))
pk, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t))
defer ctrl.Finish()

chainIDs := []string{"chain-2", "chain-1", "chain-4", "chain-3"}

// mock the validator set
vals := []stakingtypes.Validator{
{OperatorAddress: "cosmosvaloper1c4k24jzduc365kywrsvf5ujz4ya6mwympnc4en"}, // 50 power
{OperatorAddress: "cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcvrj90c"}, // 150 power
{OperatorAddress: "cosmosvaloper1clpqr4nrk4khgkxj78fcwwh6dl3uw4epsluffn"}, // 300 power
{OperatorAddress: "cosmosvaloper1tflk30mq5vgqjdly92kkhhq3raev2hnz6eete3"}, // 500 power
}
powers := []int64{50, 150, 300, 500} // sum = 1000
mocks.MockStakingKeeper.EXPECT().GetLastValidators(gomock.Any()).Return(vals).AnyTimes()

for i, val := range vals {
mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), val.GetOperator()).Return(powers[i]).AnyTimes()
}

// set Top N parameters, client ids and expected result
topNs := []uint32{0, 70, 90, 100}
expectedMinPowerInTopNs := []int64{
-1, // Top N is 0, so not a Top N chain
300, // 500 and 300 are in Top 70%
150, // 150 is also in the top 90%,
50, // everyone is in the top 100%
}

validatorSetCaps := []uint32{0, 5, 10, 20}
validatorPowerCaps := []uint32{0, 5, 10, 33}
allowlists := [][]types.ProviderConsAddress{
{},
{types.NewProviderConsAddress([]byte("providerAddr1")), types.NewProviderConsAddress([]byte("providerAddr2"))},
{types.NewProviderConsAddress([]byte("providerAddr3"))},
{},
}

denylists := [][]types.ProviderConsAddress{
{types.NewProviderConsAddress([]byte("providerAddr4")), types.NewProviderConsAddress([]byte("providerAddr5"))},
{},
{types.NewProviderConsAddress([]byte("providerAddr6"))},
{},
}

expectedGetAllOrder := []types.Chain{}
for i, chainID := range chainIDs {
clientID := fmt.Sprintf("client-%d", len(chainIDs)-i)
topN := uint32(i)
topN := topNs[i]
pk.SetConsumerClientId(ctx, chainID, clientID)
pk.SetTopN(ctx, chainID, topN)
expectedGetAllOrder = append(expectedGetAllOrder, types.Chain{ChainId: chainID, ClientId: clientID, Top_N: topN})
pk.SetValidatorSetCap(ctx, chainID, validatorSetCaps[i])
pk.SetValidatorsPowerCap(ctx, chainID, validatorPowerCaps[i])
for _, addr := range allowlists[i] {
pk.SetAllowlist(ctx, chainID, addr)
}
for _, addr := range denylists[i] {
pk.SetDenylist(ctx, chainID, addr)
}
strAllowlist := make([]string, len(allowlists[i]))
for j, addr := range allowlists[i] {
strAllowlist[j] = addr.String()
}

strDenylist := make([]string, len(denylists[i]))
for j, addr := range denylists[i] {
strDenylist[j] = addr.String()
}

expectedGetAllOrder = append(expectedGetAllOrder,
types.Chain{
ChainId: chainID,
ClientId: clientID,
Top_N: topN,
MinPowerInTop_N: expectedMinPowerInTopNs[i],
ValidatorSetCap: validatorSetCaps[i],
ValidatorsPowerCap: validatorPowerCaps[i],
Allowlist: strAllowlist,
Denylist: strDenylist,
})
}
// sorting by chainID
sort.Slice(expectedGetAllOrder, func(i, j int) bool {
Expand Down
Loading

0 comments on commit 78dad0c

Please sign in to comment.