cosmos-sdk/docs/spec/slashing/04_begin_block.md

125 lines
4.5 KiB
Markdown
Raw Normal View History

2018-08-13 07:04:35 -07:00
# Begin-Block
2018-05-25 15:52:34 -07:00
2018-08-13 07:12:59 -07:00
## Evidence handling
2018-05-25 15:52:34 -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
2018-08-20 06:07:23 -07:00
committed malicious behavior. The relevant information is forwarded to the
application as [ABCI
2018-08-13 07:04:35 -07:00
Evidence](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto#L259) in `abci.RequestBeginBlock`
so that the validator an be accordingly punished.
2018-05-25 15:52:34 -07:00
For some `evidence` to be valid, it must satisfy:
2018-05-25 15:52:34 -07:00
`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 infraction occurred (rather than when the evidence was discovered).
We want to "follow the stake": the stake which contributed to the infraction should be
slashed, even if it has since been redelegated or started unbonding.
2018-05-25 15:52:34 -07:00
We first need to loop through the unbondings and redelegations from the slashed validator
and track how much stake has since moved:
2018-06-14 20:26:21 -07:00
2018-05-25 15:52:34 -07:00
```
slashAmountUnbondings := 0
slashAmountRedelegations := 0
2018-05-25 15:52:34 -07:00
2018-06-14 20:26:21 -07:00
unbondings := getUnbondings(validator.Address)
for unbond in unbondings {
if was not bonded before evidence.Height or started unbonding before unbonding period ago {
2018-06-14 20:26:21 -07:00
continue
}
2018-06-14 20:26:21 -07:00
burn := unbond.InitialTokens * SLASH_PROPORTION
slashAmountUnbondings += burn
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 or started redelegating before unbonding period ago {
2018-06-14 20:26:21 -07:00
continue
}
burn := redel.InitialTokens * SLASH_PROPORTION
slashAmountRedelegations += burn
2018-06-14 20:26:21 -07:00
amount := unbondFromValidator(redel.Destination, burn)
destroy(amount)
}
```
2018-05-25 15:52:34 -07:00
2019-01-10 17:22:49 -08:00
We then slash the validator and tombstone them:
```
curVal := validator
oldVal := loadValidator(evidence.Height, evidence.Address)
slashAmount := SLASH_PROPORTION * oldVal.Shares
slashAmount -= slashAmountUnbondings
slashAmount -= slashAmountRedelegations
curVal.Shares = max(0, curVal.Shares - slashAmount)
2019-01-10 17:22:49 -08:00
signInfo = SigningInfo.Get(val.Address)
signInfo.JailedUntil = MAX_TIME
signInfo.Tombstoned = true
SigningInfo.Set(val.Address, signInfo)
```
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
2019-01-10 17:22:49 -08:00
stake. The amount slashed for all double signature infractions committed within a
single slashing period is capped as described in [overview.md](overview.md) under Tombstone Caps.
2018-08-13 07:12:59 -07:00
## Uptime tracking
2018-05-25 15:52:34 -07:00
At the beginning of each block, we update the signing info for each validator and check if they've dipped below the liveness threshold over the tracked window. If so, they will be slashed by `LivenessSlashAmount` and will be Jailed for `LivenessJailPeriod`. Liveness slashes do NOT lead to a tombstombing.
2018-05-25 15:52:34 -07:00
```
height := block.Height
2018-05-25 15:52:34 -07:00
for val in block.Validators:
signInfo = SigningInfo.Get(val.Address)
2018-06-14 20:26:21 -07:00
if signInfo == nil{
signInfo.StartHeight = height
}
index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW
signInfo.IndexOffset++
2018-10-15 14:01:29 -07:00
previous = MissedBlockBitArray.Get(val.Address, index)
// update counter if array has changed
2018-10-11 16:04:57 -07:00
if !previous and val in block.AbsentValidators:
2018-10-15 14:01:29 -07:00
MissedBlockBitArray.Set(val.Address, index, true)
2018-10-11 16:04:57 -07:00
signInfo.MissedBlocksCounter++
else if previous and val not in block.AbsentValidators:
2018-10-15 14:01:29 -07:00
MissedBlockBitArray.Set(val.Address, index, false)
2018-10-11 16:04:57 -07:00
signInfo.MissedBlocksCounter--
// 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
2018-10-11 16:04:57 -07:00
maxMissed = SIGNED_BLOCKS_WINDOW / 2
2018-10-12 12:15:39 -07:00
if height > minHeight AND signInfo.MissedBlocksCounter > maxMissed:
signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION
2018-10-15 14:01:29 -07:00
signInfo.IndexOffset = 0
signInfo.MissedBlocksCounter = 0
clearMissedBlockBitArray()
2019-01-10 17:22:49 -08:00
slash & jail the validator
2018-06-14 20:26:21 -07:00
SigningInfo.Set(val.Address, signInfo)
2018-05-25 15:52:34 -07:00
```