From fde165e159fe6d2ed53db0834e2e279404e89139 Mon Sep 17 00:00:00 2001 From: insumity Date: Thu, 1 Feb 2024 14:24:08 +0100 Subject: [PATCH] store opted in and opted out validators --- x/ccv/provider/client/cli/tx.go | 87 +++++++++++ x/ccv/provider/keeper/keeper.go | 143 ++++++++++++++++++ x/ccv/provider/keeper/msg_server.go | 63 +++++--- x/ccv/provider/keeper/partial_set_security.go | 38 ++++- x/ccv/provider/types/keys.go | 29 ++++ x/ccv/provider/types/keys_test.go | 1 + x/ccv/provider/types/msg.go | 36 +++-- x/ccv/types/events.go | 2 + 8 files changed, 359 insertions(+), 40 deletions(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 379e55a792..6a6da64387 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -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 } @@ -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 +} diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 510d957c5d..df27307146 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -1136,3 +1136,146 @@ func (k Keeper) GetAllRegisteredAndProposedChainIDs(ctx sdk.Context) []string { return allConsumerChains } + +func (k Keeper) SetOptedIn( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, + blockHeight uint64, +) { + store := ctx.KVStore(k.storeKey) + + // validator is considered opted in + isOptedIn := []byte{1} + blockHeightBytes := make([]byte, 8) + binary.BigEndian.PutUint64(blockHeightBytes, blockHeight) + toStore := append(isOptedIn, blockHeightBytes...) + + store.Set(types.OptedInKey(chainID, providerAddr), toStore) +} + +func (k Keeper) RemoveOptedIn( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + buf := store.Get(types.OptedInKey(chainID, providerAddr)) + + buf[0] = 0 // consider it not opted in anymore + store.Set(types.OptedInKey(chainID, providerAddr), buf) +} + +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) 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) (addresses []types.ProviderConsAddress) { + + store := ctx.KVStore(k.storeKey) + key := types.ChainIdWithLenKey(types.OptedInBytePrefix, 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) 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) RemoveToBeOptedIn( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.ToBeOptedOutKey(chainID, providerAddr)) +} + +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) 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 +} + +func (k Keeper) RemoveToBeOptedOut( + ctx sdk.Context, + chainID string, + providerAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.ToBeOptedOutKey(chainID, providerAddr)) +} diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index 30da045a8b..0b61d56a9b 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -177,36 +177,63 @@ func (k msgServer) SubmitConsumerDoubleVoting(goCtx context.Context, msg *types. func (k msgServer) OptIn(goCtx context.Context, msg *types.MsgOptIn) (*types.MsgOptInResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - if err := k.Keeper.HandleOptIn(ctx, *msg); err != nil { + + valAddress, err := sdk.ConsAddressFromBech32(msg.ProviderAddr) + if err != nil { + return nil, err + } + providerAddr := types.NewProviderConsAddress(valAddress) + if err != nil { return nil, err } - // add some outpout - //ctx.EventManager().EmitEvents(sdk.Events{ - // sdk.NewEvent( - // ccvtypes.EventTypeSubmitConsumerMisbehaviour, - // sdk.NewAttribute(ccvtypes.AttributeConsumerMisbehaviour, msg.Misbehaviour.String()), - // - // ), - //}) + // todo also send down the consumer key ...??? + if err := k.Keeper.HandleOptIn(ctx, msg.ChainId, providerAddr); err != nil { + return nil, err + } + + if msg.GetConsumerKey() != "" { // FIXME: there should be a nicer way to do this + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + ccvtypes.EventTypeOptIn, + sdk.NewAttribute(types.AttributeProviderValidatorAddress, msg.ProviderAddr), + sdk.NewAttribute(types.AttributeConsumerConsensusPubKey, msg.GetConsumerKey()), + ), + }) + } else { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + ccvtypes.EventTypeOptIn, + sdk.NewAttribute(types.AttributeProviderValidatorAddress, msg.ProviderAddr), + ), + }) + } return &types.MsgOptInResponse{}, nil } func (k msgServer) OptOut(goCtx context.Context, msg *types.MsgOptOut) (*types.MsgOptOutResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - if err := k.Keeper.HandleOptOut(ctx, *msg); err != nil { + + valAddress, err := sdk.ConsAddressFromBech32(msg.ProviderAddr) + if err != nil { + return nil, err + } + providerAddr := types.NewProviderConsAddress(valAddress) + if err != nil { + return nil, err + } + + if err := k.Keeper.HandleOptOut(ctx, msg.ChainId, providerAddr); err != nil { return nil, err } - // add some outpout - //ctx.EventManager().EmitEvents(sdk.Events{ - // sdk.NewEvent( - // ccvtypes.EventTypeSubmitConsumerMisbehaviour, - // sdk.NewAttribute(ccvtypes.AttributeConsumerMisbehaviour, msg.Misbehaviour.String()), - // - // ), - //}) + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + ccvtypes.EventTypeOptOut, + sdk.NewAttribute(types.AttributeProviderValidatorAddress, msg.ProviderAddr), + ), + }) return &types.MsgOptOutResponse{}, nil } diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go index c5c34df8bf..264028216e 100644 --- a/x/ccv/provider/keeper/partial_set_security.go +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -1,22 +1,48 @@ package keeper import ( + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" ) -// HandleOptIn TODO -func (k Keeper) HandleOptIn(ctx sdk.Context, msg types.MsgOptIn) error { +// TODO: HandleOptIn +func (k Keeper) HandleOptIn(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress) error { logger := k.Logger(ctx) - logger.Info("something ..") + + if k.IsToBeOptedOut(ctx, chainID, providerAddr) { + k.RemoveToBeOptedOut(ctx, chainID, providerAddr) + return nil + } else if k.IsToBeOptedIn(ctx, chainID, providerAddr) { + return nil + } else if k.IsOptedIn(ctx, chainID, providerAddr) { + logger.Error("That's a big no") + return fmt.Errorf("What's happening? already opted in") + } else { + k.SetToBeOptedIn(ctx, chainID, providerAddr) + } + + //if msg.GetConsumerKey() != "" { + // k.AssignConsumerKey(ctx, msg.ChainId) + //} return nil } -// HandleOptOut TODO -func (k Keeper) HandleOptOut(ctx sdk.Context, msg types.MsgOptOut) error { +func (k Keeper) HandleOptOut(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress) error { logger := k.Logger(ctx) - logger.Info("something ..") + + if k.IsToBeOptedIn(ctx, chainID, providerAddr) { + k.RemoveToBeOptedIn(ctx, chainID, providerAddr) + return nil + } else if k.IsToBeOptedOut(ctx, chainID, providerAddr) { + return nil + } else if !k.IsOptedIn(ctx, chainID, providerAddr) { + logger.Error("that's a no!") + return fmt.Errorf("What's happening? already opted in") + } else { + k.SetToBeOptedOut(ctx, chainID, providerAddr) + } return nil } diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 615b901368..c63223d78a 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -145,6 +145,17 @@ const ( // ProposedConsumerChainByteKey is the byte prefix storing the consumer chainId in consumerAddition gov proposal submitted before voting finishes ProposedConsumerChainByteKey + // OptedInBytePrefix is the byte prefix used when storing for each consumer chain all the opted in validators + OptedInBytePrefix = 31 + + // ToBeOptedInBytePrefix is the byte prefix used when storing for each consumer chain the validators that + // are about to be opted in + ToBeOptedInBytePrefix = 32 + + // ToBeOptedOutBytePrefix is the byte prefix used when storing for each consumer chain the validators that + // are about to be opted out + ToBeOptedOutBytePrefix = 33 + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -517,6 +528,24 @@ func ParseProposedConsumerChainKey(prefix byte, bz []byte) (uint64, error) { return proposalID, nil } +// OptedInKey returns the key of consumer chain `chainID` and validator with `providerAddr` +func OptedInKey(chainID string, providerAddr ProviderConsAddress) []byte { + prefix := ChainIdWithLenKey(OptedInBytePrefix, chainID) + return append(prefix, providerAddr.ToSdkConsAddr().Bytes()...) +} + +// ToBeOptedInKey returns the key of consumer chain `chainID` and validator with `providerAddr` +func ToBeOptedInKey(chainID string, providerAddr ProviderConsAddress) []byte { + prefix := ChainIdWithLenKey(ToBeOptedInBytePrefix, chainID) + return append(prefix, providerAddr.ToSdkConsAddr().Bytes()...) +} + +// ToBeOptedOutKey returns the key of consumer chain `chainID` and validator with `providerAddr` +func ToBeOptedOutKey(chainID string, providerAddr ProviderConsAddress) []byte { + prefix := ChainIdWithLenKey(ToBeOptedOutBytePrefix, chainID) + return append(prefix, providerAddr.ToSdkConsAddr().Bytes()...) +} + // // End of generic helpers section // diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index 4d5ea58ff8..447ae04d28 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -56,6 +56,7 @@ func getAllKeyPrefixes() []byte { providertypes.VSCMaturedHandledThisBlockBytePrefix, providertypes.EquivocationEvidenceMinHeightBytePrefix, providertypes.ProposedConsumerChainByteKey, + providertypes.OptedInBytePrefix, } } diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 87ce53ba33..601a4a82f0 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -209,26 +209,19 @@ func (msg MsgSubmitConsumerDoubleVoting) GetSigners() []sdk.AccAddress { } // NewMsgOptIn creates a new NewMsgOptIn instance. -func NewMsgOptIn(chainID string, providerValidatorAddress sdk.ValAddress) (*MsgOptIn, error) { +func NewMsgOptIn(chainID string, providerValidatorAddress sdk.ValAddress, consumerConsensusPubKey MsgOptIn_ConsumerKey) (*MsgOptIn, error) { return &MsgOptIn{ ChainId: chainID, ProviderAddr: providerValidatorAddress.String(), + XConsumerKey: &consumerConsensusPubKey, }, nil } // Route implements the sdk.Msg interface. func (msg MsgOptIn) Route() string { return RouterKey } -// Type implements the sdk.Msg interface. NOT REALLY -//func (msg MsgOptIn) Type() string { -// return TypeMsgOptIn -//} - // GetSigners implements the sdk.Msg interface. It returns the address(es) that // must sign over msg.GetSignBytes(). -// TODO -// If the validator address is not same as delegator's, then the validator must -// sign the msg as well. func (msg MsgOptIn) GetSigners() []sdk.AccAddress { valAddr, err := sdk.ValAddressFromBech32(msg.ProviderAddr) if err != nil { @@ -259,6 +252,15 @@ func (msg MsgOptIn) ValidateBasic() error { if err != nil { return ErrInvalidProviderAddress } + + consumerKey := msg.GetConsumerKey() + if consumerKey != "" { + // only parse if a non-empty key was provided + // FIXME: I fee there's should be a nicer way to do this ... + if _, _, err := ParseConsumerKeyFromJson(msg.GetConsumerKey()); err != nil { + return ErrInvalidConsumerConsensusPubKey + } + } return nil } @@ -273,16 +275,13 @@ func NewMsgOptOut(chainID string, providerValidatorAddress sdk.ValAddress) (*Msg // Route implements the sdk.Msg interface. func (msg MsgOptOut) Route() string { return RouterKey } -// Type implements the sdk.Msg interface. NOT REALLY -//func (msg MsgOptIn) Type() string { -// return TypeMsgOptIn -//} +// Type implements the sdk.Msg interface. +func (msg MsgOptIn) Type() string { + return TypeMsgOptIn +} // GetSigners implements the sdk.Msg interface. It returns the address(es) that // must sign over msg.GetSignBytes(). -// TODO -// If the validator address is not same as delegator's, then the validator must -// sign the msg as well. func (msg MsgOptOut) GetSigners() []sdk.AccAddress { valAddr, err := sdk.ValAddressFromBech32(msg.ProviderAddr) if err != nil { @@ -315,3 +314,8 @@ func (msg MsgOptOut) ValidateBasic() error { } return nil } + +// Type implements the sdk.Msg interface. +func (msg MsgOptOut) Type() string { + return TypeMsgOptOut +} diff --git a/x/ccv/types/events.go b/x/ccv/types/events.go index 4c597ded56..2fa584dd40 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -12,6 +12,8 @@ const ( EventTypeRemoveConsumerRewardDenom = "remove_consumer_reward_denom" EventTypeSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" EventTypeSubmitConsumerDoubleVoting = "submit_consumer_double_voting" + EventTypeOptIn = "opt_in" + EventTypeOptOut = "opt_out" EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash" EventTypeFeeDistribution = "fee_distribution" EventTypeConsumerSlashRequest = "consumer_slash_request"