github.com/cosmos/cosmos-sdk@v0.50.10/x/slashing/keeper/infractions.go (about) 1 package keeper 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/cockroachdb/errors" 8 9 "cosmossdk.io/core/comet" 10 11 cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" 12 sdk "github.com/cosmos/cosmos-sdk/types" 13 "github.com/cosmos/cosmos-sdk/x/slashing/types" 14 stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 15 ) 16 17 // HandleValidatorSignature handles a validator signature, must be called once per validator per block. 18 func (k Keeper) HandleValidatorSignature(ctx context.Context, addr cryptotypes.Address, power int64, signed comet.BlockIDFlag) error { 19 sdkCtx := sdk.UnwrapSDKContext(ctx) 20 logger := k.Logger(ctx) 21 height := sdkCtx.BlockHeight() 22 23 // fetch the validator public key 24 consAddr := sdk.ConsAddress(addr) 25 26 // don't update missed blocks when validator's jailed 27 isJailed, err := k.sk.IsValidatorJailed(ctx, consAddr) 28 if err != nil { 29 return err 30 } 31 32 if isJailed { 33 return nil 34 } 35 36 // fetch signing info 37 signInfo, err := k.GetValidatorSigningInfo(ctx, consAddr) 38 if err != nil { 39 return err 40 } 41 42 signedBlocksWindow, err := k.SignedBlocksWindow(ctx) 43 if err != nil { 44 return err 45 } 46 47 // Compute the relative index, so we count the blocks the validator *should* 48 // have signed. We will use the 0-value default signing info if not present, 49 // except for start height. The index is in the range [0, SignedBlocksWindow) 50 // and is used to see if a validator signed a block at the given height, which 51 // is represented by a bit in the bitmap. 52 index := signInfo.IndexOffset % signedBlocksWindow 53 signInfo.IndexOffset++ 54 55 // determine if the validator signed the previous block 56 previous, err := k.GetMissedBlockBitmapValue(ctx, consAddr, index) 57 if err != nil { 58 return errors.Wrap(err, "failed to get the validator's bitmap value") 59 } 60 61 missed := signed == comet.BlockIDFlagAbsent 62 switch { 63 case !previous && missed: 64 // Bitmap value has changed from not missed to missed, so we flip the bit 65 // and increment the counter. 66 if err := k.SetMissedBlockBitmapValue(ctx, consAddr, index, true); err != nil { 67 return err 68 } 69 70 signInfo.MissedBlocksCounter++ 71 72 case previous && !missed: 73 // Bitmap value has changed from missed to not missed, so we flip the bit 74 // and decrement the counter. 75 if err := k.SetMissedBlockBitmapValue(ctx, consAddr, index, false); err != nil { 76 return err 77 } 78 79 signInfo.MissedBlocksCounter-- 80 81 default: 82 // bitmap value at this index has not changed, no need to update counter 83 } 84 85 minSignedPerWindow, err := k.MinSignedPerWindow(ctx) 86 if err != nil { 87 return err 88 } 89 90 if missed { 91 sdkCtx.EventManager().EmitEvent( 92 sdk.NewEvent( 93 types.EventTypeLiveness, 94 sdk.NewAttribute(types.AttributeKeyAddress, consAddr.String()), 95 sdk.NewAttribute(types.AttributeKeyMissedBlocks, fmt.Sprintf("%d", signInfo.MissedBlocksCounter)), 96 sdk.NewAttribute(types.AttributeKeyHeight, fmt.Sprintf("%d", height)), 97 ), 98 ) 99 100 logger.Debug( 101 "absent validator", 102 "height", height, 103 "validator", consAddr.String(), 104 "missed", signInfo.MissedBlocksCounter, 105 "threshold", minSignedPerWindow, 106 ) 107 } 108 109 minHeight := signInfo.StartHeight + signedBlocksWindow 110 maxMissed := signedBlocksWindow - minSignedPerWindow 111 112 // if we are past the minimum height and the validator has missed too many blocks, punish them 113 if height > minHeight && signInfo.MissedBlocksCounter > maxMissed { 114 validator, err := k.sk.ValidatorByConsAddr(ctx, consAddr) 115 if err != nil { 116 return err 117 } 118 if validator != nil && !validator.IsJailed() { 119 // Downtime confirmed: slash and jail the validator 120 // We need to retrieve the stake distribution which signed the block, so we subtract ValidatorUpdateDelay from the evidence height, 121 // and subtract an additional 1 since this is the LastCommit. 122 // Note that this *can* result in a negative "distributionHeight" up to -ValidatorUpdateDelay-1, 123 // i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block. 124 // That's fine since this is just used to filter unbonding delegations & redelegations. 125 distributionHeight := height - sdk.ValidatorUpdateDelay - 1 126 127 slashFractionDowntime, err := k.SlashFractionDowntime(ctx) 128 if err != nil { 129 return err 130 } 131 132 coinsBurned, err := k.sk.SlashWithInfractionReason(ctx, consAddr, distributionHeight, power, slashFractionDowntime, stakingtypes.Infraction_INFRACTION_DOWNTIME) 133 if err != nil { 134 return err 135 } 136 137 sdkCtx.EventManager().EmitEvent( 138 sdk.NewEvent( 139 types.EventTypeSlash, 140 sdk.NewAttribute(types.AttributeKeyAddress, consAddr.String()), 141 sdk.NewAttribute(types.AttributeKeyPower, fmt.Sprintf("%d", power)), 142 sdk.NewAttribute(types.AttributeKeyReason, types.AttributeValueMissingSignature), 143 sdk.NewAttribute(types.AttributeKeyJailed, consAddr.String()), 144 sdk.NewAttribute(types.AttributeKeyBurnedCoins, coinsBurned.String()), 145 ), 146 ) 147 k.sk.Jail(sdkCtx, consAddr) 148 149 downtimeJailDur, err := k.DowntimeJailDuration(ctx) 150 if err != nil { 151 return err 152 } 153 signInfo.JailedUntil = sdkCtx.BlockHeader().Time.Add(downtimeJailDur) 154 155 // We need to reset the counter & bitmap so that the validator won't be 156 // immediately slashed for downtime upon re-bonding. 157 signInfo.MissedBlocksCounter = 0 158 signInfo.IndexOffset = 0 159 err = k.DeleteMissedBlockBitmap(ctx, consAddr) 160 if err != nil { 161 return err 162 } 163 164 logger.Info( 165 "slashing and jailing validator due to liveness fault", 166 "height", height, 167 "validator", consAddr.String(), 168 "min_height", minHeight, 169 "threshold", minSignedPerWindow, 170 "slashed", slashFractionDowntime.String(), 171 "jailed_until", signInfo.JailedUntil, 172 ) 173 } else { 174 // validator was (a) not found or (b) already jailed so we do not slash 175 logger.Info( 176 "validator would have been slashed for downtime, but was either not found in store or already jailed", 177 "validator", consAddr.String(), 178 ) 179 } 180 } 181 182 // Set the updated signing info 183 return k.SetValidatorSigningInfo(ctx, consAddr, signInfo) 184 }