diff --git a/PENDING.md b/PENDING.md index 654e342f8..eb61a5468 100644 --- a/PENDING.md +++ b/PENDING.md @@ -70,6 +70,7 @@ BREAKING CHANGES * [x/staking] \#2236 more distribution hooks for distribution * [x/stake] \#2394 Split up UpdateValidator into distinct state transitions applied only in EndBlock * [x/stake] \#2412 Added an unbonding validator queue to EndBlock to automatically update validator.Status when finished Unbonding + * [x/stake] \#2500 Block conflicting redelegations until we add an index * [x/params] Global Paramstore refactored * Tendermint diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index de7aee90f..f3d6ae5eb 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -863,6 +863,48 @@ func TestTransitiveRedelegation(t *testing.T) { require.True(t, got.IsOK(), "expected no error") } +func TestConflictingRedelegation(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr := sdk.ValAddress(keep.Addrs[0]) + validatorAddr2 := sdk.ValAddress(keep.Addrs[1]) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 1 + keeper.SetParams(ctx, params) + + // create the validators + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // end block to bond them + EndBlocker(ctx, keeper) + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, sdk.NewDec(5)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // cannot redelegate again while first redelegation still exists + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate) + + // progress forward in time + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(10 * time.Second)) + + // complete first redelegation + EndBlocker(ctx, keeper) + + // now should be able to redelegate again + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") +} + func TestUnbondingWhenExcessValidators(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, 1000) validatorAddr1 := sdk.ValAddress(keep.Addrs[0]) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 6f5a44ac0..52b9d8595 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -526,6 +526,13 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAd func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.Redelegation, sdk.Error) { + // check if there is already a redelgation in progress from src to dst + // TODO quick fix, instead we should use an index, see https://github.com/cosmos/cosmos-sdk/issues/1402 + _, found := k.GetRedelegation(ctx, delAddr, valSrcAddr, valDstAddr) + if found { + return types.Redelegation{}, types.ErrConflictingRedelegation(k.Codespace()) + } + // check if this is a transitive redelegation if k.HasReceivingRedelegation(ctx, delAddr, valSrcAddr) { return types.Redelegation{}, types.ErrTransitiveRedelegation(k.Codespace()) diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go index 84a7e5ae6..e8c85800f 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -164,6 +164,11 @@ func ErrTransitiveRedelegation(codespace sdk.CodespaceType) sdk.Error { "redelegation to this validator already in progress, first redelegation to this validator must complete before next redelegation") } +func ErrConflictingRedelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, + "conflicting redelegation from this source validator to this dest validator already exists, you must wait for it to finish") +} + func ErrBothShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "both shares amount and shares percent provided") }