diff --git a/app/app.go b/app/app.go index c9c0f5e893..b7b9f4ed8f 100644 --- a/app/app.go +++ b/app/app.go @@ -810,6 +810,10 @@ func (app *OKExChainApp) InitUpgrade(ctx sdk.Context) { app.WasmKeeper.UpdateMilestone(ctx, "wasm_v1", info.EffectiveHeight) }) + app.ParamsKeeper.ClaimReadyForUpgrade(tmtypes.MILESTONE_VENUS8_NAME, func(info paramstypes.UpgradeInfo) { + tmtypes.InitMilestoneVenus8Height(int64(info.EffectiveHeight)) + }) + if err := app.ParamsKeeper.ApplyEffectiveUpgrade(ctx); err != nil { tmos.Exit(fmt.Sprintf("failed apply effective upgrade height info: %s", err)) } diff --git a/libs/tendermint/types/milestone.go b/libs/tendermint/types/milestone.go index 9daabf89e5..009136d30c 100644 --- a/libs/tendermint/types/milestone.go +++ b/libs/tendermint/types/milestone.go @@ -48,6 +48,9 @@ var ( MILESTONE_VENUS7_NAME = "venus7" milestoneVenus7Height int64 = 0 + MILESTONE_VENUS8_NAME = "venus8" + milestoneVenus8Height int64 = 0 + // note: it stores the earlies height of the node,and it is used by cli nodePruneHeight int64 @@ -354,3 +357,15 @@ func GetVenus7Height() int64 { // =========== Venus7 =============== // ================================== + +// change val's pubkey +func HigherThanVenus8(h int64) bool { + if milestoneVenus8Height == 0 { + return false + } + return h > milestoneVenus8Height +} + +func InitMilestoneVenus8Height(h int64) { + milestoneVenus8Height = h +} 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/handler.go b/x/staking/handler.go index 8ef4229f97..83b59b4b8e 100644 --- a/x/staking/handler.go +++ b/x/staking/handler.go @@ -165,15 +165,40 @@ 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 + pk, err := types.GetConsPubKeyBech32(msg.Details) + if err == nil && tmtypes.HigherThanVenus8(ctx.BlockHeight()) { + if validator.ConsPubKey.Equals(pk) { + return nil, ErrPubkeyEqual(pk.Address().String()) + } + if _, found := k.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)); found { + return nil, ErrValidatorPubKeyExists() + } + if ctx.ConsensusParams() != nil { + tmPubKey := tmtypes.TM2PB.PubKey(pk) + if !StringInSlice(tmPubKey.Type, ctx.ConsensusParams().Validator.PubKeyTypes) { + return nil, ErrValidatorPubKeyTypeNotSupported(tmPubKey.Type, + ctx.ConsensusParams().Validator.PubKeyTypes) + } + } + k.SetChangePubkey(ctx, validator.OperatorAddress, validator.GetConsPubKey()) + oldConsAddr := validator.GetConsAddr() + + validator.ConsPubKey = pk + newConsAddr := validator.GetConsAddr() + k.SetValidator(ctx, validator) + k.SetValidatorByConsAddr(ctx, validator) + k.DeleteValidatorByConsAddr(ctx, oldConsAddr) + k.AfterValidatorPubkeyChanged(ctx, oldConsAddr, newConsAddr, pk) + } else { + // 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) + } 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..0e0547dcbd 100644 --- a/x/staking/keeper/validator.go +++ b/x/staking/keeper/validator.go @@ -6,6 +6,8 @@ import ( "time" sdk "github.com/okex/exchain/libs/cosmos-sdk/types" + "github.com/okex/exchain/libs/tendermint/crypto" + cryptoAmino "github.com/okex/exchain/libs/tendermint/crypto/encoding/amino" "github.com/okex/exchain/x/staking/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..fddc5a962f 100644 --- a/x/staking/types/errors.go +++ b/x/staking/types/errors.go @@ -58,17 +58,24 @@ const ( CodeNoDelegatorExisted uint32 = 67044 CodeTargetValsDuplicate uint32 = 67045 CodeAlreadyBound uint32 = 67046 + + CodePubkeyEqual uint32 = 67047 ) 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))} +} + // 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..491525369b 100644 --- a/x/staking/types/msg.go +++ b/x/staking/types/msg.go @@ -162,7 +162,7 @@ func (msg MsgEditValidator) ValidateBasic() error { } if msg.Description == (Description{}) { - return ErrNilValidatorAddr() + return ErrDescriptionIsEmpty() } return nil