2018-06-15 03:22:06 -07:00
|
|
|
# End-Block
|
2018-05-25 15:52:34 -07:00
|
|
|
|
|
|
|
## Slashing
|
|
|
|
|
2018-06-15 23:26:09 -07:00
|
|
|
Tendermint blocks can include
|
|
|
|
[Evidence](https://github.com/tendermint/tendermint/blob/develop/docs/spec/blockchain/blockchain.md#evidence), which indicates that a validator
|
|
|
|
committed malicious behaviour. The relevant information is forwarded to the
|
|
|
|
application as [ABCI
|
|
|
|
Evidence](https://github.com/tendermint/abci/blob/develop/types/types.proto#L259), so the validator an be accordingly punished.
|
2018-05-25 15:52:34 -07:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2018-06-14 20:26:21 -07:00
|
|
|
If valid evidence is included in a block, the validator's stake is reduced by `SLASH_PROPORTION` of
|
2018-06-15 14:26:14 -07:00
|
|
|
what their stake was when the equivocation occurred (rather than when the evidence was discovered):
|
2018-05-25 15:52:34 -07:00
|
|
|
|
|
|
|
```
|
2018-06-14 20:26:21 -07:00
|
|
|
curVal := validator
|
|
|
|
oldVal := loadValidator(evidence.Height, evidence.Address)
|
|
|
|
|
|
|
|
slashAmount := SLASH_PROPORTION * oldVal.Shares
|
|
|
|
|
2018-06-15 14:26:14 -07:00
|
|
|
curVal.Shares = max(0, curVal.Shares - slashAmount)
|
2018-05-25 15:52:34 -07:00
|
|
|
```
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
2018-06-14 20:26:21 -07:00
|
|
|
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
|
2018-06-15 14:26:14 -07:00
|
|
|
unbond.Tokens = max(0, unbond.Tokens - burn)
|
2018-06-14 20:26:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
```
|
2018-05-25 15:52:34 -07:00
|
|
|
|
|
|
|
## Automatic Unbonding
|
|
|
|
|
|
|
|
At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded:
|
|
|
|
|
|
|
|
```
|
2018-05-30 15:54:46 -07:00
|
|
|
height := block.Height
|
2018-05-25 15:52:34 -07:00
|
|
|
|
|
|
|
for val in block.Validators:
|
2018-06-15 03:22:06 -07:00
|
|
|
signInfo = SigningInfo.Get(val.Address)
|
2018-06-14 20:26:21 -07:00
|
|
|
if signInfo == nil{
|
|
|
|
signInfo.StartHeight = height
|
|
|
|
}
|
|
|
|
|
2018-05-30 15:54:46 -07:00
|
|
|
index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW
|
|
|
|
signInfo.IndexOffset++
|
2018-06-15 03:22:06 -07:00
|
|
|
previous = SigningBitArray.Get(val.Address, index)
|
2018-05-30 15:54:46 -07:00
|
|
|
|
|
|
|
// update counter if array has changed
|
|
|
|
if previous and val in block.AbsentValidators:
|
2018-06-15 03:22:06 -07:00
|
|
|
SigningBitArray.Set(val.Address, index, false)
|
2018-05-30 15:54:46 -07:00
|
|
|
signInfo.SignedBlocksCounter--
|
|
|
|
else if !previous and val not in block.AbsentValidators:
|
2018-06-15 03:22:06 -07:00
|
|
|
SigningBitArray.Set(val.Address, index, true)
|
2018-05-30 15:54:46 -07:00
|
|
|
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
|
2018-06-14 20:26:21 -07:00
|
|
|
|
2018-05-30 15:54:46 -07:00
|
|
|
slash & unbond the validator
|
2018-06-14 20:26:21 -07:00
|
|
|
|
2018-06-15 03:22:06 -07:00
|
|
|
SigningInfo.Set(val.Address, signInfo)
|
2018-05-25 15:52:34 -07:00
|
|
|
```
|