Skip to content

Commit

Permalink
feat: extend consumer chains query (#2172)
Browse files Browse the repository at this point in the history
* feat: first iteration on Permissionless ICS (#2117)

* (partially) renamed chain ids to consumer ids

* renamed proposal messages

* removed global slash entry

* fixed unit tests

* added new messages

* introduced new state

* added functionality for the register and initialize messages

* renamed (partially) chainIds to consumerIds

* set consumerId to chainId association during registration

* added extra check in the initialization so unknokwn, launched, or stopped chains cannot re-initialize

* added initial work on traversing initialized chains that are to-be-launched

* fixed rebase issues after bringing the VSCMaturedPackets work in

* made it so we traverse initialization records instead of addition proposals (+ additional changes so the unit tests pass)

* renamed more chainIDs to consumerIds

* removed ClientIdToChainId state because chainId already resides on the registration record

* nit fixes in go docs

* removed MsgConsumerAddition

* added CLI commands for new messages

* removed consumer modification proposal

* removed (partially) consumer removal proposal

* rebased to pick up the inactive-validators work (PR #2079)

* introduced consumerId in the equivocation messages (and a useful query for Hermes to get the consumerId)

* added safeguard so that a validator cannot opt-in to two different chains with the same chain id

* renamed some chainIDs to consumerIds

* updated based on comments

Co-authored-by: bernd-m <[email protected]>

* fixed integration tests

* rebased to pick up the removal of legacy proposals (#2130) and re-introduced old messages so that existing proposals can deserialize

* changes messages to only have MsgCreateConsumer and MsgUpdateConsumer and modified protos so that we are backward-compatible

* cleaned up slightly a few things (mostly committing & pushing) so people can pick up the latest changes

* fixed the CreateConsumer and UpdateConsumer logic and made most of the fields optional

* fixed hooks and the code around proposalId to consumerId

* feat: extend consumer validator query to return commission rate (backport #2162) (#2165)

* adapt #2162 changes for permissionless ICS

* nits

---------

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

* renamed some chainIds to consumerIds

* took into account comments and also added safeguard to reject new proposals that still use deprecated messages (e.g., MsgConsumerAddition, etc.)

* Update x/ccv/provider/types/msg.go

Co-authored-by: bernd-m <[email protected]>

* removed double-gas charge on MsgCreateConsumer and imroved the logic of MsgUpdateConsumer

* added PopulateMinimumPowerInTopN tested

* took into account comments (using protos for marshalling string slice, fixed issues in the UpdateConsumer logic, added extra check to abort spurious proposals)

* feat: add fields to consumer validators query (#2167)

* extend consumer validators query

* nit

* nits

* fix msg order

* deprecate power for consumer_power

* modified the way we verify the new owner address, as well as nit refactoring on the ConsumerIds

* fixed some rebase issues and changed a proto to be backward-compatible

---------

Co-authored-by: bernd-m <[email protected]>
Co-authored-by: Simon Noetzlin <[email protected]>
Co-authored-by: kirdatatjana <[email protected]>

* add phase + metadata

* first logic draft

* add filter

* reformat test

* nits

* nit

* address comments

* update tests

* nits

* update logic

* update CLI

* revert unwanted changes

* remove filter field

* nit

* fix bad int conversion

* update cli

---------

Co-authored-by: insumity <[email protected]>
Co-authored-by: bernd-m <[email protected]>
Co-authored-by: kirdatatjana <[email protected]>
  • Loading branch information
4 people authored Aug 29, 2024
1 parent aec6c93 commit 4ed2723
Show file tree
Hide file tree
Showing 7 changed files with 587 additions and 201 deletions.
18 changes: 14 additions & 4 deletions proto/interchain_security/ccv/provider/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,14 @@ message QueryConsumerGenesisResponse {
[ (gogoproto.nullable) = false ];
}

message QueryConsumerChainsRequest {}
message QueryConsumerChainsRequest {
// The phase of the consumer chains returned (optional)
// Registered=1|Initialized=2|Launched=3|Stopped=4
ConsumerPhase phase = 1;
// The limit of consumer chains returned (optional)
// default is 100
int32 limit = 2;
}

message QueryConsumerChainsResponse { repeated Chain chains = 1; }

Expand Down Expand Up @@ -233,11 +240,14 @@ message 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;
// The phase the consumer chain (Registered=0|Initialized=1|FailedToLaunch=2|Launched=3|Stopped=4)
ConsumerPhase phase = 9;

Check failure on line 244 in proto/interchain_security/ccv/provider/v1/query.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "9" with name "phase" on message "Chain" changed option "json_name" from "minStake" to "phase".

Check failure on line 244 in proto/interchain_security/ccv/provider/v1/query.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "9" with name "phase" on message "Chain" changed type from "uint64" to "enum".

Check failure on line 244 in proto/interchain_security/ccv/provider/v1/query.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "9" on message "Chain" changed name from "min_stake" to "phase".
// The metadata of the consumer chain
ConsumerMetadata metadata = 10 [(gogoproto.nullable) = false ];

Check failure on line 246 in proto/interchain_security/ccv/provider/v1/query.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "10" with name "metadata" on message "Chain" changed cardinality from "optional with implicit presence" to "optional with explicit presence".

Check failure on line 246 in proto/interchain_security/ccv/provider/v1/query.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "10" with name "metadata" on message "Chain" changed option "json_name" from "allowInactiveVals" to "metadata".

Check failure on line 246 in proto/interchain_security/ccv/provider/v1/query.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "10" with name "metadata" on message "Chain" changed type from "bool" to "message".

Check failure on line 246 in proto/interchain_security/ccv/provider/v1/query.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "10" on message "Chain" changed name from "allow_inactive_vals" to "metadata".
// Corresponds to the minimal amount of (provider chain) stake required to validate on the consumer chain.
uint64 min_stake = 9;
uint64 min_stake = 11;
// Corresponds to whether inactive validators are allowed to validate the consumer chain.
bool allow_inactive_vals = 10;

bool allow_inactive_vals = 12;
}

message QueryValidatorConsumerAddrRequest {
Expand Down
27 changes: 24 additions & 3 deletions x/ccv/provider/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"fmt"
"strconv"
"strings"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -75,9 +76,12 @@ func CmdConsumerGenesis() *cobra.Command {

func CmdConsumerChains() *cobra.Command {
cmd := &cobra.Command{
Use: "list-consumer-chains",
Short: "Query active consumer chains for provider chain.",
Args: cobra.ExactArgs(0),
Use: "list-consumer-chains [phase] [limit]",
Short: "Query consumer chains for provider chain.",
Long: `Query consumer chains for provider chain. An optional
integer parameter can be passed for phase filtering of consumer chains,
(Registered=1|Initialized=2|Launched=3|Stopped=4).`,
Args: cobra.MaximumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) (err error) {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
Expand All @@ -86,6 +90,23 @@ func CmdConsumerChains() *cobra.Command {
queryClient := types.NewQueryClient(clientCtx)

req := &types.QueryConsumerChainsRequest{}

if args[0] != "" {
phase, err := strconv.ParseInt(args[0], 10, 32)
if err != nil {
return err
}
req.Phase = types.ConsumerPhase(phase)
}

if args[1] != "" {
limit, err := strconv.ParseInt(args[1], 10, 32)
if err != nil {
return err
}
req.Limit = int32(limit)
}

res, err := queryClient.QueryConsumerChains(cmd.Context(), req)
if err != nil {
return err
Expand Down
74 changes: 58 additions & 16 deletions x/ccv/provider/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import (
"context"
"fmt"
"sort"
"strconv"

"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

errorsmod "cosmossdk.io/errors"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/cosmos/interchain-security/v5/x/ccv/provider/types"
ccvtypes "github.com/cosmos/interchain-security/v5/x/ccv/types"
)
Expand Down Expand Up @@ -51,13 +52,51 @@ func (k Keeper) QueryConsumerChains(goCtx context.Context, req *types.QueryConsu

ctx := sdk.UnwrapSDKContext(goCtx)

chains := []*types.Chain{}
for _, chainID := range k.GetAllRegisteredConsumerIds(ctx) {
c, err := k.GetConsumerChain(ctx, chainID)
consumerIds := []string{}
phaseFilter := req.Phase

// if the phase filter is set Launched get consumer from the state directly
if phaseFilter == types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED {
consumerIds = append(consumerIds, k.GetAllRegisteredConsumerIds(ctx)...)
// otherwise iterate over all the consumer using the last unused consumer Id
} else {
firstUnusedConsumerId, ok := k.GetConsumerId(ctx)
if !ok {
return &types.QueryConsumerChainsResponse{}, nil
}
for i := uint64(0); i < firstUnusedConsumerId; i++ {
// if the phase filter is set, verify that the consumer has the same phase
if phaseFilter != types.ConsumerPhase_CONSUMER_PHASE_UNSPECIFIED {
p := k.GetConsumerPhase(ctx, strconv.FormatInt(int64(i), 10))
if p == types.ConsumerPhase_CONSUMER_PHASE_UNSPECIFIED {
return nil, status.Error(codes.Internal, fmt.Sprintf("cannot retrieve phase for consumer id: %d", i))
}
if p != phaseFilter {
continue
}
}

consumerIds = append(consumerIds, strconv.FormatInt(int64(i), 10))
}
}

// set limit to default value
limit := 100
if req.Limit != 0 {
// update limit if specified
limit = int(req.Limit)
}

chains := make([]*types.Chain, math.Min(len(consumerIds), limit))
for i, cID := range consumerIds {
if i == limit {
break
}
c, err := k.GetConsumerChain(ctx, cID)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
chains = append(chains, &c)
chains[i] = &c
}

return &types.QueryConsumerChainsResponse{Chains: chains}, nil
Expand All @@ -70,11 +109,7 @@ func (k Keeper) GetConsumerChain(ctx sdk.Context, consumerId string) (types.Chai
return types.Chain{}, fmt.Errorf("cannot find chainID for consumer (%s)", consumerId)
}

clientID, found := k.GetConsumerClientId(ctx, consumerId)
if !found {
return types.Chain{}, fmt.Errorf("cannot find clientID for consumer (%s)", consumerId)
}

clientID, _ := k.GetConsumerClientId(ctx, consumerId)
topN := k.GetTopN(ctx, consumerId)

// Get the minimal power in the top N for the consumer chain
Expand All @@ -84,9 +119,10 @@ func (k Keeper) GetConsumerChain(ctx sdk.Context, consumerId string) (types.Chai
minPowerInTopN = -1
}

validatorSetCap := k.GetValidatorSetCap(ctx, consumerId)

validatorsPowerCap := k.GetValidatorsPowerCap(ctx, consumerId)
phase := k.GetConsumerPhase(ctx, consumerId)
if phase == types.ConsumerPhase_CONSUMER_PHASE_UNSPECIFIED {
return types.Chain{}, fmt.Errorf("cannot find phase for consumer (%s)", consumerId)
}

allowlist := k.GetAllowList(ctx, consumerId)
strAllowlist := make([]string, len(allowlist))
Expand All @@ -100,19 +136,25 @@ func (k Keeper) GetConsumerChain(ctx sdk.Context, consumerId string) (types.Chai
strDenylist[i] = addr.String()
}

allowInactiveVals := k.AllowsInactiveValidators(ctx, consumerId)
metadata, err := k.GetConsumerMetadata(ctx, consumerId)
if err != nil {
return types.Chain{}, fmt.Errorf("cannot get metadata for consumer (%s): %w", consumerId, err)
}

allowInactiveVals := k.AllowsInactiveValidators(ctx, consumerId)
minStake := k.GetMinStake(ctx, consumerId)

return types.Chain{
ChainId: chainID,
ClientId: clientID,
Top_N: topN,
MinPowerInTop_N: minPowerInTopN,
ValidatorSetCap: validatorSetCap,
ValidatorsPowerCap: validatorsPowerCap,
ValidatorSetCap: k.GetValidatorSetCap(ctx, consumerId),
ValidatorsPowerCap: k.GetValidatorsPowerCap(ctx, consumerId),
Allowlist: strAllowlist,
Denylist: strDenylist,
Phase: phase,
Metadata: metadata,
AllowInactiveVals: allowInactiveVals,
MinStake: minStake,
}, nil
Expand Down
131 changes: 129 additions & 2 deletions x/ccv/provider/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"bytes"
"fmt"
"sort"
"strconv"
"testing"
"time"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
Expand All @@ -18,6 +20,7 @@ import (

cryptotestutil "github.com/cosmos/interchain-security/v5/testutil/crypto"
testkeeper "github.com/cosmos/interchain-security/v5/testutil/keeper"
"github.com/cosmos/interchain-security/v5/x/ccv/provider/keeper"
"github.com/cosmos/interchain-security/v5/x/ccv/provider/types"
ccvtypes "github.com/cosmos/interchain-security/v5/x/ccv/types"
)
Expand Down Expand Up @@ -427,6 +430,8 @@ func TestGetConsumerChain(t *testing.T) {
math.NewInt(300),
}

metadataLists := []types.ConsumerMetadata{}

expectedGetAllOrder := []types.Chain{}
for i, consumerID := range consumerIDs {
pk.SetConsumerChainId(ctx, consumerID, chainIDs[i])
Expand All @@ -437,6 +442,8 @@ func TestGetConsumerChain(t *testing.T) {
Top_N: topN,
ValidatorSetCap: validatorSetCaps[i],
ValidatorsPowerCap: validatorPowerCaps[i],
MinStake: minStakes[i].Uint64(),
AllowInactiveVals: allowInactiveVals[i],
})
pk.SetMinimumPowerInTopN(ctx, consumerID, expectedMinPowerInTopNs[i])
for _, addr := range allowlists[i] {
Expand All @@ -455,6 +462,12 @@ func TestGetConsumerChain(t *testing.T) {
strDenylist[j] = addr.String()
}

metadataLists = append(metadataLists, types.ConsumerMetadata{Name: chainIDs[i]})
pk.SetConsumerMetadata(ctx, consumerID, metadataLists[i])

phase := types.ConsumerPhase(int32(i + 1))
pk.SetConsumerPhase(ctx, consumerID, phase)

expectedGetAllOrder = append(expectedGetAllOrder,
types.Chain{
ChainId: chainIDs[i],
Expand All @@ -465,13 +478,15 @@ func TestGetConsumerChain(t *testing.T) {
ValidatorsPowerCap: validatorPowerCaps[i],
Allowlist: strAllowlist,
Denylist: strDenylist,
Phase: phase,
Metadata: metadataLists[i],
AllowInactiveVals: allowInactiveVals[i],
MinStake: minStakes[i].Uint64(),
})
}

for i, chainID := range pk.GetAllActiveConsumerIds(ctx) {
c, err := pk.GetConsumerChain(ctx, chainID)
for i, cId := range consumerIDs {
c, err := pk.GetConsumerChain(ctx, cId)
require.NoError(t, err)
require.Equal(t, expectedGetAllOrder[i], c)
}
Expand Down Expand Up @@ -562,3 +577,115 @@ func TestQueryConsumerIdFromClientId(t *testing.T) {
require.NoError(t, err)
require.Equal(t, expectedConsumerId, res.ConsumerId)
}

func TestQueryConsumerChains(t *testing.T) {
pk, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t))
defer ctrl.Finish()

consumerNum := 4
consumerIds := make([]string, consumerNum)
consumers := make([]*types.Chain, consumerNum)

// expect no error and returned chains
res, err := pk.QueryConsumerChains(ctx, &types.QueryConsumerChainsRequest{})
require.NoError(t, err)
require.Len(t, res.Chains, 0)

// create four consumer chains in different phase
for i := 0; i < consumerNum; i++ {
cID := pk.FetchAndIncrementConsumerId(ctx)
chainID := "consumer-" + strconv.Itoa(i)
c := types.Chain{
ChainId: chainID,
MinPowerInTop_N: -1,
ValidatorsPowerCap: 0,
ValidatorSetCap: 0,
Allowlist: []string{},
Denylist: []string{},
Phase: types.ConsumerPhase(i + 1),
Metadata: types.ConsumerMetadata{Name: chainID},
}
pk.SetConsumerPhase(ctx, cID, c.Phase)
pk.SetConsumerMetadata(ctx, cID, c.Metadata)
pk.SetConsumerChainId(ctx, cID, chainID)

consumerIds[i] = cID
consumers[i] = &c
}

testCases := []struct {
name string
setup func(ctx sdk.Context, pk keeper.Keeper)
phase_filter types.ConsumerPhase
limit int32
expConsumers []*types.Chain
}{
{
name: "expect all consumers when phase filter isn't set",
setup: func(ctx sdk.Context, pk keeper.Keeper) {},
expConsumers: consumers,
},
{
name: "expect an amount of consumer equal to the limit",
setup: func(ctx sdk.Context, pk keeper.Keeper) {},
expConsumers: consumers[:3],
limit: int32(3),
},
{
name: "expect registered consumers when phase filter is set to Registered",
setup: func(ctx sdk.Context, pk keeper.Keeper) {
consumers[0].Phase = types.ConsumerPhase_CONSUMER_PHASE_REGISTERED
pk.SetConsumerPhase(ctx, consumerIds[0], types.ConsumerPhase_CONSUMER_PHASE_REGISTERED)
},
phase_filter: types.ConsumerPhase_CONSUMER_PHASE_REGISTERED,
expConsumers: consumers[0:1],
},
{
name: "expect initialized consumers when phase is set to Initialized",
setup: func(ctx sdk.Context, pk keeper.Keeper) {
consumers[1].Phase = types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED
err := pk.AppendConsumerToBeLaunchedOnSpawnTime(ctx, consumerIds[1], time.Now())
require.NoError(t, err)
pk.SetConsumerPhase(ctx, consumerIds[1], types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED)
},
phase_filter: types.ConsumerPhase_CONSUMER_PHASE_INITIALIZED,
expConsumers: consumers[1:2],
},
{
name: "expect launched consumers when phase is set to Launched",
setup: func(ctx sdk.Context, pk keeper.Keeper) {
consumers[2].Phase = types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED
consumers[2].ClientId = "ClientID"
pk.SetConsumerClientId(ctx, consumerIds[2], consumers[2].ClientId)
pk.SetConsumerPhase(ctx, consumerIds[2], types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED)
},
phase_filter: types.ConsumerPhase_CONSUMER_PHASE_LAUNCHED,
expConsumers: consumers[2:3],
},
{
name: "expect stopped consumers when phase is set to Stopped",
setup: func(ctx sdk.Context, pk keeper.Keeper) {
consumers[3].Phase = types.ConsumerPhase_CONSUMER_PHASE_STOPPED
pk.SetConsumerPhase(ctx, consumerIds[3], types.ConsumerPhase_CONSUMER_PHASE_STOPPED)
},
phase_filter: types.ConsumerPhase_CONSUMER_PHASE_STOPPED,
expConsumers: consumers[3:],
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.setup(ctx, pk)
req := types.QueryConsumerChainsRequest{
Phase: tc.phase_filter,
Limit: tc.limit,
}
expectedResponse := types.QueryConsumerChainsResponse{
Chains: tc.expConsumers,
}
res, err := pk.QueryConsumerChains(ctx, &req)
require.NoError(t, err)
require.Equal(t, &expectedResponse, res)
})
}
}
Loading

0 comments on commit 4ed2723

Please sign in to comment.