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  }