diff --git a/x/distribution/keeper/hooks.go b/x/distribution/keeper/hooks.go index 4b144966f1..57705fa8c8 100644 --- a/x/distribution/keeper/hooks.go +++ b/x/distribution/keeper/hooks.go @@ -2,6 +2,7 @@ package keeper import ( sdk "github.com/okex/exchain/libs/cosmos-sdk/types" + "github.com/okex/exchain/libs/tendermint/crypto" "github.com/okex/exchain/x/distribution/types" stakingtypes "github.com/okex/exchain/x/staking/types" @@ -23,6 +24,10 @@ func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { h.k.initializeValidator(ctx, val) } +func (h Hooks) AfterValidatorPubkeyChanged(ctx sdk.Context, oldAddress sdk.ConsAddress, newAddress sdk.ConsAddress, newPubkey crypto.PubKey) { + +} + // AfterValidatorRemoved cleans up for after validator is removed func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { if h.k.CheckDistributionProposalValid(ctx) { diff --git a/x/slashing/internal/keeper/hooks.go b/x/slashing/internal/keeper/hooks.go index fbbe6f3b37..289db2af3b 100644 --- a/x/slashing/internal/keeper/hooks.go +++ b/x/slashing/internal/keeper/hooks.go @@ -40,6 +40,17 @@ func (k Keeper) AfterValidatorRemoved(ctx sdk.Context, address sdk.ConsAddress) k.modifyValidatorStatus(ctx, address, types.Destroyed) } +func (k Keeper) AfterValidatorPubkeyChanged(ctx sdk.Context, oldAddress sdk.ConsAddress, newAddress sdk.ConsAddress, newPubkey crypto.PubKey) { + k.deleteAddrPubkeyRelation(ctx, crypto.Address(oldAddress)) + signingInfo, found := k.GetValidatorSigningInfo(ctx, oldAddress) + k.modifyValidatorStatus(ctx, oldAddress, types.Destroyed) + + k.AddPubkey(ctx, newPubkey) + if found { + k.SetValidatorSigningInfo(ctx, newAddress, signingInfo) + } +} + func (k Keeper) AfterValidatorDestroyed(ctx sdk.Context, valAddr sdk.ValAddress) { validator := k.sk.Validator(ctx, valAddr) if validator != nil { @@ -76,6 +87,9 @@ func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { h.k.AfterValidatorCreated(ctx, valAddr) } +func (h Hooks) AfterValidatorPubkeyChanged(ctx sdk.Context, oldAddress sdk.ConsAddress, newAddress sdk.ConsAddress, newPubkey crypto.PubKey) { + h.k.AfterValidatorPubkeyChanged(ctx, oldAddress, newAddress, newPubkey) +} func (h Hooks) AfterValidatorDestroyed(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { h.k.AfterValidatorDestroyed(ctx, valAddr) } diff --git a/x/slashing/internal/keeper/infractions.go b/x/slashing/internal/keeper/infractions.go index 52cd6c4ed2..3fdb565a16 100644 --- a/x/slashing/internal/keeper/infractions.go +++ b/x/slashing/internal/keeper/infractions.go @@ -17,7 +17,8 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // fetch the validator public key consAddr := sdk.ConsAddress(addr) if _, err := k.GetPubkey(ctx, addr); err != nil { - panic(fmt.Sprintf("Validator consensus-address %s not found", consAddr)) + // ignore this panic, now we can change the validator's pub key + //panic(fmt.Sprintf("Validator consensus-address %s not found", consAddr)) } // fetch signing info diff --git a/x/staking/alias.go b/x/staking/alias.go index 8d1e9bf0ff..d7480ed93c 100644 --- a/x/staking/alias.go +++ b/x/staking/alias.go @@ -31,6 +31,7 @@ var ( RegisterCodec = types.RegisterCodec NewCommission = types.NewCommission ErrNoValidatorFound = types.ErrNoValidatorFound + ErrPubkeyEqual = types.ErrPubkeyEqual ErrValidatorOwnerExists = types.ErrValidatorOwnerExists ErrValidatorPubKeyExists = types.ErrValidatorPubKeyExists ErrValidatorPubKeyTypeNotSupported = types.ErrValidatorPubKeyTypeNotSupported diff --git a/x/staking/client/cli/tx.go b/x/staking/client/cli/tx.go index 10fd940bd5..f3fe5c5cf1 100644 --- a/x/staking/client/cli/tx.go +++ b/x/staking/client/cli/tx.go @@ -106,6 +106,16 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { Details: viper.GetString(FlagDetails), } + pkStr := viper.GetString(FlagPubKey) + var pk crypto.PubKey = nil + var err error + if pkStr != "" { + pk, err = types.GetConsPubKeyBech32(pkStr) + if err != nil { + return err + } + } + // TODO: recover the msd modification later //var newMinSelfDelegation *sdk.Int // @@ -122,7 +132,7 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { //} // //msg := types.NewMsgEditValidator(sdk.ValAddress(valAddr), description, newRate, newMinSelfDelegation) - msg := types.NewMsgEditValidator(sdk.ValAddress(valAddr), description) + msg := types.NewMsgEditValidator(sdk.ValAddress(valAddr), description, pk) // build and sign the transaction, then broadcast to Tendermint return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) @@ -130,6 +140,7 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { } cmd.Flags().AddFlagSet(fsDescriptionEdit) + cmd.Flags().String(FlagPubKey, "", "The Bech32 encoded PubKey of the validator") //cmd.Flags().AddFlagSet(fsCommissionUpdate) return cmd @@ -138,11 +149,11 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { //__________________________________________________________ var ( -//defaultTokens = sdk.TokensFromConsensusPower(100) -//defaultAmount = defaultTokens.String() + sdk.DefaultBondDenom -//defaultCommissionRate = "0.1" -//defaultCommissionMaxRate = "0.2" -//defaultCommissionMaxChangeRate = "0.01" +// defaultTokens = sdk.TokensFromConsensusPower(100) +// defaultAmount = defaultTokens.String() + sdk.DefaultBondDenom +// defaultCommissionRate = "0.1" +// defaultCommissionMaxRate = "0.2" +// defaultCommissionMaxChangeRate = "0.01" ) // CreateValidatorMsgHelpers returns the flagset, particular flags, and a description of defaults diff --git a/x/staking/handler.go b/x/staking/handler.go index 8ef4229f97..d2a49309a7 100644 --- a/x/staking/handler.go +++ b/x/staking/handler.go @@ -165,15 +165,30 @@ func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keepe return nil, ErrNoValidatorFound(msg.ValidatorAddress.String()) } - // replace all editable fields (clients should autofill existing values) - description, err := validator.Description.UpdateDescription(msg.Description) - if err != nil { - return nil, err - } - - validator.Description = description + if msg.Description != (Description{}) { + // replace all editable fields (clients should autofill existing values) + description, err := validator.Description.UpdateDescription(msg.Description) + if err != nil { + return nil, err + } - k.SetValidator(ctx, validator) + validator.Description = description + k.SetValidator(ctx, validator) + } + if msg.PubKey != nil && len(msg.PubKey.Bytes()) != 0 { + if validator.ConsPubKey.Equals(msg.PubKey) { + return nil, ErrPubkeyEqual(msg.PubKey.Address().String()) + } + k.SetChangePubkey(ctx, validator.OperatorAddress, validator.GetConsPubKey()) + oldConsAddr := validator.GetConsAddr() + + validator.ConsPubKey = msg.PubKey + newConsAddr := validator.GetConsAddr() + k.SetValidator(ctx, validator) + k.SetValidatorByConsAddr(ctx, validator) + k.DeleteValidatorByConsAddr(ctx, oldConsAddr) + k.AfterValidatorPubkeyChanged(ctx, oldConsAddr, newConsAddr, msg.PubKey) + } ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent(types.EventTypeEditValidator, diff --git a/x/staking/keeper/hooks.go b/x/staking/keeper/hooks.go index 55a157ccdd..d379b79288 100644 --- a/x/staking/keeper/hooks.go +++ b/x/staking/keeper/hooks.go @@ -2,6 +2,7 @@ package keeper import ( sdk "github.com/okex/exchain/libs/cosmos-sdk/types" + "github.com/okex/exchain/libs/tendermint/crypto" "github.com/okex/exchain/x/staking/types" ) @@ -15,6 +16,12 @@ func (k Keeper) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { } } +func (k Keeper) AfterValidatorPubkeyChanged(ctx sdk.Context, oldAddress sdk.ConsAddress, newAddress sdk.ConsAddress, newPubkey crypto.PubKey) { + if k.hooks != nil { + k.hooks.AfterValidatorPubkeyChanged(ctx, oldAddress, newAddress, newPubkey) + } +} + // BeforeValidatorModified - call hook if registered func (k Keeper) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { if k.hooks != nil { diff --git a/x/staking/keeper/test_common.go b/x/staking/keeper/test_common.go index be234bbaaa..03123ab3de 100644 --- a/x/staking/keeper/test_common.go +++ b/x/staking/keeper/test_common.go @@ -315,8 +315,10 @@ func SimpleCheckValidator(t *testing.T, ctx sdk.Context, stkKeeper Keeper, vaAdd // mockDistributionKeeper is supported to test Hooks type mockDistributionKeeper struct{} -func (dk mockDistributionKeeper) Hooks() types.StakingHooks { return dk } -func (dk mockDistributionKeeper) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) {} +func (dk mockDistributionKeeper) Hooks() types.StakingHooks { return dk } +func (dk mockDistributionKeeper) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) {} +func (dk mockDistributionKeeper) AfterValidatorPubkeyChanged(ctx sdk.Context, oldAddress sdk.ConsAddress, newAddress sdk.ConsAddress, newPubkey crypto.PubKey) { +} func (dk mockDistributionKeeper) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) {} func (dk mockDistributionKeeper) AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { } diff --git a/x/staking/keeper/val_state_change.go b/x/staking/keeper/val_state_change.go index 337e919429..5f9ae391b1 100644 --- a/x/staking/keeper/val_state_change.go +++ b/x/staking/keeper/val_state_change.go @@ -76,6 +76,16 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab k.SetLastValidatorPower(ctx, valAddr, newPower) } + // change val node key + if pk, found := k.GetChangePubkey(ctx, validator.OperatorAddress); found { + oldVal := types.Validator{ConsPubKey: pk} + // delete old pubkey + updates = append(updates, oldVal.ABCIValidatorUpdateZero()) + // add new pubkey + updates = append(updates, validator.ABCIValidatorUpdateByShares()) + k.DeleteChangePubkey(ctx, validator.OperatorAddress) + } + // validator still in the validator set, so delete from the copy delete(last, valAddrBytes) diff --git a/x/staking/keeper/validator.go b/x/staking/keeper/validator.go index 3945043ad4..0042f566e9 100644 --- a/x/staking/keeper/validator.go +++ b/x/staking/keeper/validator.go @@ -3,6 +3,8 @@ package keeper import ( "bytes" "fmt" + "github.com/okex/exchain/libs/tendermint/crypto" + cryptoAmino "github.com/okex/exchain/libs/tendermint/crypto/encoding/amino" "time" sdk "github.com/okex/exchain/libs/cosmos-sdk/types" @@ -54,6 +56,29 @@ func (k Keeper) SetValidator(ctx sdk.Context, validator types.Validator) { store.Set(types.GetValidatorKey(validator.OperatorAddress), bz) } +func (k Keeper) SetChangePubkey(ctx sdk.Context, addr sdk.ValAddress, pubkey crypto.PubKey) { + store := ctx.KVStore(k.storeKey) + store.Set(types.GetValidatorChangePubkeyKey(addr), pubkey.Bytes()) +} + +func (k Keeper) DeleteChangePubkey(ctx sdk.Context, addr sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.GetValidatorChangePubkeyKey(addr)) +} + +func (k Keeper) GetChangePubkey(ctx sdk.Context, addr sdk.ValAddress) (pubkey crypto.PubKey, found bool) { + store := ctx.KVStore(k.storeKey) + value := store.Get(types.GetValidatorChangePubkeyKey(addr)) + if value == nil { + return nil, false + } + pk, err := cryptoAmino.PubKeyFromBytes(value) + if err != nil { + panic(err) + } + return pk, true +} + // SetValidatorByConsAddr sets the operator address with the key of validator consensus pubkey func (k Keeper) SetValidatorByConsAddr(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) @@ -61,6 +86,11 @@ func (k Keeper) SetValidatorByConsAddr(ctx sdk.Context, validator types.Validato store.Set(types.GetValidatorByConsAddrKey(consAddr), validator.OperatorAddress) } +func (k Keeper) DeleteValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.GetValidatorByConsAddrKey(consAddr)) +} + // SetValidatorByPowerIndex sets the power index key of an unjailed validator func (k Keeper) SetValidatorByPowerIndex(ctx sdk.Context, validator types.Validator) { // jailed validators are not kept in the power index diff --git a/x/staking/types/errors.go b/x/staking/types/errors.go index e9ccfd8724..ce51d77fd3 100644 --- a/x/staking/types/errors.go +++ b/x/staking/types/errors.go @@ -58,17 +58,29 @@ const ( CodeNoDelegatorExisted uint32 = 67044 CodeTargetValsDuplicate uint32 = 67045 CodeAlreadyBound uint32 = 67046 + + CodePubkeyEqual uint32 = 67047 + CodeDescriptionAndPubkeyIsEmpty uint32 = 67048 ) var ( - ErrInvalidHistoricalInfo = sdkerrors.Register(ModuleName, 144, "invalid historical info") - ErrNoHistoricalInfo = sdkerrors.Register(ModuleName, 145, "no historical info found") + ErrInvalidHistoricalInfo = sdkerrors.Register(ModuleName, 144, "invalid historical info") + ErrNoHistoricalInfo = sdkerrors.Register(ModuleName, 145, "no historical info found") ) + // ErrNoValidatorFound returns an error when a validator doesn't exist func ErrNoValidatorFound(valAddr string) sdk.EnvelopedErr { return sdk.EnvelopedErr{Err: sdkerrors.New(DefaultCodespace, CodeNoValidatorFound, fmt.Sprintf("validator %s does not exist", valAddr))} } +func ErrPubkeyEqual(pubkey string) sdk.EnvelopedErr { + return sdk.EnvelopedErr{Err: sdkerrors.New(DefaultCodespace, CodePubkeyEqual, fmt.Sprintf("validator pubkey %s does exist", pubkey))} +} + +func ErrDescriptionAndPubkeyIsEmpty() sdk.Error { + return sdkerrors.New(DefaultCodespace, CodeDescriptionAndPubkeyIsEmpty, "empty description and pubkey") +} + // ErrInvalidDelegation returns an error when the delegation is invalid func ErrInvalidDelegation(delegator string) sdk.EnvelopedErr { return sdk.EnvelopedErr{Err: sdkerrors.New(DefaultCodespace, CodeInvalidDelegation, diff --git a/x/staking/types/expected_keepers.go b/x/staking/types/expected_keepers.go index 781a0e5afb..82aaec3ce4 100644 --- a/x/staking/types/expected_keepers.go +++ b/x/staking/types/expected_keepers.go @@ -4,6 +4,7 @@ import ( sdk "github.com/okex/exchain/libs/cosmos-sdk/types" authexported "github.com/okex/exchain/libs/cosmos-sdk/x/auth/exported" supplyexported "github.com/okex/exchain/libs/cosmos-sdk/x/supply/exported" + "github.com/okex/exchain/libs/tendermint/crypto" stakingexported "github.com/okex/exchain/x/staking/exported" ) @@ -74,6 +75,7 @@ type ValidatorSet interface { type StakingHooks interface { // Must be called when a validator is created AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) + AfterValidatorPubkeyChanged(ctx sdk.Context, oldAddress sdk.ConsAddress, newAddress sdk.ConsAddress, newPubkey crypto.PubKey) // Must be called when a validator's state changes BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) // Must be called when a validator is deleted diff --git a/x/staking/types/hooks.go b/x/staking/types/hooks.go index c2170d7e10..14a6af1866 100644 --- a/x/staking/types/hooks.go +++ b/x/staking/types/hooks.go @@ -2,6 +2,7 @@ package types import ( sdk "github.com/okex/exchain/libs/cosmos-sdk/types" + "github.com/okex/exchain/libs/tendermint/crypto" ) // MultiStakingHooks combines multiple staking hooks, all hook functions are run in array sequence @@ -20,6 +21,12 @@ func (h MultiStakingHooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.Va } } +func (h MultiStakingHooks) AfterValidatorPubkeyChanged(ctx sdk.Context, oldAddress sdk.ConsAddress, newAddress sdk.ConsAddress, newPubkey crypto.PubKey) { + for i := range h { + h[i].AfterValidatorPubkeyChanged(ctx, oldAddress, newAddress, newPubkey) + } +} + // BeforeValidatorModified handles the hooks before the validator modified func (h MultiStakingHooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { for i := range h { diff --git a/x/staking/types/keys.go b/x/staking/types/keys.go index 71b71f7c8b..ab6605d181 100644 --- a/x/staking/types/keys.go +++ b/x/staking/types/keys.go @@ -30,7 +30,7 @@ const ( RouterKey = ModuleName ) -//nolint +// nolint var ( // Keys for store prefixes // Last* values are constant during a block. @@ -40,6 +40,7 @@ var ( ValidatorsKey = []byte{0x21} // prefix for each key to a validator ValidatorsByConsAddrKey = []byte{0x22} // prefix for each key to a validator index, by pubkey ValidatorsByPowerIndexKey = []byte{0x23} // prefix for each key to a validator index, sorted by power + ValChangePubkeyKey = []byte{0x24} ValidatorQueueKey = []byte{0x43} // prefix for the timestamps in validator queue @@ -67,6 +68,10 @@ func GetValidatorByConsAddrKey(addr sdk.ConsAddress) []byte { return append(ValidatorsByConsAddrKey, addr.Bytes()...) } +func GetValidatorChangePubkeyKey(operatorAddr sdk.ValAddress) []byte { + return append(ValChangePubkeyKey, operatorAddr.Bytes()...) +} + // AddressFromLastValidatorPowerKey gets the validator operator address from LastValidatorPowerKey func AddressFromLastValidatorPowerKey(key []byte) []byte { return key[1:] // remove prefix bytes diff --git a/x/staking/types/msg.go b/x/staking/types/msg.go index d458fbfc91..8c2071764a 100644 --- a/x/staking/types/msg.go +++ b/x/staking/types/msg.go @@ -132,13 +132,15 @@ func (msg MsgCreateValidator) ValidateBasic() error { type MsgEditValidator struct { Description ValidatorAddress sdk.ValAddress `json:"address" yaml:"address"` + PubKey crypto.PubKey `json:"pubkey" yaml:"pubkey"` } // NewMsgEditValidator creates a msg of edit-validator -func NewMsgEditValidator(valAddr sdk.ValAddress, description Description) MsgEditValidator { +func NewMsgEditValidator(valAddr sdk.ValAddress, description Description, pubKey crypto.PubKey) MsgEditValidator { return MsgEditValidator{ Description: description, ValidatorAddress: valAddr, + PubKey: pubKey, } } @@ -161,8 +163,8 @@ func (msg MsgEditValidator) ValidateBasic() error { return ErrNilValidatorAddr() } - if msg.Description == (Description{}) { - return ErrNilValidatorAddr() + if msg.Description == (Description{}) && (msg.PubKey == nil || len(msg.PubKey.Bytes()) == 0) { + return ErrDescriptionAndPubkeyIsEmpty() } return nil