diff --git a/x/nodes/keeper/valStateChanges.go b/x/nodes/keeper/valStateChanges.go index d559b6f86..a6e4646bd 100644 --- a/x/nodes/keeper/valStateChanges.go +++ b/x/nodes/keeper/valStateChanges.go @@ -261,6 +261,15 @@ func (k Keeper) ValidateEditStake(ctx sdk.Ctx, currentValidator, newValidtor typ return types.ErrUnequalOutputAddr(k.Codespace()) } } + + // Delegators can be set/edited only if 1) the feature has been activated + // AND 2) the message is signed by the operator address. + if k.Cdc.IsAfterDelegatorUpgrade(ctx.BlockHeight()) && + !types.CompareStringMaps(currentValidator.Delegators, newValidtor.Delegators) && + !signer.Equals(currentValidator.Address) { + return types.ErrDisallowedOutputAddressEdit(k.Codespace()) + } + // prevent waiting vals from modifying anything if k.IsWaitingValidator(ctx, currentValidator.Address) { return types.ErrValidatorWaitingToUnstake(types.ModuleName) @@ -328,6 +337,11 @@ func (k Keeper) EditStakeValidator(ctx sdk.Ctx, currentValidator, updatedValidat currentValidator.OutputAddress == nil { currentValidator.OutputAddress = updatedValidator.OutputAddress } + + // After the upgrade, we allow delegators change + if k.Cdc.IsAfterDelegatorUpgrade(ctx.BlockHeight()) { + currentValidator.Delegators = updatedValidator.Delegators + } } // update chains currentValidator.Chains = updatedValidator.Chains diff --git a/x/nodes/keeper/valStateChanges_test.go b/x/nodes/keeper/valStateChanges_test.go index cff7c1155..2950aca58 100644 --- a/x/nodes/keeper/valStateChanges_test.go +++ b/x/nodes/keeper/valStateChanges_test.go @@ -369,13 +369,8 @@ func handleStakeForTesting( msg types.MsgStake, signer crypto.PublicKey, ) sdk.Error { - validator := types.NewValidator( - sdk.Address(msg.PublicKey.Address()), - msg.PublicKey, - msg.Chains, - msg.ServiceUrl, - sdk.ZeroInt(), - msg.Output) + validator := types.NewValidatorFromMsg(msg) + validator.StakedTokens = sdk.ZeroInt() if err := k.ValidateValidatorStaking( ctx, validator, msg.Value, sdk.Address(signer.Address())); err != nil { return err @@ -498,6 +493,97 @@ func TestValidatorStateChange_OutputAddressEdit(t *testing.T) { assert.Equal(t, validatorCur.OutputAddress, outputAddress) } +func TestValidatorStateChange_Delegators(t *testing.T) { + ctx, _, k := createTestInput(t, true) + + originalUpgradeHeight := codec.UpgradeHeight + originalTestMode := codec.TestMode + originalNCUST := codec.UpgradeFeatureMap[codec.NonCustodialUpdateKey] + originalOEDIT := codec.UpgradeFeatureMap[codec.OutputAddressEditKey] + originalDELE := codec.UpgradeFeatureMap[codec.DelegatorsKey] + t.Cleanup(func() { + codec.UpgradeHeight = originalUpgradeHeight + codec.TestMode = originalTestMode + codec.UpgradeFeatureMap[codec.NonCustodialUpdateKey] = originalNCUST + codec.UpgradeFeatureMap[codec.OutputAddressEditKey] = originalOEDIT + codec.UpgradeFeatureMap[codec.DelegatorsKey] = originalDELE + }) + + // Enable EditStake, NCUST, and OEDIT + codec.TestMode = 0 + codec.UpgradeHeight = -1 + codec.UpgradeFeatureMap[codec.NonCustodialUpdateKey] = -1 + codec.UpgradeFeatureMap[codec.OutputAddressEditKey] = -1 + + // Prepare accounts + outputPubKey := getRandomPubKey() + operatorPubKey1 := getRandomPubKey() + operatorPubKey2 := getRandomPubKey() + operatorAddr1 := sdk.Address(operatorPubKey1.Address()) + outputAddress := sdk.Address(outputPubKey.Address()) + operatorAddr2 := sdk.Address(operatorPubKey2.Address()) + + // Fund output address for two nodes + stakeAmount := sdk.NewCoin(k.StakeDenom(ctx), sdk.NewInt(k.MinimumStake(ctx))) + assert.Nil(t, fundAccount(ctx, k, outputAddress, stakeAmount)) + assert.Nil(t, fundAccount(ctx, k, outputAddress, stakeAmount)) + + runStake := func( + operatorPubkey crypto.PublicKey, + delegators map[string]uint32, + signer crypto.PublicKey, + ) sdk.Error { + msgStake := types.MsgStake{ + Chains: []string{"0021", "0040"}, + ServiceUrl: "https://www.pokt.network:443", + Value: stakeAmount.Amount, + PublicKey: operatorPubkey, + Output: outputAddress, + Delegators: delegators, + } + return handleStakeForTesting(ctx, k, msgStake, signer) + } + + singleDelegator := map[string]uint32{} + singleDelegator[getRandomValidatorAddress().String()] = 1 + + // Attempt to set a delegators before DELE upgrade --> The field is ignored + assert.Nil(t, runStake(operatorPubKey1, singleDelegator, outputPubKey)) + validatorCur, found := k.GetValidator(ctx, operatorAddr1) + assert.True(t, found) + assert.Nil(t, validatorCur.Delegators) + + // Enable DELE + codec.UpgradeFeatureMap[codec.DelegatorsKey] = -1 + + // Attempt to change the delegators with output's signature --> Fail + err := runStake(operatorPubKey1, singleDelegator, outputPubKey) + assert.NotNil(t, err) + assert.Equal(t, k.codespace, err.Codespace()) + assert.Equal(t, types.CodeDisallowedOutputAddressEdit, err.Code()) + + // Attempt to set the delegators with operator's signature --> Success + err = runStake(operatorPubKey1, singleDelegator, operatorPubKey1) + assert.Nil(t, err) + validatorCur, found = k.GetValidator(ctx, operatorAddr1) + assert.True(t, found) + assert.True(t, types.CompareStringMaps(validatorCur.Delegators, singleDelegator)) + + // Attempt to reset the delegators with operator's signature --> Success + err = runStake(operatorPubKey1, nil, operatorPubKey1) + assert.Nil(t, err) + validatorCur, found = k.GetValidator(ctx, operatorAddr1) + assert.True(t, found) + assert.Nil(t, validatorCur.Delegators) + + // New stake with delegators can be signed by the output --> Success + err = runStake(operatorPubKey2, singleDelegator, outputPubKey) + assert.Nil(t, err) + validatorCur, found = k.GetValidator(ctx, operatorAddr2) + assert.True(t, found) + assert.True(t, types.CompareStringMaps(validatorCur.Delegators, singleDelegator)) +} + func TestKeeper_JailValidator(t *testing.T) { type fields struct { keeper Keeper