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  }