Skip to content

Commit d90b2e7

Browse files
authored
Allow Unjail of Non-Bonded Jailed Validator (#6061)
* Use correct API and add TODO * Allow unjail without signing info * Add unit test * Update godoc * Add changelog entries * Fix TestUndelegateSelfDelegationBelowMinSelfDelegation * Fix delegation tests * Revert API call
1 parent e9b5fc9 commit d90b2e7

File tree

5 files changed

+119
-18
lines changed

5 files changed

+119
-18
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ information on how to implement the new `Keyring` interface.
113113

114114
### Bug Fixes
115115

116+
* (x/staking) [\#6061](https://github.com/cosmos/cosmos-sdk/pull/6061) Allow a validator to immediately unjail when no signing info is present due to
117+
falling below their minimum self-delegation and never having been bonded. The validator may immediately unjail once they've met their minimum self-delegation.
116118
* (types) [\#5741](https://github.com/cosmos/cosmos-sdk/issues/5741) Prevent ChainAnteDecorators() from panicking when empty AnteDecorator slice is supplied.
117119
* (modules) [\#5569](https://github.com/cosmos/cosmos-sdk/issues/5569) `InitGenesis`, for the relevant modules, now ensures module accounts exist.
118120
* (crypto/keyring) [\#5844](https://github.com/cosmos/cosmos-sdk/pull/5844) `Keyring.Sign()` methods no longer decode amino signatures when method receivers
@@ -128,6 +130,8 @@ invalid or incomplete requests.
128130

129131
### State Machine Breaking
130132

133+
* (x/staking) [\#6061](https://github.com/cosmos/cosmos-sdk/pull/6061) Allow a validator to immediately unjail when no signing info is present due to
134+
falling below their minimum self-delegation and never having been bonded. The validator may immediately unjail once they've met their minimum self-delegation.
131135
* (x/supply) [\#6010](https://github.com/cosmos/cosmos-sdk/pull/6010) Removed the `x/supply` module by merging the existing types and APIs into the `x/bank` module.
132136
* (modules) [\#5572](https://github.com/cosmos/cosmos-sdk/pull/5572) Separate balance from accounts per ADR 004.
133137
* Account balances are now persisted and retrieved via the `x/bank` module.

x/slashing/keeper/keeper_test.go

+80-2
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,94 @@ import (
44
"testing"
55
"time"
66

7-
abci "github.com/tendermint/tendermint/abci/types"
8-
97
"github.com/stretchr/testify/require"
8+
abci "github.com/tendermint/tendermint/abci/types"
109

1110
"github.com/cosmos/cosmos-sdk/simapp"
1211
sdk "github.com/cosmos/cosmos-sdk/types"
1312
"github.com/cosmos/cosmos-sdk/x/slashing/keeper"
1413
"github.com/cosmos/cosmos-sdk/x/staking"
1514
)
1615

16+
func TestUnJailNotBonded(t *testing.T) {
17+
app := simapp.Setup(false)
18+
ctx := app.BaseApp.NewContext(false, abci.Header{})
19+
20+
p := app.StakingKeeper.GetParams(ctx)
21+
p.MaxValidators = 5
22+
app.StakingKeeper.SetParams(ctx, p)
23+
24+
amt := sdk.TokensFromConsensusPower(100)
25+
sh := staking.NewHandler(app.StakingKeeper)
26+
27+
addrDels := simapp.AddTestAddrsIncremental(app, ctx, 6, sdk.TokensFromConsensusPower(200))
28+
valAddrs := simapp.ConvertAddrsToValAddrs(addrDels)
29+
pks := simapp.CreateTestPubKeys(6)
30+
31+
// create max (5) validators all with the same power
32+
for i := uint32(0); i < p.MaxValidators; i++ {
33+
addr, val := valAddrs[i], pks[i]
34+
res, err := sh(ctx, keeper.NewTestMsgCreateValidator(addr, val, amt))
35+
require.NoError(t, err)
36+
require.NotNil(t, res)
37+
}
38+
39+
staking.EndBlocker(ctx, app.StakingKeeper)
40+
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
41+
42+
// create a 6th validator with less power than the cliff validator (won't be bonded)
43+
addr, val := valAddrs[5], pks[5]
44+
createValMsg := keeper.NewTestMsgCreateValidator(addr, val, sdk.TokensFromConsensusPower(50))
45+
createValMsg.MinSelfDelegation = sdk.TokensFromConsensusPower(50)
46+
res, err := sh(ctx, createValMsg)
47+
require.NoError(t, err)
48+
require.NotNil(t, res)
49+
50+
staking.EndBlocker(ctx, app.StakingKeeper)
51+
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
52+
53+
validator, ok := app.StakingKeeper.GetValidator(ctx, addr)
54+
require.True(t, ok)
55+
require.False(t, validator.Jailed)
56+
require.Equal(t, sdk.BondStatusUnbonded, validator.GetStatus().String())
57+
58+
// unbond below minimum self-delegation
59+
msgUnbond := staking.NewMsgUndelegate(sdk.AccAddress(addr), addr, sdk.NewCoin(p.BondDenom, sdk.TokensFromConsensusPower(1)))
60+
res, err = sh(ctx, msgUnbond)
61+
require.NoError(t, err)
62+
require.NotNil(t, res)
63+
64+
staking.EndBlocker(ctx, app.StakingKeeper)
65+
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
66+
67+
// verify that validator is jailed
68+
validator, ok = app.StakingKeeper.GetValidator(ctx, addr)
69+
require.True(t, ok)
70+
require.True(t, validator.Jailed)
71+
72+
// verify we cannot unjail (yet)
73+
require.Error(t, app.SlashingKeeper.Unjail(ctx, addr))
74+
75+
staking.EndBlocker(ctx, app.StakingKeeper)
76+
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
77+
78+
// bond to meet minimum self-delegation
79+
msgBond := staking.NewMsgDelegate(sdk.AccAddress(addr), addr, sdk.NewCoin(p.BondDenom, sdk.TokensFromConsensusPower(1)))
80+
res, err = sh(ctx, msgBond)
81+
require.NoError(t, err)
82+
require.NotNil(t, res)
83+
84+
staking.EndBlocker(ctx, app.StakingKeeper)
85+
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
86+
87+
// verify we can immediately unjail
88+
require.NoError(t, app.SlashingKeeper.Unjail(ctx, addr))
89+
90+
validator, ok = app.StakingKeeper.GetValidator(ctx, addr)
91+
require.True(t, ok)
92+
require.False(t, validator.Jailed)
93+
}
94+
1795
// Test a new validator entering the validator set
1896
// Ensure that SigningInfo.StartHeight is set correctly
1997
// and that they are not immediately jailed

x/slashing/keeper/unjail.go

+25-14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package keeper
22

33
import (
44
sdk "github.com/cosmos/cosmos-sdk/types"
5+
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
56
"github.com/cosmos/cosmos-sdk/x/slashing/types"
67
)
78

@@ -19,8 +20,12 @@ func (k Keeper) Unjail(ctx sdk.Context, validatorAddr sdk.ValAddress) error {
1920
return types.ErrMissingSelfDelegation
2021
}
2122

22-
if validator.TokensFromShares(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) {
23-
return types.ErrSelfDelegationTooLowToUnjail
23+
tokens := validator.TokensFromShares(selfDel.GetShares()).TruncateInt()
24+
minSelfBond := validator.GetMinSelfDelegation()
25+
if tokens.LT(minSelfBond) {
26+
return sdkerrors.Wrapf(
27+
types.ErrSelfDelegationTooLowToUnjail, "%s less than %s", tokens, minSelfBond,
28+
)
2429
}
2530

2631
// cannot be unjailed if not jailed
@@ -30,19 +35,25 @@ func (k Keeper) Unjail(ctx sdk.Context, validatorAddr sdk.ValAddress) error {
3035

3136
consAddr := sdk.ConsAddress(validator.GetConsPubKey().Address())
3237

38+
// If the validator has a ValidatorSigningInfo object that signals that the
39+
// validator was bonded and so we must check that the validator is not tombstoned
40+
// and can be unjailed at the current block.
41+
//
42+
// A validator that is jailed but has no ValidatorSigningInfo object signals
43+
// that the validator was never bonded and must've been jailed due to falling
44+
// below their minimum self-delegation. The validator can unjail at any point
45+
// assuming they've now bonded above their minimum self-delegation.
3346
info, found := k.GetValidatorSigningInfo(ctx, consAddr)
34-
if !found {
35-
return types.ErrNoValidatorForAddress
36-
}
37-
38-
// cannot be unjailed if tombstoned
39-
if info.Tombstoned {
40-
return types.ErrValidatorJailed
41-
}
42-
43-
// cannot be unjailed until out of jail
44-
if ctx.BlockHeader().Time.Before(info.JailedUntil) {
45-
return types.ErrValidatorJailed
47+
if found {
48+
// cannot be unjailed if tombstoned
49+
if info.Tombstoned {
50+
return types.ErrValidatorJailed
51+
}
52+
53+
// cannot be unjailed until out of jail
54+
if ctx.BlockHeader().Time.Before(info.JailedUntil) {
55+
return types.ErrValidatorJailed
56+
}
4657
}
4758

4859
k.sk.Unjail(ctx, consAddr)

x/staking/keeper/delegation.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -577,8 +577,8 @@ func (k Keeper) Unbond(
577577

578578
isValidatorOperator := delegation.DelegatorAddress.Equals(validator.OperatorAddress)
579579

580-
// if the delegation is the operator of the validator and undelegating will decrease the validator's self delegation below their minimum
581-
// trigger a jail validator
580+
// If the delegation is the operator of the validator and undelegating will decrease the validator's
581+
// self-delegation below their minimum, we jail the validator.
582582
if isValidatorOperator && !validator.Jailed &&
583583
validator.TokensFromShares(delegation.Shares).TruncateInt().LT(validator.MinSelfDelegation) {
584584

x/staking/keeper/delegation_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ func TestUndelegateSelfDelegationBelowMinSelfDelegation(t *testing.T) {
327327
app.AccountKeeper.SetModuleAccount(ctx, notBondedPool)
328328

329329
validator = keeper.TestingUpdateValidator(app.StakingKeeper, ctx, validator, true)
330+
app.StakingKeeper.SetValidatorByConsAddr(ctx, validator)
330331
require.True(t, validator.IsBonded())
331332

332333
selfDelegation := types.NewDelegation(sdk.AccAddress(addrVals[0].Bytes()), addrVals[0], issuedShares)
@@ -380,6 +381,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) {
380381

381382
//create a validator with a self-delegation
382383
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
384+
app.StakingKeeper.SetValidatorByConsAddr(ctx, validator)
383385

384386
validator, issuedShares := validator.AddTokensFromDel(delTokens)
385387
require.Equal(t, delTokens, issuedShares.RoundInt())
@@ -480,6 +482,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) {
480482

481483
// create a validator with a self-delegation
482484
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
485+
app.StakingKeeper.SetValidatorByConsAddr(ctx, validator)
483486

484487
valTokens := sdk.TokensFromConsensusPower(10)
485488
validator, issuedShares := validator.AddTokensFromDel(valTokens)
@@ -564,6 +567,7 @@ func TestUnbondingAllDelegationFromValidator(t *testing.T) {
564567

565568
//create a validator with a self-delegation
566569
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
570+
app.StakingKeeper.SetValidatorByConsAddr(ctx, validator)
567571

568572
valTokens := sdk.TokensFromConsensusPower(10)
569573
validator, issuedShares := validator.AddTokensFromDel(valTokens)
@@ -820,6 +824,8 @@ func TestRedelegateSelfDelegation(t *testing.T) {
820824

821825
//create a validator with a self-delegation
822826
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
827+
app.StakingKeeper.SetValidatorByConsAddr(ctx, validator)
828+
823829
valTokens := sdk.TokensFromConsensusPower(10)
824830
validator, issuedShares := validator.AddTokensFromDel(valTokens)
825831
require.Equal(t, valTokens, issuedShares.RoundInt())
@@ -877,6 +883,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) {
877883

878884
//create a validator with a self-delegation
879885
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
886+
app.StakingKeeper.SetValidatorByConsAddr(ctx, validator)
880887

881888
valTokens := sdk.TokensFromConsensusPower(10)
882889
validator, issuedShares := validator.AddTokensFromDel(valTokens)
@@ -961,6 +968,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) {
961968

962969
//create a validator with a self-delegation
963970
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
971+
app.StakingKeeper.SetValidatorByConsAddr(ctx, validator)
964972

965973
valTokens := sdk.TokensFromConsensusPower(10)
966974
validator, issuedShares := validator.AddTokensFromDel(valTokens)

0 commit comments

Comments
 (0)