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