github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/x/staking/keeper/slash.go (about) 1 package keeper 2 3 import ( 4 "fmt" 5 6 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 7 types "github.com/fibonacci-chain/fbc/libs/cosmos-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 := sdk.TokensFromConsensusPower(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(fmt.Sprintf( 52 "WARNING: Ignored attempt to slash a nonexistent validator with address %s, we recommend you investigate immediately", 53 consAddr)) 54 return 55 } 56 57 // should not be slashing an unbonded validator 58 if validator.IsUnbonded() { 59 panic(fmt.Sprintf("should not be slashing unbonded validator: %s", validator.GetOperator())) 60 } 61 62 operatorAddress := validator.GetOperator() 63 64 // call the before-modification hook 65 k.BeforeValidatorModified(ctx, operatorAddress) 66 67 // Track remaining slash amount for the validator 68 // This will decrease when we slash unbondings and 69 // redelegations, as that stake has since unbonded 70 remainingSlashAmount := slashAmount 71 72 switch { 73 case infractionHeight > ctx.BlockHeight(): 74 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 82 // Special-case slash at current height for efficiency - we don't need to look through unbonding delegations or redelegations 83 logger.Info(fmt.Sprintf( 84 "slashing at current height %d, not scanning unbonding delegations & redelegations", 85 infractionHeight)) 86 87 case infractionHeight < ctx.BlockHeight(): 88 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 remainingSlashAmount = remainingSlashAmount.Sub(amountSlashed) 97 } 98 99 // Iterate through redelegations from slashed source validator 100 redelegations := k.GetRedelegationsFromSrcValidator(ctx, operatorAddress) 101 for _, redelegation := range redelegations { 102 amountSlashed := k.slashRedelegation(ctx, validator, redelegation, infractionHeight, slashFactor) 103 if amountSlashed.IsZero() { 104 continue 105 } 106 remainingSlashAmount = remainingSlashAmount.Sub(amountSlashed) 107 } 108 } 109 110 // cannot decrease balance below zero 111 tokensToBurn := sdk.MinInt(remainingSlashAmount, validator.Tokens) 112 tokensToBurn = sdk.MaxInt(tokensToBurn, sdk.ZeroInt()) // defensive. 113 114 // we need to calculate the *effective* slash fraction for distribution 115 if validator.Tokens.IsPositive() { 116 effectiveFraction := tokensToBurn.ToDec().QuoRoundUp(validator.Tokens.ToDec()) 117 // possible if power has changed 118 if effectiveFraction.GT(sdk.OneDec()) { 119 effectiveFraction = sdk.OneDec() 120 } 121 // call the before-slashed hook 122 k.BeforeValidatorSlashed(ctx, operatorAddress, effectiveFraction) 123 } 124 125 // Deduct from validator's bonded tokens and update the validator. 126 // Burn the slashed tokens from the pool account and decrease the total supply. 127 validator = k.RemoveValidatorTokens(ctx, validator, tokensToBurn) 128 129 switch validator.GetStatus() { 130 case sdk.Bonded: 131 if err := k.burnBondedTokens(ctx, tokensToBurn); err != nil { 132 panic(err) 133 } 134 case sdk.Unbonding, sdk.Unbonded: 135 if err := k.burnNotBondedTokens(ctx, tokensToBurn); err != nil { 136 panic(err) 137 } 138 default: 139 panic("invalid validator status") 140 } 141 142 // Log that a slash occurred! 143 logger.Info(fmt.Sprintf( 144 "validator %s slashed by slash factor of %s; burned %v tokens", 145 validator.GetOperator(), slashFactor.String(), tokensToBurn)) 146 147 } 148 149 // jail a validator 150 func (k Keeper) Jail(ctx sdk.Context, consAddr sdk.ConsAddress) { 151 validator := k.mustGetValidatorByConsAddr(ctx, consAddr) 152 k.jailValidator(ctx, validator) 153 logger := k.Logger(ctx) 154 logger.Info(fmt.Sprintf("validator %s jailed", consAddr)) 155 } 156 157 // unjail a validator 158 func (k Keeper) Unjail(ctx sdk.Context, consAddr sdk.ConsAddress) { 159 validator := k.mustGetValidatorByConsAddr(ctx, consAddr) 160 k.unjailValidator(ctx, validator) 161 logger := k.Logger(ctx) 162 logger.Info(fmt.Sprintf("validator %s unjailed", consAddr)) 163 } 164 165 // slash an unbonding delegation and update the pool 166 // return the amount that would have been slashed assuming 167 // the unbonding delegation had enough stake to slash 168 // (the amount actually slashed may be less if there's 169 // insufficient stake remaining) 170 func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation types.UnbondingDelegation, 171 infractionHeight int64, slashFactor sdk.Dec) (totalSlashAmount sdk.Int) { 172 173 now := ctx.BlockHeader().Time 174 totalSlashAmount = sdk.ZeroInt() 175 burnedAmount := sdk.ZeroInt() 176 177 // perform slashing on all entries within the unbonding delegation 178 for i, entry := range unbondingDelegation.Entries { 179 180 // If unbonding started before this height, stake didn't contribute to infraction 181 if entry.CreationHeight < infractionHeight { 182 continue 183 } 184 185 if entry.IsMature(now) { 186 // Unbonding delegation no longer eligible for slashing, skip it 187 continue 188 } 189 190 // Calculate slash amount proportional to stake contributing to infraction 191 slashAmountDec := slashFactor.MulInt(entry.InitialBalance) 192 slashAmount := slashAmountDec.TruncateInt() 193 totalSlashAmount = totalSlashAmount.Add(slashAmount) 194 195 // Don't slash more tokens than held 196 // Possible since the unbonding delegation may already 197 // have been slashed, and slash amounts are calculated 198 // according to stake held at time of infraction 199 unbondingSlashAmount := sdk.MinInt(slashAmount, entry.Balance) 200 201 // Update unbonding delegation if necessary 202 if unbondingSlashAmount.IsZero() { 203 continue 204 } 205 206 burnedAmount = burnedAmount.Add(unbondingSlashAmount) 207 entry.Balance = entry.Balance.Sub(unbondingSlashAmount) 208 unbondingDelegation.Entries[i] = entry 209 k.SetUnbondingDelegation(ctx, unbondingDelegation) 210 } 211 212 if err := k.burnNotBondedTokens(ctx, burnedAmount); err != nil { 213 panic(err) 214 } 215 216 return totalSlashAmount 217 } 218 219 // slash a redelegation and update the pool 220 // return the amount that would have been slashed assuming 221 // the unbonding delegation had enough stake to slash 222 // (the amount actually slashed may be less if there's 223 // insufficient stake remaining) 224 // NOTE this is only slashing for prior infractions from the source validator 225 func (k Keeper) slashRedelegation(ctx sdk.Context, srcValidator types.Validator, redelegation types.Redelegation, 226 infractionHeight int64, slashFactor sdk.Dec) (totalSlashAmount sdk.Int) { 227 228 now := ctx.BlockHeader().Time 229 totalSlashAmount = sdk.ZeroInt() 230 bondedBurnedAmount, notBondedBurnedAmount := sdk.ZeroInt(), sdk.ZeroInt() 231 232 // perform slashing on all entries within the redelegation 233 for _, entry := range redelegation.Entries { 234 235 // If redelegation started before this height, stake didn't contribute to infraction 236 if entry.CreationHeight < infractionHeight { 237 continue 238 } 239 240 if entry.IsMature(now) { 241 // Redelegation no longer eligible for slashing, skip it 242 continue 243 } 244 245 // Calculate slash amount proportional to stake contributing to infraction 246 slashAmountDec := slashFactor.MulInt(entry.InitialBalance) 247 slashAmount := slashAmountDec.TruncateInt() 248 totalSlashAmount = totalSlashAmount.Add(slashAmount) 249 250 // Unbond from target validator 251 sharesToUnbond := slashFactor.Mul(entry.SharesDst) 252 if sharesToUnbond.IsZero() { 253 continue 254 } 255 delegation, found := k.GetDelegation(ctx, redelegation.DelegatorAddress, redelegation.ValidatorDstAddress) 256 if !found { 257 // If deleted, delegation has zero shares, and we can't unbond any more 258 continue 259 } 260 if sharesToUnbond.GT(delegation.Shares) { 261 sharesToUnbond = delegation.Shares 262 } 263 264 tokensToBurn, err := k.unbond(ctx, redelegation.DelegatorAddress, redelegation.ValidatorDstAddress, sharesToUnbond) 265 if err != nil { 266 panic(fmt.Errorf("error unbonding delegator: %v", err)) 267 } 268 269 dstValidator, found := k.GetValidator(ctx, redelegation.ValidatorDstAddress) 270 if !found { 271 panic("destination validator not found") 272 } 273 274 // tokens of a redelegation currently live in the destination validator 275 // therefor we must burn tokens from the destination-validator's bonding status 276 switch { 277 case dstValidator.IsBonded(): 278 bondedBurnedAmount = bondedBurnedAmount.Add(tokensToBurn) 279 case dstValidator.IsUnbonded() || dstValidator.IsUnbonding(): 280 notBondedBurnedAmount = notBondedBurnedAmount.Add(tokensToBurn) 281 default: 282 panic("unknown validator status") 283 } 284 } 285 286 if err := k.burnBondedTokens(ctx, bondedBurnedAmount); err != nil { 287 panic(err) 288 } 289 290 if err := k.burnNotBondedTokens(ctx, notBondedBurnedAmount); err != nil { 291 panic(err) 292 } 293 294 return totalSlashAmount 295 }