-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1263 from cosmos/bucky/staking-spec-updates
spec updates
- Loading branch information
Showing
14 changed files
with
269 additions
and
176 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Empty file.
Empty file.
File renamed without changes.
Empty file.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# End-Block | ||
|
||
## Slashing | ||
|
||
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. | ||
|
||
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) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
## State | ||
|
||
### Signing Info | ||
|
||
Every block includes a set of precommits by the validators for the previous block, | ||
known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power. | ||
|
||
Proposers are incentivized to include precommits from all | ||
validators in the LastCommit by receiving additional fees | ||
proportional to the difference between the voting power included in the | ||
LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)). | ||
|
||
Validators are penalized for failing to be included in the LastCommit for some | ||
number of blocks by being automatically unbonded. | ||
|
||
Information about validator activity is tracked in a `ValidatorSigningInfo`. | ||
It is indexed in the store as follows: | ||
|
||
- SigningInfo: ` 0x01 | ValTendermintAddr -> amino(valSigningInfo)` | ||
- SigningBitArray: ` 0x02 | ValTendermintAddr | LittleEndianUint64(signArrayIndex) -> VarInt(didSign)` | ||
|
||
The first map allows us to easily lookup the recent signing info for a | ||
validator, according to the Tendermint validator address. The second map acts as | ||
a bit-array of size `SIGNED_BLOCKS_WINDOW` that tells us if the validator signed for a given index in the bit-array. | ||
|
||
The index in the bit-array is given as little endian uint64. | ||
|
||
The result is a `varint` that takes on `0` or `1`, where `0` indicates the | ||
validator did not sign the corresponding block, and `1` indicates they did. | ||
|
||
Note that the SigningBitArray is not explicitly initialized up-front. Keys are | ||
added as we progress through the first `SIGNED_BLOCKS_WINDOW` blocks for a newly | ||
bonded validator. | ||
|
||
The information stored for tracking validator liveness is as follows: | ||
|
||
```go | ||
type ValidatorSigningInfo struct { | ||
StartHeight int64 | ||
IndexOffset int64 | ||
JailedUntil int64 | ||
SignedBlocksCounter int64 | ||
} | ||
|
||
``` | ||
|
||
Where: | ||
* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power). | ||
* `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not). | ||
* `JailedUntil` is set whenever the candidate is revoked due to downtime | ||
* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always. |
File renamed without changes.
Oops, something went wrong.