github.com/Finschia/finschia-sdk@v0.48.1/x/staking/keeper/slash.go (about) 1 package keeper 2 3 import ( 4 "fmt" 5 6 sdk "github.com/Finschia/finschia-sdk/types" 7 types "github.com/Finschia/finschia-sdk/x/staking/types" 8 ) 9 10 // Slash a validator for an infraction committed at a known height 11 // Find the contributing stake at that height and burn the specified slashFactor 12 // of it, updating unbonding delegations & redelegations appropriately 13 // 14 // CONTRACT: 15 // 16 // slashFactor is non-negative 17 // 18 // CONTRACT: 19 // 20 // Infraction was committed equal to or less than an unbonding period in the past, 21 // so all unbonding delegations and redelegations from that height are stored 22 // 23 // CONTRACT: 24 // 25 // Slash will not slash unbonded validators (for the above reason) 26 // 27 // CONTRACT: 28 // 29 // Infraction was committed at the current height or at a past height, 30 // not at a height in the future 31 func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeight int64, power int64, slashFactor sdk.Dec) { 32 logger := k.Logger(ctx) 33 34 if slashFactor.IsNegative() { 35 panic(fmt.Errorf("attempted to slash with a negative slash factor: %v", slashFactor)) 36 } 37 38 // Amount of slashing = slash slashFactor * power at time of infraction 39 amount := k.TokensFromConsensusPower(ctx, power) 40 slashAmountDec := amount.ToDec().Mul(slashFactor) 41 slashAmount := slashAmountDec.TruncateInt() 42 43 // ref https://github.com/cosmos/cosmos-sdk/issues/1348 44 45 validator, found := k.GetValidatorByConsAddr(ctx, consAddr) 46 if !found { 47 // If not found, the validator must have been overslashed and removed - so we don't need to do anything 48 // NOTE: Correctness dependent on invariant that unbonding delegations / redelegations must also have been completely 49 // slashed in this case - which we don't explicitly check, but should be true. 50 // Log the slash attempt for future reference (maybe we should tag it too) 51 logger.Error( 52 "WARNING: ignored attempt to slash a nonexistent validator; we recommend you investigate immediately", 53 "validator", consAddr.String(), 54 ) 55 return 56 } 57 58 // should not be slashing an unbonded validator 59 if validator.IsUnbonded() { 60 panic(fmt.Sprintf("should not be slashing unbonded validator: %s", validator.GetOperator())) 61 } 62 63 operatorAddress := validator.GetOperator() 64 65 // call the before-modification hook 66 k.BeforeValidatorModified(ctx, operatorAddress) 67 68 // Track remaining slash amount for the validator 69 // This will decrease when we slash unbondings and 70 // redelegations, as that stake has since unbonded 71 remainingSlashAmount := slashAmount 72 73 switch { 74 case infractionHeight > ctx.BlockHeight(): 75 // Can't slash infractions in the future 76 panic(fmt.Sprintf( 77 "impossible attempt to slash future infraction at height %d but we are at height %d", 78 infractionHeight, ctx.BlockHeight())) 79 80 case infractionHeight == ctx.BlockHeight(): 81 // Special-case slash at current height for efficiency - we don't need to 82 // look through unbonding delegations or redelegations. 83 logger.Info( 84 "slashing at current height; not scanning unbonding delegations & redelegations", 85 "height", infractionHeight, 86 ) 87 88 case infractionHeight < ctx.BlockHeight(): 89 // Iterate through unbonding delegations from slashed validator 90 unbondingDelegations := k.GetUnbondingDelegationsFromValidator(ctx, operatorAddress) 91 for _, unbondingDelegation := range unbondingDelegations { 92 amountSlashed := k.SlashUnbondingDelegation(ctx, unbondingDelegation, infractionHeight, slashFactor) 93 if amountSlashed.IsZero() { 94 continue 95 } 96 97 remainingSlashAmount = remainingSlashAmount.Sub(amountSlashed) 98 } 99 100 // Iterate through redelegations from slashed source validator 101 redelegations := k.GetRedelegationsFromSrcValidator(ctx, operatorAddress) 102 for _, redelegation := range redelegations { 103 amountSlashed := k.SlashRedelegation(ctx, validator, redelegation, infractionHeight, slashFactor) 104 if amountSlashed.IsZero() { 105 continue 106 } 107 108 remainingSlashAmount = remainingSlashAmount.Sub(amountSlashed) 109 } 110 } 111 112 // cannot decrease balance below zero 113 tokensToBurn := sdk.MinInt(remainingSlashAmount, validator.Tokens) 114 tokensToBurn = sdk.MaxInt(tokensToBurn, sdk.ZeroInt()) // defensive. 115 116 // we need to calculate the *effective* slash fraction for distribution 117 if validator.Tokens.IsPositive() { 118 effectiveFraction := tokensToBurn.ToDec().QuoRoundUp(validator.Tokens.ToDec()) 119 // possible if power has changed 120 if effectiveFraction.GT(sdk.OneDec()) { 121 effectiveFraction = sdk.OneDec() 122 } 123 // call the before-slashed hook 124 k.BeforeValidatorSlashed(ctx, operatorAddress, effectiveFraction) 125 } 126 127 // Deduct from validator's bonded tokens and update the validator. 128 // Burn the slashed tokens from the pool account and decrease the total supply. 129 validator = k.RemoveValidatorTokens(ctx, validator, tokensToBurn) 130 131 switch validator.GetStatus() { 132 case types.Bonded: 133 if err := k.burnBondedTokens(ctx, tokensToBurn); err != nil { 134 panic(err) 135 } 136 case types.Unbonding, types.Unbonded: 137 if err := k.burnNotBondedTokens(ctx, tokensToBurn); err != nil { 138 panic(err) 139 } 140 default: 141 panic("invalid validator status") 142 } 143 144 logger.Info( 145 "validator slashed by slash factor", 146 "validator", validator.GetOperator().String(), 147 "slash_factor", slashFactor.String(), 148 "burned", tokensToBurn, 149 ) 150 } 151 152 // jail a validator 153 func (k Keeper) Jail(ctx sdk.Context, consAddr sdk.ConsAddress) { 154 validator := k.mustGetValidatorByConsAddr(ctx, consAddr) 155 k.jailValidator(ctx, validator) 156 logger := k.Logger(ctx) 157 logger.Info("validator jailed", "validator", consAddr) 158 } 159 160 // unjail a validator 161 func (k Keeper) Unjail(ctx sdk.Context, consAddr sdk.ConsAddress) { 162 validator := k.mustGetValidatorByConsAddr(ctx, consAddr) 163 k.unjailValidator(ctx, validator) 164 logger := k.Logger(ctx) 165 logger.Info("validator un-jailed", "validator", consAddr) 166 } 167 168 // slash an unbonding delegation and update the pool 169 // return the amount that would have been slashed assuming 170 // the unbonding delegation had enough stake to slash 171 // (the amount actually slashed may be less if there's 172 // insufficient stake remaining) 173 func (k Keeper) SlashUnbondingDelegation(ctx sdk.Context, unbondingDelegation types.UnbondingDelegation, 174 infractionHeight int64, slashFactor sdk.Dec, 175 ) (totalSlashAmount sdk.Int) { 176 now := ctx.BlockHeader().Time 177 totalSlashAmount = sdk.ZeroInt() 178 burnedAmount := sdk.ZeroInt() 179 180 // perform slashing on all entries within the unbonding delegation 181 for i, entry := range unbondingDelegation.Entries { 182 // If unbonding started before this height, stake didn't contribute to infraction 183 if entry.CreationHeight < infractionHeight { 184 continue 185 } 186 187 if entry.IsMature(now) { 188 // Unbonding delegation no longer eligible for slashing, skip it 189 continue 190 } 191 192 // Calculate slash amount proportional to stake contributing to infraction 193 slashAmountDec := slashFactor.MulInt(entry.InitialBalance) 194 slashAmount := slashAmountDec.TruncateInt() 195 totalSlashAmount = totalSlashAmount.Add(slashAmount) 196 197 // Don't slash more tokens than held 198 // Possible since the unbonding delegation may already 199 // have been slashed, and slash amounts are calculated 200 // according to stake held at time of infraction 201 unbondingSlashAmount := sdk.MinInt(slashAmount, entry.Balance) 202 203 // Update unbonding delegation if necessary 204 if unbondingSlashAmount.IsZero() { 205 continue 206 } 207 208 burnedAmount = burnedAmount.Add(unbondingSlashAmount) 209 entry.Balance = entry.Balance.Sub(unbondingSlashAmount) 210 unbondingDelegation.Entries[i] = entry 211 k.SetUnbondingDelegation(ctx, unbondingDelegation) 212 } 213 214 if err := k.burnNotBondedTokens(ctx, burnedAmount); err != nil { 215 panic(err) 216 } 217 218 return totalSlashAmount 219 } 220 221 // slash a redelegation and update the pool 222 // return the amount that would have been slashed assuming 223 // the unbonding delegation had enough stake to slash 224 // (the amount actually slashed may be less if there's 225 // insufficient stake remaining) 226 // NOTE this is only slashing for prior infractions from the source validator 227 func (k Keeper) SlashRedelegation(ctx sdk.Context, srcValidator types.Validator, redelegation types.Redelegation, 228 infractionHeight int64, slashFactor sdk.Dec, 229 ) (totalSlashAmount sdk.Int) { 230 now := ctx.BlockHeader().Time 231 totalSlashAmount = sdk.ZeroInt() 232 bondedBurnedAmount, notBondedBurnedAmount := sdk.ZeroInt(), sdk.ZeroInt() 233 234 // perform slashing on all entries within the redelegation 235 for _, entry := range redelegation.Entries { 236 // If redelegation started before this height, stake didn't contribute to infraction 237 if entry.CreationHeight < infractionHeight { 238 continue 239 } 240 241 if entry.IsMature(now) { 242 // Redelegation no longer eligible for slashing, skip it 243 continue 244 } 245 246 // Calculate slash amount proportional to stake contributing to infraction 247 slashAmountDec := slashFactor.MulInt(entry.InitialBalance) 248 slashAmount := slashAmountDec.TruncateInt() 249 totalSlashAmount = totalSlashAmount.Add(slashAmount) 250 251 // Unbond from target validator 252 sharesToUnbond := slashFactor.Mul(entry.SharesDst) 253 if sharesToUnbond.IsZero() { 254 continue 255 } 256 257 valDstAddr, err := sdk.ValAddressFromBech32(redelegation.ValidatorDstAddress) 258 if err != nil { 259 panic(err) 260 } 261 262 delegatorAddress := sdk.MustAccAddressFromBech32(redelegation.DelegatorAddress) 263 264 delegation, found := k.GetDelegation(ctx, delegatorAddress, valDstAddr) 265 if !found { 266 // If deleted, delegation has zero shares, and we can't unbond any more 267 continue 268 } 269 270 if sharesToUnbond.GT(delegation.Shares) { 271 sharesToUnbond = delegation.Shares 272 } 273 274 tokensToBurn, err := k.Unbond(ctx, delegatorAddress, valDstAddr, sharesToUnbond) 275 if err != nil { 276 panic(fmt.Errorf("error unbonding delegator: %v", err)) 277 } 278 279 dstValidator, found := k.GetValidator(ctx, valDstAddr) 280 if !found { 281 panic("destination validator not found") 282 } 283 284 // tokens of a redelegation currently live in the destination validator 285 // therefor we must burn tokens from the destination-validator's bonding status 286 switch { 287 case dstValidator.IsBonded(): 288 bondedBurnedAmount = bondedBurnedAmount.Add(tokensToBurn) 289 case dstValidator.IsUnbonded() || dstValidator.IsUnbonding(): 290 notBondedBurnedAmount = notBondedBurnedAmount.Add(tokensToBurn) 291 default: 292 panic("unknown validator status") 293 } 294 } 295 296 if err := k.burnBondedTokens(ctx, bondedBurnedAmount); err != nil { 297 panic(err) 298 } 299 300 if err := k.burnNotBondedTokens(ctx, notBondedBurnedAmount); err != nil { 301 panic(err) 302 } 303 304 return totalSlashAmount 305 }