cosmos-sdk/docs/spec/slashing/end_block.md

3.3 KiB

End-Block

Slashing

Tendermint blocks can include Evidence, which indicates that a validator committed malicious behaviour. The relevant information is forwarded to the application as ABCI Evidence, so the validator an be accordingly punished.

For some evidence to be valid, it must satisfy:

evidence.Timestamp >= block.Timestamp - MAX_EVIDENCE_AGE

where evidence.Timestamp is the timestamp in the block at height evidence.Height and block.Timestamp is the current block timestamp.

If valid evidence is included in a block, the validator's stake is reduced by SLASH_PROPORTION of what their stake was when the equivocation occurred (rather than when the evidence was discovered):

curVal := validator
oldVal := loadValidator(evidence.Height, evidence.Address)

slashAmount := SLASH_PROPORTION * oldVal.Shares

curVal.Shares = max(0, curVal.Shares - slashAmount)

This ensures that offending validators are punished the same amount whether they act as a single validator with X stake or as N validators with collectively X stake.

We also need to loop through the unbondings and redelegations to slash them as well:

unbondings := getUnbondings(validator.Address)
for unbond in unbondings {
    if was not bonded before evidence.Height {
        continue
    }
    unbond.InitialTokens
    burn := unbond.InitialTokens * SLASH_PROPORTION
    unbond.Tokens = max(0, unbond.Tokens - burn)
}

// only care if source gets slashed because we're already bonded to destination
// so if destination validator gets slashed our delegation just has same shares
// of smaller pool.
redels := getRedelegationsBySource(validator.Address)
for redel in redels {

    if was not bonded before evidence.Height {
        continue
    }

    burn := redel.InitialTokens * SLASH_PROPORTION

    amount := unbondFromValidator(redel.Destination, burn)
    destroy(amount)
}

Automatic Unbonding

At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded:

height := block.Height

for val in block.Validators:
  signInfo = SigningInfo.Get(val.Address)
  if signInfo == nil{
        signInfo.StartHeight = height
  }

  index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW
  signInfo.IndexOffset++
  previous = SigningBitArray.Get(val.Address, index)

  // update counter if array has changed
  if previous and val in block.AbsentValidators:
    SigningBitArray.Set(val.Address, index, false)
    signInfo.SignedBlocksCounter--
  else if !previous and val not in block.AbsentValidators:
    SigningBitArray.Set(val.Address, index, true)
    signInfo.SignedBlocksCounter++
  // else previous == val not in block.AbsentValidators, no change

  // validator must be active for at least SIGNED_BLOCKS_WINDOW
  // before they can be automatically unbonded for failing to be 
  // included in 50% of the recent LastCommits
  minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW
  minSigned = SIGNED_BLOCKS_WINDOW / 2
  if height > minHeight AND signInfo.SignedBlocksCounter < minSigned:
    signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION

    slash & unbond the validator

  SigningInfo.Set(val.Address, signInfo)