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  }