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 }