github.com/Finschia/finschia-sdk@v0.48.1/x/slashing/keeper/keeper_test.go (about)

     1  package keeper_test
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/stretchr/testify/require"
     8  	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
     9  
    10  	"github.com/Finschia/finschia-sdk/simapp"
    11  	sdk "github.com/Finschia/finschia-sdk/types"
    12  	"github.com/Finschia/finschia-sdk/x/slashing/testslashing"
    13  	"github.com/Finschia/finschia-sdk/x/staking"
    14  	"github.com/Finschia/finschia-sdk/x/staking/teststaking"
    15  	stakingtypes "github.com/Finschia/finschia-sdk/x/staking/types"
    16  )
    17  
    18  func TestUnJailNotBonded(t *testing.T) {
    19  	app := simapp.Setup(false)
    20  	ctx := app.BaseApp.NewContext(false, tmproto.Header{})
    21  
    22  	p := app.StakingKeeper.GetParams(ctx)
    23  	p.MaxValidators = 5
    24  	app.StakingKeeper.SetParams(ctx, p)
    25  
    26  	addrDels := simapp.AddTestAddrsIncremental(app, ctx, 6, app.StakingKeeper.TokensFromConsensusPower(ctx, 200))
    27  	valAddrs := simapp.ConvertAddrsToValAddrs(addrDels)
    28  	pks := simapp.CreateTestPubKeys(6)
    29  	tstaking := teststaking.NewHelper(t, ctx, app.StakingKeeper)
    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  		tstaking.CreateValidatorWithValPower(addr, val, 100, true)
    35  	}
    36  
    37  	staking.EndBlocker(ctx, app.StakingKeeper)
    38  	ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
    39  
    40  	// create a 6th validator with less power than the cliff validator (won't be bonded)
    41  	addr, val := valAddrs[5], pks[5]
    42  	amt := app.StakingKeeper.TokensFromConsensusPower(ctx, 50)
    43  	msg := tstaking.CreateValidatorMsg(addr, val, amt)
    44  	msg.MinSelfDelegation = amt
    45  	tstaking.Handle(msg, true)
    46  
    47  	staking.EndBlocker(ctx, app.StakingKeeper)
    48  	ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
    49  
    50  	tstaking.CheckValidator(addr, stakingtypes.Unbonded, false)
    51  
    52  	// unbond below minimum self-delegation
    53  	require.Equal(t, p.BondDenom, tstaking.Denom)
    54  	tstaking.Undelegate(sdk.AccAddress(addr), addr, app.StakingKeeper.TokensFromConsensusPower(ctx, 1), true)
    55  
    56  	staking.EndBlocker(ctx, app.StakingKeeper)
    57  	ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
    58  
    59  	// verify that validator is jailed
    60  	tstaking.CheckValidator(addr, -1, true)
    61  
    62  	// verify we cannot unjail (yet)
    63  	require.Error(t, app.SlashingKeeper.Unjail(ctx, addr))
    64  
    65  	staking.EndBlocker(ctx, app.StakingKeeper)
    66  	ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
    67  	// bond to meet minimum self-delegation
    68  	tstaking.DelegateWithPower(sdk.AccAddress(addr), addr, 1)
    69  
    70  	staking.EndBlocker(ctx, app.StakingKeeper)
    71  	ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
    72  
    73  	// verify we can immediately unjail
    74  	require.NoError(t, app.SlashingKeeper.Unjail(ctx, addr))
    75  
    76  	tstaking.CheckValidator(addr, -1, false)
    77  }
    78  
    79  // Test a new validator entering the validator set
    80  // Ensure that SigningInfo.VoterSetCounter is set correctly
    81  // and that they are not immediately jailed
    82  func TestHandleNewValidator(t *testing.T) {
    83  	app := simapp.Setup(false)
    84  	ctx := app.BaseApp.NewContext(false, tmproto.Header{})
    85  
    86  	addrDels := simapp.AddTestAddrsIncremental(app, ctx, 1, app.StakingKeeper.TokensFromConsensusPower(ctx, 200))
    87  	valAddrs := simapp.ConvertAddrsToValAddrs(addrDels)
    88  	pks := simapp.CreateTestPubKeys(1)
    89  	addr, val := valAddrs[0], pks[0]
    90  	tstaking := teststaking.NewHelper(t, ctx, app.StakingKeeper)
    91  	ctx = ctx.WithBlockHeight(app.SlashingKeeper.SignedBlocksWindow(ctx) + 1)
    92  
    93  	// Validator created
    94  	amt := tstaking.CreateValidatorWithValPower(addr, val, 100, true)
    95  
    96  	staking.EndBlocker(ctx, app.StakingKeeper)
    97  	require.Equal(
    98  		t, app.BankKeeper.GetAllBalances(ctx, sdk.AccAddress(addr)),
    99  		sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.GetParams(ctx).BondDenom, InitTokens.Sub(amt))),
   100  	)
   101  	require.Equal(t, amt, app.StakingKeeper.Validator(ctx, addr).GetBondedTokens())
   102  
   103  	// Now a validator, for two blocks
   104  	app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), 100, true)
   105  	ctx = ctx.WithBlockHeight(app.SlashingKeeper.SignedBlocksWindow(ctx) + 2)
   106  	app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), 100, false)
   107  
   108  	info, found := app.SlashingKeeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
   109  	require.True(t, found)
   110  	require.Equal(t, app.SlashingKeeper.SignedBlocksWindow(ctx)+1, info.StartHeight)
   111  	require.Equal(t, int64(2), info.IndexOffset)
   112  	require.Equal(t, int64(1), info.MissedBlocksCounter)
   113  	require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil)
   114  
   115  	// validator should be bonded still, should not have been jailed or slashed
   116  	validator, _ := app.StakingKeeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val))
   117  	require.Equal(t, stakingtypes.Bonded, validator.GetStatus())
   118  	bondPool := app.StakingKeeper.GetBondedPool(ctx)
   119  	expTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 100)
   120  	require.True(t, expTokens.Equal(app.BankKeeper.GetBalance(ctx, bondPool.GetAddress(), app.StakingKeeper.BondDenom(ctx)).Amount))
   121  }
   122  
   123  // Test a jailed validator being "down" twice
   124  // Ensure that they're only slashed once
   125  func TestHandleAlreadyJailed(t *testing.T) {
   126  	// initial setup
   127  	app := simapp.Setup(false)
   128  	ctx := app.BaseApp.NewContext(false, tmproto.Header{})
   129  
   130  	addrDels := simapp.AddTestAddrsIncremental(app, ctx, 1, app.StakingKeeper.TokensFromConsensusPower(ctx, 200))
   131  	valAddrs := simapp.ConvertAddrsToValAddrs(addrDels)
   132  	pks := simapp.CreateTestPubKeys(1)
   133  	addr, val := valAddrs[0], pks[0]
   134  	power := int64(100)
   135  	tstaking := teststaking.NewHelper(t, ctx, app.StakingKeeper)
   136  
   137  	amt := tstaking.CreateValidatorWithValPower(addr, val, power, true)
   138  
   139  	staking.EndBlocker(ctx, app.StakingKeeper)
   140  
   141  	// 1000 first blocks OK
   142  	height := int64(0)
   143  	for ; height < app.SlashingKeeper.SignedBlocksWindow(ctx); height++ {
   144  		ctx = ctx.WithBlockHeight(height)
   145  		app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, true)
   146  	}
   147  
   148  	// 501 blocks missed
   149  	for ; height < app.SlashingKeeper.SignedBlocksWindow(ctx)+(app.SlashingKeeper.SignedBlocksWindow(ctx)-app.SlashingKeeper.MinSignedPerWindow(ctx))+1; height++ {
   150  		ctx = ctx.WithBlockHeight(height)
   151  		app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, false)
   152  	}
   153  
   154  	// end block
   155  	staking.EndBlocker(ctx, app.StakingKeeper)
   156  
   157  	// validator should have been jailed and slashed
   158  	validator, _ := app.StakingKeeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val))
   159  	require.Equal(t, stakingtypes.Unbonding, validator.GetStatus())
   160  
   161  	// validator should have been slashed
   162  	resultingTokens := amt.Sub(app.StakingKeeper.TokensFromConsensusPower(ctx, 1))
   163  	require.Equal(t, resultingTokens, validator.GetTokens())
   164  
   165  	// another block missed
   166  	ctx = ctx.WithBlockHeight(height)
   167  	app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, false)
   168  
   169  	// validator should not have been slashed twice
   170  	validator, _ = app.StakingKeeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val))
   171  	require.Equal(t, resultingTokens, validator.GetTokens())
   172  }
   173  
   174  // Test a validator dipping in and out of the validator set
   175  // Ensure that missed blocks are tracked correctly and that
   176  // the voter set counter of the signing info is reset correctly
   177  func TestValidatorDippingInAndOut(t *testing.T) {
   178  	// initial setup
   179  	// TestParams set the SignedBlocksWindow to 1000 and MaxMissedBlocksPerWindow to 500
   180  	app := simapp.Setup(false)
   181  	ctx := app.BaseApp.NewContext(false, tmproto.Header{})
   182  	app.SlashingKeeper.SetParams(ctx, testslashing.TestParams())
   183  
   184  	params := app.StakingKeeper.GetParams(ctx)
   185  	params.MaxValidators = 1
   186  	app.StakingKeeper.SetParams(ctx, params)
   187  	power := int64(100)
   188  
   189  	pks := simapp.CreateTestPubKeys(3)
   190  	simapp.AddTestAddrsFromPubKeys(app, ctx, pks, app.StakingKeeper.TokensFromConsensusPower(ctx, 200))
   191  
   192  	addr, val := pks[0].Address(), pks[0]
   193  	consAddr := sdk.ConsAddress(addr)
   194  	tstaking := teststaking.NewHelper(t, ctx, app.StakingKeeper)
   195  	valAddr := sdk.ValAddress(addr)
   196  
   197  	tstaking.CreateValidatorWithValPower(valAddr, val, power, true)
   198  	staking.EndBlocker(ctx, app.StakingKeeper)
   199  
   200  	// 100 first blocks OK
   201  	height := int64(0)
   202  	for ; height < int64(100); height++ {
   203  		ctx = ctx.WithBlockHeight(height)
   204  		app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), power, true)
   205  	}
   206  
   207  	// kick first validator out of validator set
   208  	tstaking.CreateValidatorWithValPower(sdk.ValAddress(pks[1].Address()), pks[1], 101, true)
   209  	validatorUpdates := staking.EndBlocker(ctx, app.StakingKeeper)
   210  	require.Equal(t, 2, len(validatorUpdates))
   211  	tstaking.CheckValidator(valAddr, stakingtypes.Unbonding, false)
   212  
   213  	// 600 more blocks happened
   214  	height = int64(700)
   215  	ctx = ctx.WithBlockHeight(height)
   216  
   217  	// validator added back in
   218  	tstaking.DelegateWithPower(sdk.AccAddress(pks[2].Address()), sdk.ValAddress(pks[0].Address()), 50)
   219  
   220  	validatorUpdates = staking.EndBlocker(ctx, app.StakingKeeper)
   221  	require.Equal(t, 2, len(validatorUpdates))
   222  	tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false)
   223  	newPower := int64(150)
   224  
   225  	// validator misses 501 blocks exceeding the liveness threshold
   226  	latest := height
   227  	for ; height < latest+501; height++ {
   228  		ctx = ctx.WithBlockHeight(height)
   229  		app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false)
   230  	}
   231  
   232  	// 398 more blocks happened
   233  	latest = height
   234  	for ; height < latest+398; height++ {
   235  		ctx = ctx.WithBlockHeight(height)
   236  		app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true)
   237  	}
   238  
   239  	// shouldn't be jailed/kicked yet because it have not joined to vote set 1000 times
   240  	// 100 times + (kicked) + 501 times + 398 times = 999 times
   241  	tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false)
   242  
   243  	// check all the signing information
   244  	signInfo, found := app.SlashingKeeper.GetValidatorSigningInfo(ctx, consAddr)
   245  	require.True(t, found)
   246  	require.Equal(t, int64(0), signInfo.StartHeight)
   247  	require.Equal(t, int64(999), signInfo.IndexOffset)
   248  	require.Equal(t, int64(501), signInfo.MissedBlocksCounter)
   249  
   250  	// another block happened
   251  	ctx = ctx.WithBlockHeight(height)
   252  	app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true)
   253  	height++
   254  
   255  	// should now be jailed & kicked
   256  	staking.EndBlocker(ctx, app.StakingKeeper)
   257  	tstaking.CheckValidator(valAddr, stakingtypes.Unbonding, true)
   258  
   259  	// check all the signing information
   260  	signInfo, found = app.SlashingKeeper.GetValidatorSigningInfo(ctx, consAddr)
   261  	require.True(t, found)
   262  	require.Equal(t, int64(0), signInfo.StartHeight)
   263  	require.Equal(t, int64(0), signInfo.IndexOffset)
   264  	require.Equal(t, int64(0), signInfo.MissedBlocksCounter)
   265  	// array should be cleared
   266  	for offset := int64(0); offset < app.SlashingKeeper.SignedBlocksWindow(ctx); offset++ {
   267  		missed := app.SlashingKeeper.GetValidatorMissedBlockBitArray(ctx, consAddr, offset)
   268  		require.False(t, missed)
   269  	}
   270  
   271  	// some blocks pass
   272  	height = int64(5000)
   273  	ctx = ctx.WithBlockHeight(height)
   274  
   275  	// validator rejoins and starts signing again
   276  	app.StakingKeeper.Unjail(ctx, consAddr)
   277  	app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true)
   278  	height++
   279  
   280  	// validator should not be kicked since we reset counter/array when it was jailed
   281  	staking.EndBlocker(ctx, app.StakingKeeper)
   282  	tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false)
   283  
   284  	// 1000 blocks happened
   285  	latest = height
   286  	for ; height < latest+1000; height++ {
   287  		ctx = ctx.WithBlockHeight(height)
   288  		app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, true)
   289  	}
   290  
   291  	// validator misses 500 blocks
   292  	latest = height
   293  	for ; height < latest+500; height++ {
   294  		ctx = ctx.WithBlockHeight(height)
   295  		app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false)
   296  	}
   297  	tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false)
   298  
   299  	// validator misses another block
   300  	ctx = ctx.WithBlockHeight(height)
   301  	app.SlashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false)
   302  	height++
   303  
   304  	// validator should now be jailed & kicked
   305  	staking.EndBlocker(ctx, app.StakingKeeper)
   306  	tstaking.CheckValidator(valAddr, stakingtypes.Unbonding, true)
   307  }