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!: introduce MsgOptIn and MsgOptOut #1616

Closed
wants to merge 15 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Enable Opt In and Top N chains through gov proposals.
([\#1587](https://github.com/cosmos/interchain-security/pull/1587))
5 changes: 5 additions & 0 deletions proto/interchain_security/ccv/provider/v1/provider.proto
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ message ConsumerAdditionProposal {
// chain. it is most relevant for chains performing a sovereign to consumer
// changeover in order to maintain the existing ibc transfer channel
string distribution_transmission_channel = 14;
// Corresponds to the percentage of validators that have to validate the chain under the Top N case.
// For example, 53 corresponds to a Top 53% chain, meaning that the top 53% provider validators by voting power
// have to validate the proposed consumer chain. top_N can either be 0 or any value in [50, 100].
// A chain can join with top_N == 0 as an Opt In chain, or with top_N ∈ [50, 100] as a Top N chain.
uint32 top_N = 15;
}

// ConsumerRemovalProposal is a governance proposal on the provider chain to
Expand Down
27 changes: 27 additions & 0 deletions proto/interchain_security/ccv/provider/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ service Msg {
rpc AssignConsumerKey(MsgAssignConsumerKey) returns (MsgAssignConsumerKeyResponse);
rpc SubmitConsumerMisbehaviour(MsgSubmitConsumerMisbehaviour) returns (MsgSubmitConsumerMisbehaviourResponse);
rpc SubmitConsumerDoubleVoting(MsgSubmitConsumerDoubleVoting) returns (MsgSubmitConsumerDoubleVotingResponse);
rpc OptIn(MsgOptIn) returns (MsgOptInResponse);
rpc OptOut(MsgOptOut) returns (MsgOptOutResponse);
}

message MsgAssignConsumerKey {
Expand Down Expand Up @@ -61,3 +63,28 @@ message MsgSubmitConsumerDoubleVoting {
}

message MsgSubmitConsumerDoubleVotingResponse {}

message MsgOptIn {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
// the chain id of the consumer chain to opt in to
string chain_id = 1;
// the validator address on the provider
string provider_addr = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ];
// the consensus public key to use on the consumer in json string format corresponding to proto-any,
// for example `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}`
optional string consumerKey = 3;
}

message MsgOptInResponse {}

message MsgOptOut {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
// the chain id of the consumer chain to opt out from
string chain_id = 1;
// the validator address on the provider
string provider_addr = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ];
}

message MsgOptOutResponse {}
1 change: 1 addition & 0 deletions testutil/keeper/unit_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ func GetTestConsumerAdditionProp() *providertypes.ConsumerAdditionProposal {
types.DefaultCCVTimeoutPeriod,
types.DefaultTransferTimeoutPeriod,
types.DefaultConsumerUnbondingPeriod,
0,
).(*providertypes.ConsumerAdditionProposal)

return prop
Expand Down
87 changes: 87 additions & 0 deletions x/ccv/provider/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func GetTxCmd() *cobra.Command {
cmd.AddCommand(NewAssignConsumerKeyCmd())
cmd.AddCommand(NewSubmitConsumerMisbehaviourCmd())
cmd.AddCommand(NewSubmitConsumerDoubleVotingCmd())
cmd.AddCommand(NewOptInCmd())
cmd.AddCommand(NewOptOutCmd())

return cmd
}
Expand Down Expand Up @@ -202,3 +204,88 @@ Example:

return cmd
}

func NewOptInCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "opt-in [consumer-chain-id] [consumer-pubkey]",
Short: "opts in validator to the consumer chain, and if given uses the " +
"provided consensus public key for this consumer chain",
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags())
if err != nil {
return err
}
txf = txf.WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever)

providerValAddr := clientCtx.GetFromAddress()

var consumerPubKey types.MsgOptIn_ConsumerKey
if len(args) == 2 {
// consumer public key was provided
consumerPubKey = types.MsgOptIn_ConsumerKey{ConsumerKey: args[1]}
} else {
consumerPubKey = types.MsgOptIn_ConsumerKey{}
}
msg, err := types.NewMsgOptIn(args[0], sdk.ValAddress(providerValAddr), consumerPubKey)

if err != nil {
return err
}
if err := msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg)
},
}

flags.AddTxFlagsToCmd(cmd)

_ = cmd.MarkFlagRequired(flags.FlagFrom)

return cmd
}

func NewOptOutCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "opt-out [consumer-chain-id]",
Short: "opts out validator from this consumer chain",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags())
if err != nil {
return err
}
txf = txf.WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever)

providerValAddr := clientCtx.GetFromAddress()

msg, err := types.NewMsgOptOut(args[0], sdk.ValAddress(providerValAddr))
if err != nil {
return err
}
if err := msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg)
},
}

flags.AddTxFlagsToCmd(cmd)

_ = cmd.MarkFlagRequired(flags.FlagFrom)

return cmd
}
6 changes: 4 additions & 2 deletions x/ccv/provider/client/proposal_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ Where proposal.json contains:
"transfer_timeout_period": 3600000000000,
"ccv_timeout_period": 2419200000000000,
"unbonding_period": 1728000000000000,
"deposit": "10000stake"
"deposit": "10000stake",
"top_n": 0,
}
`,
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -86,7 +87,7 @@ Where proposal.json contains:
proposal.GenesisHash, proposal.BinaryHash, proposal.SpawnTime,
proposal.ConsumerRedistributionFraction, proposal.BlocksPerDistributionTransmission,
proposal.DistributionTransmissionChannel, proposal.HistoricalEntries,
proposal.CcvTimeoutPeriod, proposal.TransferTimeoutPeriod, proposal.UnbondingPeriod)
proposal.CcvTimeoutPeriod, proposal.TransferTimeoutPeriod, proposal.UnbondingPeriod, proposal.TopN)

from := clientCtx.GetFromAddress()

Expand Down Expand Up @@ -241,6 +242,7 @@ type ConsumerAdditionProposalJSON struct {
UnbondingPeriod time.Duration `json:"unbonding_period"`

Deposit string `json:"deposit"`
TopN uint32 `json:"top_N"`
}

type ConsumerAdditionProposalReq struct {
Expand Down
6 changes: 6 additions & 0 deletions x/ccv/provider/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ func NewHandler(k *keeper.Keeper) sdk.Handler {
case *types.MsgSubmitConsumerDoubleVoting:
res, err := msgServer.SubmitConsumerDoubleVoting(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgOptIn:
res, err := msgServer.OptIn(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgOptOut:
res, err := msgServer.OptOut(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
default:
return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg)
}
Expand Down
186 changes: 186 additions & 0 deletions x/ccv/provider/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1136,3 +1136,189 @@ func (k Keeper) GetAllRegisteredAndProposedChainIDs(ctx sdk.Context) []string {

return allConsumerChains
}

// SetTopN stores the N value associated to chain with `chainID`
func (k Keeper) SetTopN(
ctx sdk.Context,
chainID string,
N uint32,
) {
store := ctx.KVStore(k.storeKey)

buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, N)

store.Set(types.TopNKey(chainID), buf)
}

// DeleteTopN removes the N value associated to chain with `chainID`
func (k Keeper) DeleteTopN(
ctx sdk.Context,
chainID string,
) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.TopNKey(chainID))
}

// GetTopN returns (N, true) if chain `chainID` has a top N associated, and (0, false) otherwise.
func (k Keeper) GetTopN(
ctx sdk.Context,
chainID string,
) (uint32, bool) {
store := ctx.KVStore(k.storeKey)
buf := store.Get(types.TopNKey(chainID))
if buf == nil {
return 0, false
}
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`)
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`)
func (k Keeper) IsOptIn(ctx sdk.Context, chainID string) bool {
topN, found := k.GetTopN(ctx, chainID)
return !found || topN == 0
}
func (k Keeper) SetOptedIn(
ctx sdk.Context,
chainID string,
providerAddr types.ProviderConsAddress,
blockHeight uint64,
) {
store := ctx.KVStore(k.storeKey)

// validator is considered opted in
blockHeightBytes := make([]byte, 8)
binary.BigEndian.PutUint64(blockHeightBytes, blockHeight)

store.Set(types.OptedInKey(chainID, providerAddr), blockHeightBytes)
}

func (k Keeper) DeleteOptedIn(
ctx sdk.Context,
chainID string,
providerAddr types.ProviderConsAddress,
) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.OptedInKey(chainID, providerAddr))
}

func (k Keeper) IsOptedIn(
ctx sdk.Context,
chainID string,
providerAddr types.ProviderConsAddress,
) bool {
store := ctx.KVStore(k.storeKey)
return store.Get(types.OptedInKey(chainID, providerAddr)) != nil
}

func (k Keeper) GetOptedIn(
ctx sdk.Context,
chainID string) (optedInValidators []OptedInValidator) {
store := ctx.KVStore(k.storeKey)
key := types.ChainIdWithLenKey(types.OptedInBytePrefix, chainID)
iterator := sdk.KVStorePrefixIterator(store, key)
defer iterator.Close()

for ; iterator.Valid(); iterator.Next() {
optedInValidators = append(optedInValidators, OptedInValidator{
ProviderAddr: types.NewProviderConsAddress(iterator.Key()[len(key):]),
BlockHeight: binary.BigEndian.Uint64(iterator.Value()),
})
}

return optedInValidators
}

func (k Keeper) SetToBeOptedIn(
ctx sdk.Context,
chainID string,
providerAddr types.ProviderConsAddress,
) {
store := ctx.KVStore(k.storeKey)
store.Set(types.ToBeOptedInKey(chainID, providerAddr), []byte{})
}

func (k Keeper) DeleteToBeOptedIn(
ctx sdk.Context,
chainID string,
providerAddr types.ProviderConsAddress,
) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.ToBeOptedInKey(chainID, providerAddr))
}

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
}

func (k Keeper) GetToBeOptedIn(
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))
}

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
}

func (k Keeper) GetToBeOptedOut(
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
}
Loading