Skip to content

Commit

Permalink
feat: Add queries for PSS and consumer commission rate (#1733)
Browse files Browse the repository at this point in the history
* init commit

* nit change

* cleaning up

* clean up

* fix distribution test

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

Co-authored-by: Simon Noetzlin <simon.ntz@gmail.com>

* took into Simon's comments

* took into rest of the comments

* nit change

* return an error if validator cannot opt out from a Top N chain

* removed automatic opt-in for validators that vote Yes on proposals

* tiny fix for E2E tests

* nit change to remove unecessary else

* update consumer chains query to return topN

* update query consu chains proto

* add consumer chains per validator query

* Add PSS command to provider's cli

* nits

* add consumer commission rate query

* nits

* big renaming

* fix doc

* nits

* nits

* docs

* Update proto/interchain_security/ccv/provider/v1/query.proto

Co-authored-by: insumity <karolos@informal.systems>

* nit

* add OptedIn in QueryConsumerChainsValidatorHasToValidate

* remove OptIn field in consumer chains query response

* include validators that opt-in during the next epochs

* update has-to-validate condition

* fix tinny bug in the tests after merging  feat/partial-security

* update doc

* update cli description

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

Co-authored-by: insumity <karolos@informal.systems>

* changes

---------

Co-authored-by: insumity <karolos@informal.systems>
  • Loading branch information
sainoe and insumity authored Mar 28, 2024
1 parent a6989bf commit bfe10df
Show file tree
Hide file tree
Showing 10 changed files with 1,954 additions and 117 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ require (
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/protobuf v1.33.0
google.golang.org/grpc v1.62.1
google.golang.org/protobuf v1.33.0
gopkg.in/yaml.v2 v2.4.0
)

Expand Down
64 changes: 64 additions & 0 deletions proto/interchain_security/ccv/provider/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "interchain_security/ccv/provider/v1/provider.proto";
import "interchain_security/ccv/v1/shared_consumer.proto";
import "interchain_security/ccv/v1/wire.proto";
import "tendermint/crypto/keys.proto";
import "cosmos_proto/cosmos.proto";

service Query {
// ConsumerGenesis queries the genesis state needed to start a consumer chain
Expand Down Expand Up @@ -99,6 +100,33 @@ service Query {
option (google.api.http).get =
"/interchain_security/ccv/provider/params";
}

// QueryConsumerChainOptedInValidators returns a list of validators consensus addresses
// that opted-in to the given consumer chain
rpc QueryConsumerChainOptedInValidators(
QueryConsumerChainOptedInValidatorsRequest)
returns (QueryConsumerChainOptedInValidatorsResponse) {
option (google.api.http).get =
"/interchain_security/ccv/provider/opted_in_validators";
}

// QueryConsumerChainsValidatorHasToValidate returns a list of consumer chains
// that a given validator must validate
rpc QueryConsumerChainsValidatorHasToValidate(
QueryConsumerChainsValidatorHasToValidateRequest)
returns (QueryConsumerChainsValidatorHasToValidateResponse) {
option (google.api.http).get =
"/interchain_security/ccv/provider/consumer_chains_per_validator";
}

// QueryValidatorConsumerCommissionRate returns the commission rate a given
// validator charges on a given consumer chain
rpc QueryValidatorConsumerCommissionRate(
QueryValidatorConsumerCommissionRateRequest)
returns (QueryValidatorConsumerCommissionRateResponse) {
option (google.api.http).get =
"/interchain_security/ccv/provider/consumer_commission_rate";
}
}

message QueryConsumerGenesisRequest { string chain_id = 1; }
Expand Down Expand Up @@ -127,6 +155,8 @@ message QueryConsumerChainStopProposalsResponse {
message Chain {
string chain_id = 1;
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;
}

message QueryValidatorConsumerAddrRequest {
Expand Down Expand Up @@ -212,3 +242,37 @@ message QueryParamsResponse {
Params params = 1 [(gogoproto.nullable) = false];
}


message QueryConsumerChainOptedInValidatorsRequest {
string chain_id = 1;
}

message QueryConsumerChainOptedInValidatorsResponse {
// The consensus addresses of the validators on the provider chain
repeated string validators_provider_addresses = 1;
}


message QueryConsumerChainsValidatorHasToValidateRequest {
// The consensus address of the validator on the provider chain
string provider_address = 1 [ (gogoproto.moretags) = "yaml:\"address\"" ];
}

message QueryConsumerChainsValidatorHasToValidateResponse {
repeated string consumer_chain_ids = 1;
}

message QueryValidatorConsumerCommissionRateRequest {
string chain_id = 1;
// The consensus address of the validator on the provider chain
string provider_address = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ];
}

message QueryValidatorConsumerCommissionRateResponse {
// The rate to charge delegators on the consumer chain, as a fraction
string rate = 1 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}
123 changes: 123 additions & 0 deletions x/ccv/provider/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func NewQueryCmd() *cobra.Command {
cmd.AddCommand(CmdProposedConsumerChains())
cmd.AddCommand(CmdAllPairsValConAddrByConsumerChainID())
cmd.AddCommand(CmdProviderParameters())
cmd.AddCommand(CmdConsumerChainOptedInValidators())
cmd.AddCommand(CmdConsumerChainsValidatorHasToValidate())
cmd.AddCommand(CmdValidatorConsumerCommissionRate())
return cmd
}

Expand Down Expand Up @@ -409,3 +412,123 @@ $ %s query provider params

return cmd
}

// Command to query opted-in validators by consumer chain ID
func CmdConsumerChainOptedInValidators() *cobra.Command {
cmd := &cobra.Command{
Use: "consumer-opted-in-validators [chainid]",
Short: "Query opted-in validators for a given consumer chain",
Long: strings.TrimSpace(
fmt.Sprintf(`Query opted-in validators for a given consumer chain.
Example:
$ %s consumer-opted-in-validators foochain
`, version.AppName),
),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

res, err := queryClient.QueryConsumerChainOptedInValidators(cmd.Context(),
&types.QueryConsumerChainOptedInValidatorsRequest{ChainId: args[0]})
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

// Command to query the consumer chains list a given validator has to validate
func CmdConsumerChainsValidatorHasToValidate() *cobra.Command {
bech32PrefixConsAddr := sdk.GetConfig().GetBech32ConsensusAddrPrefix()
cmd := &cobra.Command{
Use: "has-to-validate [provider-validator-address]",
Short: "Query the consumer chains list a given validator has to validate",
Long: strings.TrimSpace(
fmt.Sprintf(`the list of consumer chains that as a validator, you need to be running right now, is always a subset of this, so it seems like a very nice "safe bet".
Example:
$ %s has-to-validate %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj
`, version.AppName, bech32PrefixConsAddr),
),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

addr, err := sdk.ConsAddressFromBech32(args[0])
if err != nil {
return err
}

res, err := queryClient.QueryConsumerChainsValidatorHasToValidate(cmd.Context(),
&types.QueryConsumerChainsValidatorHasToValidateRequest{
ProviderAddress: addr.String(),
})
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

// Command to query the consumer commission rate a validator charges
// on a consumer chain
func CmdValidatorConsumerCommissionRate() *cobra.Command {
bech32PrefixConsAddr := sdk.GetConfig().GetBech32ConsensusAddrPrefix()
cmd := &cobra.Command{
Use: "validator-consumer-commission-rate [chainid] [provider-validator-address]",
Short: "Query the consumer commission rate a validator charges on a consumer chain",
Long: strings.TrimSpace(
fmt.Sprintf(`Query the consumer commission rate a validator charges on a consumer chain.
Example:
$ %s validator-consumer-commission-rate foochain %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj
`, version.AppName, bech32PrefixConsAddr),
),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

addr, err := sdk.ConsAddressFromBech32(args[1])
if err != nil {
return err
}

res, err := queryClient.QueryValidatorConsumerCommissionRate(cmd.Context(),
&types.QueryValidatorConsumerCommissionRateRequest{
ChainId: args[0],
ProviderAddress: addr.String(),
})
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
120 changes: 119 additions & 1 deletion x/ccv/provider/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"
"fmt"

"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand Down Expand Up @@ -42,7 +43,6 @@ func (k Keeper) QueryConsumerChains(goCtx context.Context, req *types.QueryConsu

ctx := sdk.UnwrapSDKContext(goCtx)

// convert to array of pointers
chains := []*types.Chain{}
for _, chain := range k.GetAllConsumerChains(ctx) {
// prevent implicit memory aliasing
Expand Down Expand Up @@ -221,3 +221,121 @@ func (k Keeper) QueryParams(c context.Context, _ *types.QueryParamsRequest) (*ty

return &types.QueryParamsResponse{Params: params}, nil
}

// QueryConsumerChainOptedInValidators returns all validators that opted-in to a given consumer chain
func (k Keeper) QueryConsumerChainOptedInValidators(goCtx context.Context, req *types.QueryConsumerChainOptedInValidatorsRequest) (*types.QueryConsumerChainOptedInValidatorsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

consumerChainID := req.ChainId
if consumerChainID == "" {
return nil, status.Error(codes.InvalidArgument, "empty chainId")
}

optedInVals := []string{}
ctx := sdk.UnwrapSDKContext(goCtx)

if !k.IsConsumerProposedOrRegistered(ctx, consumerChainID) {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("unknown consumer chain: %s", consumerChainID))
}

for _, v := range k.GetAllOptedIn(ctx, ctx.ChainID()) {
optedInVals = append(optedInVals, v.ToSdkConsAddr().String())
}

return &types.QueryConsumerChainOptedInValidatorsResponse{
ValidatorsProviderAddresses: optedInVals,
}, nil
}

// QueryConsumerChainsValidatorHasToValidate returns all consumer chains that the given validator has to validate now
// or in the next epoch if nothing changes.
func (k Keeper) QueryConsumerChainsValidatorHasToValidate(goCtx context.Context, req *types.QueryConsumerChainsValidatorHasToValidateRequest) (*types.QueryConsumerChainsValidatorHasToValidateResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

if req.ProviderAddress == "" {
return nil, status.Error(codes.InvalidArgument, "empty provider address")
}

consAddr, err := sdk.ConsAddressFromBech32(req.ProviderAddress)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "invalid provider address")
}

ctx := sdk.UnwrapSDKContext(goCtx)

// get all the consumer chains for which the validator is either already
// opted-in, currently a consumer validator or if its voting power is within the TopN validators
consumersToValidate := []string{}
for _, consumer := range k.GetAllConsumerChains(ctx) {
chainID := consumer.ChainId
provAddr := types.NewProviderConsAddress(consAddr)
if !k.IsOptedIn(ctx, chainID, provAddr) && !k.IsConsumerValidator(ctx, chainID, provAddr) {
// check that the validator voting power isn't in the TopN
if topN, found := k.GetTopN(ctx, chainID); found && topN > 0 {
val, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
if !found {
return nil, status.Error(codes.InvalidArgument, "invalid provider address")
}
power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator())
minPowerToOptIn := k.ComputeMinPowerToOptIn(ctx, chainID, k.stakingKeeper.GetLastValidators(ctx), topN)

// Check if the validator's voting power is smaller
// than the minimum and hence not automatically opted in
if power < minPowerToOptIn {
continue
}
}
}

consumersToValidate = append(consumersToValidate, chainID)
}

return &types.QueryConsumerChainsValidatorHasToValidateResponse{
ConsumerChainIds: consumersToValidate,
}, nil
}

// QueryValidatorConsumerCommissionRate returns the commission rate a given
// validator charges on a given consumer chain
func (k Keeper) QueryValidatorConsumerCommissionRate(goCtx context.Context, req *types.QueryValidatorConsumerCommissionRateRequest) (*types.QueryValidatorConsumerCommissionRateResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

consumerChainID := req.ChainId
if consumerChainID == "" {
return nil, status.Error(codes.InvalidArgument, "empty chainId")
}

consAddr, err := sdk.ConsAddressFromBech32(req.ProviderAddress)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "invalid provider address")
}

ctx := sdk.UnwrapSDKContext(goCtx)

if !k.IsConsumerProposedOrRegistered(ctx, consumerChainID) {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("unknown consumer chain: %s", consumerChainID))
}

res := &types.QueryValidatorConsumerCommissionRateResponse{}

// Check if the validator has a commission rate set for the consumer chain,
// otherwise use the commission rate from the validator staking module struct
consumerRate, found := k.GetConsumerCommissionRate(ctx, consumerChainID, types.NewProviderConsAddress(consAddr))
if found {
res.Rate = consumerRate
} else {
v, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
if !ok {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("unknown validator: %s", consAddr.String()))
}
res.Rate = v.Commission.Rate
}

return res, nil
}
8 changes: 7 additions & 1 deletion x/ccv/provider/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,15 @@ func (k Keeper) GetAllConsumerChains(ctx sdk.Context) (chains []types.Chain) {
chainID := string(iterator.Key()[1:])
clientID := string(iterator.Value())

// Get the consumer TopN value by checking
// if the chain is a TopN chain,
// otherwise it's a OptIn chain
topN, _ := k.GetTopN(ctx, chainID)

chains = append(chains, types.Chain{
ChainId: chainID,
ClientId: clientID,
Top_N: topN,
})
}

Expand Down Expand Up @@ -1296,4 +1302,4 @@ func (k Keeper) DeleteConsumerCommissionRate(
) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.ConsumerCommissionRateKey(chainID, providerAddr))
}
}
Loading

0 comments on commit bfe10df

Please sign in to comment.