github.com/cosmos/cosmos-sdk@v0.50.10/x/slashing/keeper/slash_redelegation_test.go (about)

     1  package keeper_test
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"cosmossdk.io/core/header"
    11  	"cosmossdk.io/depinject"
    12  	"cosmossdk.io/log"
    13  	"cosmossdk.io/math"
    14  
    15  	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
    16  	simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
    17  	sdk "github.com/cosmos/cosmos-sdk/types"
    18  	bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
    19  	banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil"
    20  	distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
    21  	slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper"
    22  	"github.com/cosmos/cosmos-sdk/x/slashing/testutil"
    23  	stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
    24  	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
    25  )
    26  
    27  func TestSlashRedelegation(t *testing.T) {
    28  	// setting up
    29  	var stakingKeeper *stakingkeeper.Keeper
    30  	var bankKeeper bankkeeper.Keeper
    31  	var slashKeeper slashingkeeper.Keeper
    32  	var distrKeeper distributionkeeper.Keeper
    33  
    34  	app, err := simtestutil.Setup(depinject.Configs(
    35  		depinject.Supply(log.NewNopLogger()),
    36  		testutil.AppConfig,
    37  	), &stakingKeeper, &bankKeeper, &slashKeeper, &distrKeeper)
    38  	require.NoError(t, err)
    39  
    40  	// get sdk context, staking msg server and bond denom
    41  	ctx := app.BaseApp.NewContext(false)
    42  	stakingMsgServer := stakingkeeper.NewMsgServerImpl(stakingKeeper)
    43  	bondDenom, err := stakingKeeper.BondDenom(ctx)
    44  	require.NoError(t, err)
    45  
    46  	// evilVal will be slashed, goodVal won't be slashed
    47  	evilValPubKey := secp256k1.GenPrivKey().PubKey()
    48  	goodValPubKey := secp256k1.GenPrivKey().PubKey()
    49  
    50  	// both test acc 1 and 2 delegated to evil val, both acc should be slashed when evil val is slashed
    51  	// test acc 1 use the "undelegation after redelegation" trick (redelegate to good val and then undelegate) to avoid slashing
    52  	// test acc 2 only undelegate from evil val
    53  	testAcc1 := sdk.AccAddress([]byte("addr1_______________"))
    54  	testAcc2 := sdk.AccAddress([]byte("addr2_______________"))
    55  
    56  	// fund acc 1 and acc 2
    57  	testCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, stakingKeeper.TokensFromConsensusPower(ctx, 10)))
    58  	banktestutil.FundAccount(ctx, bankKeeper, testAcc1, testCoins)
    59  	banktestutil.FundAccount(ctx, bankKeeper, testAcc2, testCoins)
    60  
    61  	balance1Before := bankKeeper.GetBalance(ctx, testAcc1, bondDenom)
    62  	balance2Before := bankKeeper.GetBalance(ctx, testAcc2, bondDenom)
    63  
    64  	// assert acc 1 and acc 2 balance
    65  	require.Equal(t, balance1Before.Amount.String(), testCoins[0].Amount.String())
    66  	require.Equal(t, balance2Before.Amount.String(), testCoins[0].Amount.String())
    67  
    68  	// creating evil val
    69  	evilValAddr := sdk.ValAddress(evilValPubKey.Address())
    70  	banktestutil.FundAccount(ctx, bankKeeper, sdk.AccAddress(evilValAddr), testCoins)
    71  	createValMsg1, _ := stakingtypes.NewMsgCreateValidator(
    72  		evilValAddr.String(), evilValPubKey, testCoins[0], stakingtypes.Description{Details: "test"}, stakingtypes.NewCommissionRates(math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDec(0)), math.OneInt())
    73  	_, err = stakingMsgServer.CreateValidator(ctx, createValMsg1)
    74  	require.NoError(t, err)
    75  
    76  	// creating good val
    77  	goodValAddr := sdk.ValAddress(goodValPubKey.Address())
    78  	banktestutil.FundAccount(ctx, bankKeeper, sdk.AccAddress(goodValAddr), testCoins)
    79  	createValMsg2, _ := stakingtypes.NewMsgCreateValidator(
    80  		goodValAddr.String(), goodValPubKey, testCoins[0], stakingtypes.Description{Details: "test"}, stakingtypes.NewCommissionRates(math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDec(0)), math.OneInt())
    81  	_, err = stakingMsgServer.CreateValidator(ctx, createValMsg2)
    82  	require.NoError(t, err)
    83  
    84  	// next block, commit height 2, move to height 3
    85  	// acc 1 and acc 2 delegate to evil val
    86  	ctx = ctx.WithBlockHeight(app.LastBlockHeight() + 1).WithHeaderInfo(header.Info{Height: app.LastBlockHeight() + 1})
    87  	fmt.Println()
    88  	ctx, err = simtestutil.NextBlock(app, ctx, time.Duration(1))
    89  	require.NoError(t, err)
    90  
    91  	// Acc 2 delegate
    92  	delMsg := stakingtypes.NewMsgDelegate(testAcc2.String(), evilValAddr.String(), testCoins[0])
    93  	_, err = stakingMsgServer.Delegate(ctx, delMsg)
    94  	require.NoError(t, err)
    95  
    96  	// Acc 1 delegate
    97  	delMsg = stakingtypes.NewMsgDelegate(testAcc1.String(), evilValAddr.String(), testCoins[0])
    98  	_, err = stakingMsgServer.Delegate(ctx, delMsg)
    99  	require.NoError(t, err)
   100  
   101  	// next block, commit height 3, move to height 4
   102  	// with the new delegations, evil val increases in voting power and commit byzantine behavior at height 4 consensus
   103  	// at the same time, acc 1 and acc 2 withdraw delegation from evil val
   104  	ctx, err = simtestutil.NextBlock(app, ctx, time.Duration(1))
   105  	require.NoError(t, err)
   106  
   107  	evilVal, err := stakingKeeper.GetValidator(ctx, evilValAddr)
   108  	require.NoError(t, err)
   109  
   110  	evilPower := stakingKeeper.TokensToConsensusPower(ctx, evilVal.Tokens)
   111  
   112  	// Acc 1 redelegate from evil val to good val
   113  	redelMsg := stakingtypes.NewMsgBeginRedelegate(testAcc1.String(), evilValAddr.String(), goodValAddr.String(), testCoins[0])
   114  	_, err = stakingMsgServer.BeginRedelegate(ctx, redelMsg)
   115  	require.NoError(t, err)
   116  
   117  	// Acc 1 undelegate from good val
   118  	undelMsg := stakingtypes.NewMsgUndelegate(testAcc1.String(), goodValAddr.String(), testCoins[0])
   119  	_, err = stakingMsgServer.Undelegate(ctx, undelMsg)
   120  	require.NoError(t, err)
   121  
   122  	// Acc 2 undelegate from evil val
   123  	undelMsg = stakingtypes.NewMsgUndelegate(testAcc2.String(), evilValAddr.String(), testCoins[0])
   124  	_, err = stakingMsgServer.Undelegate(ctx, undelMsg)
   125  	require.NoError(t, err)
   126  
   127  	// next block, commit height 4, move to height 5
   128  	// Slash evil val for byzantine behavior at height 4 consensus,
   129  	// at which acc 1 and acc 2 still contributed to evil val voting power
   130  	// even tho they undelegate at block 4, the valset update is applied after committed block 4 when height 4 consensus already passes
   131  	ctx, err = simtestutil.NextBlock(app, ctx, time.Duration(1))
   132  	require.NoError(t, err)
   133  
   134  	// slash evil val with slash factor = 0.9, leaving only 10% of stake after slashing
   135  	evilVal, _ = stakingKeeper.GetValidator(ctx, evilValAddr)
   136  	evilValConsAddr, err := evilVal.GetConsAddr()
   137  	require.NoError(t, err)
   138  
   139  	err = slashKeeper.Slash(ctx, evilValConsAddr, math.LegacyMustNewDecFromStr("0.9"), evilPower, 3)
   140  	require.NoError(t, err)
   141  
   142  	// assert invariant to make sure we conduct slashing correctly
   143  	_, stop := stakingkeeper.AllInvariants(stakingKeeper)(ctx)
   144  	require.False(t, stop)
   145  
   146  	_, stop = bankkeeper.AllInvariants(bankKeeper)(ctx)
   147  	require.False(t, stop)
   148  
   149  	_, stop = distributionkeeper.AllInvariants(distrKeeper)(ctx)
   150  	require.False(t, stop)
   151  
   152  	// one eternity later
   153  	ctx, err = simtestutil.NextBlock(app, ctx, time.Duration(1000000000000000000))
   154  	require.NoError(t, err)
   155  
   156  	ctx, err = simtestutil.NextBlock(app, ctx, time.Duration(1))
   157  	require.NoError(t, err)
   158  
   159  	// confirm that account 1 and account 2 has been slashed, and the slash amount is correct
   160  	balance1AfterSlashing := bankKeeper.GetBalance(ctx, testAcc1, bondDenom)
   161  	balance2AfterSlashing := bankKeeper.GetBalance(ctx, testAcc2, bondDenom)
   162  
   163  	require.Equal(t, balance1AfterSlashing.Amount.Mul(math.NewIntFromUint64(10)).String(), balance1Before.Amount.String())
   164  	require.Equal(t, balance2AfterSlashing.Amount.Mul(math.NewIntFromUint64(10)).String(), balance2Before.Amount.String())
   165  }