github.com/Finschia/finschia-sdk@v0.48.1/x/slashing/handler_test.go (about) 1 package slashing_test 2 3 import ( 4 "errors" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/require" 10 tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 11 12 "github.com/Finschia/finschia-sdk/simapp" 13 "github.com/Finschia/finschia-sdk/testutil/testdata" 14 sdk "github.com/Finschia/finschia-sdk/types" 15 "github.com/Finschia/finschia-sdk/x/slashing" 16 "github.com/Finschia/finschia-sdk/x/slashing/keeper" 17 "github.com/Finschia/finschia-sdk/x/slashing/testslashing" 18 "github.com/Finschia/finschia-sdk/x/slashing/types" 19 "github.com/Finschia/finschia-sdk/x/staking" 20 "github.com/Finschia/finschia-sdk/x/staking/teststaking" 21 stakingtypes "github.com/Finschia/finschia-sdk/x/staking/types" 22 ) 23 24 func TestCannotUnjailUnlessJailed(t *testing.T) { 25 // initial setup 26 app := simapp.Setup(false) 27 ctx := app.BaseApp.NewContext(false, tmproto.Header{}) 28 pks := simapp.CreateTestPubKeys(1) 29 simapp.AddTestAddrsFromPubKeys(app, ctx, pks, app.StakingKeeper.TokensFromConsensusPower(ctx, 200)) 30 31 tstaking := teststaking.NewHelper(t, ctx, app.StakingKeeper) 32 slh := slashing.NewHandler(app.SlashingKeeper) 33 addr, val := sdk.ValAddress(pks[0].Address()), pks[0] 34 35 amt := tstaking.CreateValidatorWithValPower(addr, val, 100, true) 36 staking.EndBlocker(ctx, app.StakingKeeper) 37 require.Equal( 38 t, app.BankKeeper.GetAllBalances(ctx, sdk.AccAddress(addr)), 39 sdk.Coins{sdk.NewCoin(app.StakingKeeper.GetParams(ctx).BondDenom, InitTokens.Sub(amt))}, 40 ) 41 require.Equal(t, amt, app.StakingKeeper.Validator(ctx, addr).GetBondedTokens()) 42 43 // assert non-jailed validator can't be unjailed 44 res, err := slh(ctx, types.NewMsgUnjail(addr)) 45 require.Error(t, err) 46 require.Nil(t, res) 47 require.True(t, errors.Is(types.ErrValidatorNotJailed, err)) 48 } 49 50 func TestCannotUnjailUnlessMeetMinSelfDelegation(t *testing.T) { 51 // initial setup 52 app := simapp.Setup(false) 53 ctx := app.BaseApp.NewContext(false, tmproto.Header{}) 54 pks := simapp.CreateTestPubKeys(1) 55 simapp.AddTestAddrsFromPubKeys(app, ctx, pks, app.StakingKeeper.TokensFromConsensusPower(ctx, 200)) 56 57 tstaking := teststaking.NewHelper(t, ctx, app.StakingKeeper) 58 slh := slashing.NewHandler(app.SlashingKeeper) 59 addr, val := sdk.ValAddress(pks[0].Address()), pks[0] 60 amt := app.StakingKeeper.TokensFromConsensusPower(ctx, 100) 61 msg := tstaking.CreateValidatorMsg(addr, val, amt) 62 msg.MinSelfDelegation = amt 63 tstaking.Handle(msg, true) 64 65 staking.EndBlocker(ctx, app.StakingKeeper) 66 require.Equal( 67 t, app.BankKeeper.GetAllBalances(ctx, sdk.AccAddress(addr)), 68 sdk.Coins{sdk.NewCoin(app.StakingKeeper.GetParams(ctx).BondDenom, InitTokens.Sub(amt))}, 69 ) 70 71 tstaking.Undelegate(sdk.AccAddress(addr), addr, sdk.OneInt(), true) 72 require.True(t, app.StakingKeeper.Validator(ctx, addr).IsJailed()) 73 74 // assert non-jailed validator can't be unjailed 75 res, err := slh(ctx, types.NewMsgUnjail(addr)) 76 require.Error(t, err) 77 require.Nil(t, res) 78 require.True(t, errors.Is(types.ErrSelfDelegationTooLowToUnjail, err)) 79 } 80 81 func TestJailedValidatorDelegations(t *testing.T) { 82 // initial setup 83 app := simapp.Setup(false) 84 ctx := app.BaseApp.NewContext(false, tmproto.Header{Time: time.Unix(0, 0)}) 85 pks := simapp.CreateTestPubKeys(3) 86 87 simapp.AddTestAddrsFromPubKeys(app, ctx, pks, app.StakingKeeper.TokensFromConsensusPower(ctx, 20)) 88 app.SlashingKeeper.SetParams(ctx, testslashing.TestParams()) 89 90 tstaking := teststaking.NewHelper(t, ctx, app.StakingKeeper) 91 stakingParams := app.StakingKeeper.GetParams(ctx) 92 app.StakingKeeper.SetParams(ctx, stakingParams) 93 valAddr, consAddr := sdk.ValAddress(pks[1].Address()), sdk.ConsAddress(pks[0].Address()) 94 95 amt := tstaking.CreateValidatorWithValPower(valAddr, pks[1], 10, true) 96 staking.EndBlocker(ctx, app.StakingKeeper) 97 98 // set dummy signing info 99 newInfo := types.NewValidatorSigningInfo(consAddr, 0, 0, time.Unix(0, 0), false, 0) 100 app.SlashingKeeper.SetValidatorSigningInfo(ctx, consAddr, newInfo) 101 102 // delegate tokens to the validator 103 delAddr := sdk.AccAddress(pks[2].Address()) 104 tstaking.Delegate(delAddr, valAddr, amt) 105 106 // unbond validator total self-delegations (which should jail the validator) 107 valAcc := sdk.AccAddress(valAddr) 108 tstaking.Undelegate(valAcc, valAddr, amt, true) 109 _, err := app.StakingKeeper.CompleteUnbonding(ctx, sdk.AccAddress(valAddr), valAddr) 110 require.Nil(t, err, "expected complete unbonding validator to be ok, got: %v", err) 111 112 // verify validator still exists and is jailed 113 validator, found := app.StakingKeeper.GetValidator(ctx, valAddr) 114 require.True(t, found) 115 require.True(t, validator.IsJailed()) 116 117 // verify the validator cannot unjail itself 118 res, err := slashing.NewHandler(app.SlashingKeeper)(ctx, types.NewMsgUnjail(valAddr)) 119 require.Error(t, err) 120 require.Nil(t, res) 121 122 // self-delegate to validator 123 tstaking.Delegate(valAcc, valAddr, amt) 124 125 // verify the validator can now unjail itself 126 res, err = slashing.NewHandler(app.SlashingKeeper)(ctx, types.NewMsgUnjail(valAddr)) 127 require.NoError(t, err) 128 require.NotNil(t, res) 129 } 130 131 func TestInvalidMsg(t *testing.T) { 132 k := keeper.Keeper{} 133 h := slashing.NewHandler(k) 134 135 res, err := h(sdk.NewContext(nil, tmproto.Header{}, false, nil), testdata.NewTestMsg()) 136 require.Error(t, err) 137 require.Nil(t, res) 138 require.True(t, strings.Contains(err.Error(), "unrecognized slashing message type")) 139 } 140 141 // Test a validator through uptime, downtime, revocation, 142 // unrevocation, voter set counter reset, and revocation again 143 func TestHandleAbsentValidator(t *testing.T) { 144 // initial setup 145 app := simapp.Setup(false) 146 ctx := app.BaseApp.NewContext(false, tmproto.Header{Time: time.Unix(0, 0)}) 147 pks := simapp.CreateTestPubKeys(1) 148 simapp.AddTestAddrsFromPubKeys(app, ctx, pks, app.StakingKeeper.TokensFromConsensusPower(ctx, 200)) 149 app.SlashingKeeper.SetParams(ctx, testslashing.TestParams()) 150 151 power := int64(100) 152 addr, val := sdk.ValAddress(pks[0].Address()), pks[0] 153 slh := slashing.NewHandler(app.SlashingKeeper) 154 tstaking := teststaking.NewHelper(t, ctx, app.StakingKeeper) 155 156 amt := tstaking.CreateValidatorWithValPower(addr, val, power, true) 157 staking.EndBlocker(ctx, app.StakingKeeper) 158 159 require.Equal( 160 t, app.BankKeeper.GetAllBalances(ctx, sdk.AccAddress(addr)), 161 sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.GetParams(ctx).BondDenom, InitTokens.Sub(amt))), 162 ) 163 require.Equal(t, amt, app.StakingKeeper.Validator(ctx, addr).GetBondedTokens()) 164 165 // will exist since the validator has been bonded 166 info, found := app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) 167 require.True(t, found) 168 require.Equal(t, int64(0), info.StartHeight) 169 require.Equal(t, int64(0), info.IndexOffset) 170 require.Equal(t, int64(0), info.MissedBlocksCounter) 171 require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) 172 height := int64(0) 173 174 // 1000 first blocks OK 175 for ; height < app.SlashingKeeper.SignedBlocksWindow(ctx); height++ { 176 ctx = ctx.WithBlockHeight(height) 177 app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, true) 178 } 179 info, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) 180 require.True(t, found) 181 require.Equal(t, int64(0), info.StartHeight) 182 require.Equal(t, int64(1000), info.IndexOffset) 183 require.Equal(t, int64(0), info.MissedBlocksCounter) 184 185 // 500 blocks missed 186 for ; height < app.SlashingKeeper.SignedBlocksWindow(ctx)+(app.SlashingKeeper.SignedBlocksWindow(ctx)-app.SlashingKeeper.MinSignedPerWindow(ctx)); height++ { 187 ctx = ctx.WithBlockHeight(height) 188 app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, false) 189 } 190 info, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) 191 require.True(t, found) 192 require.Equal(t, int64(0), info.StartHeight) 193 require.Equal(t, int64(1500), info.IndexOffset) 194 require.Equal(t, app.SlashingKeeper.SignedBlocksWindow(ctx)-app.SlashingKeeper.MinSignedPerWindow(ctx), info.MissedBlocksCounter) 195 196 // validator should be bonded still 197 validator, _ := app.StakingKeeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) 198 require.Equal(t, stakingtypes.Bonded, validator.GetStatus()) 199 200 bondPool := app.StakingKeeper.GetBondedPool(ctx) 201 require.True(sdk.IntEq(t, amt, app.BankKeeper.GetBalance(ctx, bondPool.GetAddress(), app.StakingKeeper.BondDenom(ctx)).Amount)) 202 203 // 501st block missed 204 ctx = ctx.WithBlockHeight(height) 205 app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, false) 206 info, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) 207 require.True(t, found) 208 require.Equal(t, int64(0), info.StartHeight) 209 require.Equal(t, int64(0), info.IndexOffset) 210 // counter now reset to zero 211 require.Equal(t, int64(0), info.MissedBlocksCounter) 212 213 // end block 214 staking.EndBlocker(ctx, app.StakingKeeper) 215 216 // validator should have been jailed 217 validator, _ = app.StakingKeeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) 218 require.Equal(t, stakingtypes.Unbonding, validator.GetStatus()) 219 220 slashAmt := amt.ToDec().Mul(app.SlashingKeeper.SlashFractionDowntime(ctx)).RoundInt() 221 222 // validator should have been slashed 223 require.True(t, amt.Sub(slashAmt).Equal(validator.GetTokens())) 224 225 // 502nd block *also* missed (since the LastCommit would have still included the just-unbonded validator) 226 height++ 227 ctx = ctx.WithBlockHeight(height) 228 app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, false) 229 info, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) 230 require.True(t, found) 231 require.Equal(t, int64(0), info.StartHeight) 232 require.Equal(t, int64(1), info.IndexOffset) 233 require.Equal(t, int64(1), info.MissedBlocksCounter) 234 235 // end block 236 staking.EndBlocker(ctx, app.StakingKeeper) 237 238 // validator should not have been slashed any more, since it was already jailed 239 validator, _ = app.StakingKeeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) 240 require.True(t, amt.Sub(slashAmt).Equal(validator.GetTokens())) 241 242 // unrevocation should fail prior to jail expiration 243 res, err := slh(ctx, types.NewMsgUnjail(addr)) 244 require.Error(t, err) 245 require.Nil(t, res) 246 247 // unrevocation should succeed after jail expiration 248 ctx = ctx.WithBlockHeader(tmproto.Header{Time: time.Unix(1, 0).Add(app.SlashingKeeper.DowntimeJailDuration(ctx))}) 249 res, err = slh(ctx, types.NewMsgUnjail(addr)) 250 require.NoError(t, err) 251 require.NotNil(t, res) 252 253 // end block 254 staking.EndBlocker(ctx, app.StakingKeeper) 255 256 // validator should be rebonded now 257 validator, _ = app.StakingKeeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) 258 require.Equal(t, stakingtypes.Bonded, validator.GetStatus()) 259 260 // validator should have been slashed 261 require.True(t, amt.Sub(slashAmt).Equal(app.BankKeeper.GetBalance(ctx, bondPool.GetAddress(), app.StakingKeeper.BondDenom(ctx)).Amount)) 262 263 // Validator voter set counter should not have been changed 264 info, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) 265 require.True(t, found) 266 require.Equal(t, int64(0), info.StartHeight) 267 require.Equal(t, int64(1), info.IndexOffset) 268 // we've missed 2 blocks more than the maximum, so the counter was reset to 0 at 1 block more and is now 1 269 require.Equal(t, int64(1), info.MissedBlocksCounter) 270 271 // validator should not be immediately jailed again 272 height++ 273 ctx = ctx.WithBlockHeight(height) 274 app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, false) 275 validator, _ = app.StakingKeeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) 276 require.Equal(t, stakingtypes.Bonded, validator.GetStatus()) 277 278 // 500 signed blocks 279 nextHeight := height + app.SlashingKeeper.MinSignedPerWindow(ctx) + 1 280 for ; height < nextHeight; height++ { 281 ctx = ctx.WithBlockHeight(height) 282 app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, false) 283 } 284 285 // end block 286 staking.EndBlocker(ctx, app.StakingKeeper) 287 288 // validator should be jailed again after 500 unsigned blocks 289 nextHeight = height + app.SlashingKeeper.MinSignedPerWindow(ctx) + 1 290 for ; height <= nextHeight; height++ { 291 ctx = ctx.WithBlockHeight(height) 292 app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, false) 293 } 294 295 // end block 296 staking.EndBlocker(ctx, app.StakingKeeper) 297 298 validator, _ = app.StakingKeeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) 299 require.Equal(t, stakingtypes.Unbonding, validator.GetStatus()) 300 }