cosmos-sdk/docs/spec/slashing/begin-block.md

121 lines
4.2 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
We then slash the validator:
```
curVal := validator
oldVal := loadValidator(evidence.Height, evidence.Address)
slashAmount := SLASH_PROPORTION * oldVal.Shares
slashAmount -= slashAmountUnbondings
slashAmount -= slashAmountRedelegations
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.
2018-08-23 04:43:01 -07:00
The amount slashed for all double signature infractions committed within a single slashing period is capped as described in [state-machine.md](state-machine.md).
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 should be automatically unbonded:
```
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++
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
2018-06-14 20:26:21 -07:00
slash & unbond the validator
2018-06-14 20:26:21 -07:00
SigningInfo.Set(val.Address, signInfo)
2018-05-25 15:52:34 -07:00
```
2018-08-23 04:43:01 -07:00
The amount slashed for downtime slashes is *not* capped by the slashing period in which they are committed, although they do reset it (since the validator is unbonded).