Address @rigelrozanski comments
This commit is contained in:
parent
bb9c265590
commit
4cc2054d7b
|
@ -6,8 +6,11 @@ This section specifies the slashing module of the Cosmos SDK, which implements f
|
|||
first outlined in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in June 2016.
|
||||
|
||||
The slashing module enables Cosmos SDK-based blockchains to disincentivize any attributable action
|
||||
by a protocol-recognized actor with value at stake by "slashing" them: burning some amount of their
|
||||
stake - and possibly also removing their ability to vote on future blocks for a period of time.
|
||||
by a protocol-recognized actor with value at stake by penalizing them ("slashing").
|
||||
|
||||
Penalties may include, but are not limited to:
|
||||
- Burning some amount of their stake
|
||||
- Removing their ability to vote on future blocks for a period of time.
|
||||
|
||||
This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosystem.
|
||||
|
||||
|
@ -16,14 +19,15 @@ This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosyste
|
|||
1. **[State](state.md)**
|
||||
1. [SigningInfo](state.md#signing-info)
|
||||
1. [SlashingPeriod](state.md#slashing-period)
|
||||
1. **[State Machine](state-machine.md)**
|
||||
1. [Transactions](state-machine.md#transactions)
|
||||
1. Unjail
|
||||
1. [Interactions](state-machine.md#interactions)
|
||||
1. Validator Bonded
|
||||
1. Validator Unbonding
|
||||
1. Validator Slashed
|
||||
1. [State Cleanup](state-machine.md#state-cleanup)
|
||||
1. **[Overview](overview.md)**
|
||||
1. **[Transactions](transactions.md)**
|
||||
1. Unjail
|
||||
1. **[Hooks](hooks.md)**
|
||||
1. Validator Bonded
|
||||
1. Validator Unbonding
|
||||
1. Validator Slashed
|
||||
1. **[Begin Block](begin-block.md)**
|
||||
1. [Evidence handling](begin-block.md#evidence-handling)
|
||||
1. [Uptime tracking](begin-block.md#uptime-tracking)
|
||||
1. **[Future Improvements](future-improvements.md)**
|
||||
1. [State cleanup](future-improvements.md#state-cleanup)
|
||||
|
|
|
@ -76,7 +76,7 @@ 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.
|
||||
|
||||
Double signature slashes are capped by the slashing period as described in [state-machine.md](state-machine.md).
|
||||
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).
|
||||
|
||||
## Uptime tracking
|
||||
|
||||
|
@ -117,4 +117,4 @@ for val in block.Validators:
|
|||
SigningInfo.Set(val.Address, signInfo)
|
||||
```
|
||||
|
||||
Downtime slashes are *not* capped by the slashing period, although they do reset it (since the validator is unbonded).
|
||||
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).
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
## State Cleanup
|
||||
|
||||
Once no evidence for a given slashing period can possibly be valid (the end time plus the unbonding period is less than the current time),
|
||||
old slashing periods should be cleaned up. This will be implemented post-launch.
|
|
@ -0,0 +1,58 @@
|
|||
## Hooks
|
||||
|
||||
In this section we describe the "hooks" - slashing module code that runs when other events happen.
|
||||
|
||||
### Validator Bonded
|
||||
|
||||
Upon successful bonding of a validator (a given validator entering the "bonded" state,
|
||||
which may happen on delegation, on unjailing, etc), we create a new `SlashingPeriod` structure for the
|
||||
now-bonded validator, which `StartHeight` of the current block, `EndHeight` of `0` (sentinel value for not-yet-ended),
|
||||
and `SlashedSoFar` of `0`:
|
||||
|
||||
```
|
||||
onValidatorBonded(address sdk.ValAddress)
|
||||
|
||||
slashingPeriod = SlashingPeriod{
|
||||
ValidatorAddr : address,
|
||||
StartHeight : CurrentHeight,
|
||||
EndHeight : 0,
|
||||
SlashedSoFar : 0,
|
||||
}
|
||||
setSlashingPeriod(slashingPeriod)
|
||||
|
||||
return
|
||||
```
|
||||
|
||||
### Validator Unbonded
|
||||
|
||||
When a validator is unbonded, we update the in-progress `SlashingPeriod` with the current block as the `EndHeight`:
|
||||
|
||||
```
|
||||
onValidatorUnbonded(address sdk.ValAddress)
|
||||
|
||||
slashingPeriod = getSlashingPeriod(address, CurrentHeight)
|
||||
slashingPeriod.EndHeight = CurrentHeight
|
||||
setSlashingPeriod(slashingPeriod)
|
||||
|
||||
return
|
||||
```
|
||||
|
||||
### Validator Slashed
|
||||
|
||||
When a validator is slashed, we look up the appropriate `SlashingPeriod` based on the validator
|
||||
address and the time of infraction, cap the fraction slashed as `max(SlashFraction, SlashedSoFar)`
|
||||
(which may be `0`), and update the `SlashingPeriod` with the increased `SlashedSoFar`:
|
||||
|
||||
```
|
||||
beforeValidatorSlashed(address sdk.ValAddress, fraction sdk.Rat, infractionHeight int64)
|
||||
|
||||
slashingPeriod = getSlashingPeriod(address, infractionHeight)
|
||||
totalToSlash = max(slashingPeriod.SlashedSoFar, fraction)
|
||||
slashingPeriod.SlashedSoFar = totalToSlash
|
||||
setSlashingPeriod(slashingPeriod)
|
||||
|
||||
remainderToSlash = slashingPeriod.SlashedSoFar - totalToSlash
|
||||
fraction = remainderToSlash
|
||||
|
||||
continue with slashing
|
||||
```
|
|
@ -0,0 +1,68 @@
|
|||
## Conceptual overview
|
||||
|
||||
### States
|
||||
|
||||
At any given time, there are any number of validators registered in the state machine.
|
||||
Each block, the top `n = MaximumBondedValidators` validators who are not jailed become *bonded*, meaning that they may propose and vote on blocks.
|
||||
Validators who are *bonded* are *at stake*, meaning that part or all of their stake and their delegators' stake is at risk if they commit a protocol fault.
|
||||
|
||||
### Slashing period
|
||||
|
||||
In order to mitigate the impact of initially likely categories of non-malicious protocol faults, the Cosmos Hub implements for each validator
|
||||
a *slashing period*, in which the amount by which a validator can be slashed is capped at the punishment for the worst violation. For example,
|
||||
if you misconfigure your HSM and double-sign a bunch of old blocks, you'll only be punished for the first double-sign (and then immediately jailed,
|
||||
so that you have a chance to reconfigure your setup). This will still be quite expensive and desirable to avoid, but slashing periods somewhat blunt
|
||||
the economic impact of unintentional misconfiguration.
|
||||
|
||||
Unlike the unbonding period, the slashing period doesn't have a fixed length. A new slashing period starts whenever a validator is bonded and ends
|
||||
whenever the validator is unbonded (which will happen if the validator is jailed). The amount of tokens slashed relative to validator power for infractions
|
||||
committed within the slashing period, whenever they are discovered, is capped at the punishment for the worst infraction
|
||||
(which for the Cosmos Hub at launch will be double-signing a block).
|
||||
|
||||
#### ASCII timelines
|
||||
|
||||
*Code*
|
||||
|
||||
*[* : timeline start
|
||||
*]* : timeline end
|
||||
*<* : slashing period start
|
||||
*>* : slashing period end
|
||||
*C<sub>n</sub>* : infraction `n` committed
|
||||
*D<sub>n</sub>* : infraction `n` discovered
|
||||
*V<sub>b</sub>* : validator bonded
|
||||
*V<sub>u</sub>* : validator unbonded
|
||||
|
||||
*Single infraction*
|
||||
|
||||
<----------------->
|
||||
[----------C<sub>1</sub>----D<sub>1</sub>,V<sub>u</sub>-----]
|
||||
|
||||
A single infraction is committed then later discovered, at which point the validator is unbonded and slashed at the full amount for the infraction.
|
||||
|
||||
*Multiple infractions*
|
||||
|
||||
<---------------------------->
|
||||
[----------C<sub>1</sub>--C<sub>2</sub>---C<sub>3</sub>---D<sub>1</sub>,D<sub>2</sub>,D<sub>3</sub>V<sub>u</sub>-----]
|
||||
|
||||
Multiple infractions are committed within a single slashing period then later discovered, at which point the validator is unbonded and slashed for only the worst infraction.
|
||||
|
||||
*Multiple infractions after rebonding*
|
||||
|
||||
|
||||
<----------------------------> <-------------->
|
||||
[----------C<sub>1</sub>--C<sub>2</sub>---C<sub>3</sub>---D<sub>1</sub>,D<sub>2</sub>,D<sub>3</sub>V<sub>u</sub>---V<sub>b</sub>---C<sub>4</sub>----D<sub>4</sub>,V<sub>u</sub>--]
|
||||
|
||||
Multiple infractions are committed within a single slashing period then later discovered, at which point the validator is unbonded and slashed for only the worst infraction.
|
||||
The validator then unjails themself and rebonds, then commits a fourth infraction - which is discovered and punished at the full amount, since a new slashing period started
|
||||
when they unjailed and rebonded.
|
||||
|
||||
### Safety note
|
||||
|
||||
Slashing is capped fractionally per period, but the amount of total bonded stake associated with any given validator can change (by an unbounded amount) over that period.
|
||||
|
||||
For example, with MaxFractionSlashedPerPeriod = `0.5`, if a validator is initially slashed at `0.4` near the start of a period when they have 100 stake bonded,
|
||||
then later slashed at `0.4` when they have `1000` stake bonded, the total amount slashed is just `40 + 100 = 140` (since the latter slash is capped at `0.1`) -
|
||||
whereas if they had `1000` stake bonded initially, the first offense would have been slashed for `400` stake and the total amount slashed would have been `400 + 100 = 500`.
|
||||
|
||||
This means that any slashing events which utilize the slashing period (are capped-per-period) **must also** jail the validator when the infraction is discovered.
|
||||
Otherwise it would be possible for a validator to slash themselves intentionally at a low bond, then increase their bond but no longer be at stake since they would have already hit the `SlashedSoFar` cap.
|
|
@ -1,174 +0,0 @@
|
|||
# State Machine Interaction Overview
|
||||
|
||||
## Conceptual overview
|
||||
|
||||
### States
|
||||
|
||||
At any given time, there are any number of validator candidates registered in the state machine.
|
||||
Each block, the top `n` candidates who are not jailed become *bonded*, meaning that they may propose and vote on blocks.
|
||||
Validators who are *bonded* are *at stake*, meaning that part or all of their stake is at risk if they commit a protocol fault.
|
||||
|
||||
### Slashing period
|
||||
|
||||
In order to mitigate the impact of initially likely categories of non-malicious protocol faults, the Cosmos Hub implements for each validator
|
||||
a *slashing period*, in which the amount by which a validator can be slashed is capped at the punishment for the worst violation. For example,
|
||||
if you misconfigure your HSM and double-sign a bunch of old blocks, you'll only be punished for the first double-sign (and then immediately jailed,
|
||||
so that you have a chance to reconfigure your setup). This will still be quite expensive and desirable to avoid, but slashing periods somewhat blunt
|
||||
the economic impact of unintentional misconfiguration.
|
||||
|
||||
A new slashing period starts whenever a validator is bonded and ends whenever the validator is unbonded (which will happen if the validator is jailed).
|
||||
The amount of tokens slashed relative to validator power for infractions committed within the slashing period, whenever they are discovered, is capped
|
||||
at the punishment for the worst infraction (which for the Cosmos Hub at launch will be double-signing a block).
|
||||
|
||||
#### ASCII timelines
|
||||
|
||||
*Code*
|
||||
|
||||
*[* : timeline start
|
||||
*]* : timeline end
|
||||
*<* : slashing period start
|
||||
*>* : slashing period end
|
||||
*C<sub>n</sub>* : infraction `n` committed
|
||||
*D<sub>n</sub>* : infraction `n` discovered
|
||||
*V<sub>b</sub>* : validator bonded
|
||||
*V<sub>u</sub>* : validator unbonded
|
||||
|
||||
*Single infraction*
|
||||
|
||||
<----------------->
|
||||
[----------C<sub>1</sub>----D<sub>1</sub>,V<sub>u</sub>-----]
|
||||
|
||||
A single infraction is committed then later discovered, at which point the validator is unbonded and slashed at the full amount for the infraction.
|
||||
|
||||
*Multiple infractions*
|
||||
|
||||
<---------------------------->
|
||||
[----------C<sub>1</sub>--C<sub>2</sub>---C<sub>3</sub>---D<sub>1</sub>,D<sub>2</sub>,D<sub>3</sub>V<sub>u</sub>-----]
|
||||
|
||||
Multiple infractions are committed within a single slashing period then later discovered, at which point the validator is unbonded and slashed for only the worst infraction.
|
||||
|
||||
*Multiple infractions after rebonding*
|
||||
|
||||
|
||||
<----------------------------> <-------------->
|
||||
[----------C<sub>1</sub>--C<sub>2</sub>---C<sub>3</sub>---D<sub>1</sub>,D<sub>2</sub>,D<sub>3</sub>V<sub>u</sub>---V<sub>b</sub>---C<sub>4</sub>----D<sub>4</sub>,V<sub>u</sub>--]
|
||||
|
||||
Multiple infractions are committed within a single slashing period then later discovered, at which point the validator is unbonded and slashed for only the worst infraction.
|
||||
The validator then unjails themself and rebonds, then commits a fourth infraction - which is discovered and punished at the full amount, since a new slashing period started
|
||||
when they unjailed and rebonded.
|
||||
|
||||
## Transactions
|
||||
|
||||
In this section we describe the processing of transactions for the `slashing` module.
|
||||
|
||||
### TxUnjail
|
||||
|
||||
If a validator was automatically unbonded due to downtime and wishes to come back online &
|
||||
possibly rejoin the bonded set, it must send `TxUnjail`:
|
||||
|
||||
```
|
||||
type TxUnjail struct {
|
||||
ValidatorAddr sdk.AccAddress
|
||||
}
|
||||
|
||||
handleMsgUnjail(tx TxUnjail)
|
||||
|
||||
validator = getValidator(tx.ValidatorAddr)
|
||||
if validator is nil
|
||||
fail with "No validator found"
|
||||
|
||||
if validator is not jailed
|
||||
fail with "Validator not jailed, cannot unjail"
|
||||
|
||||
info = getValidatorSigningInfo(operator)
|
||||
if block time is before info.JailedUntil
|
||||
fail with "Validator still jailed, cannot unjail until period has expired"
|
||||
|
||||
// Update the start height so the validator won't be immediately unbonded again
|
||||
info.StartHeight = BlockHeight
|
||||
setValidatorSigningInfo(info)
|
||||
|
||||
validator.Jailed = false
|
||||
setValidator(validator)
|
||||
|
||||
return
|
||||
```
|
||||
|
||||
If the validator has enough stake to be in the top hundred, they will be automatically rebonded,
|
||||
and all delegators still delegated to the validator will be rebonded and begin to again collect
|
||||
provisions and rewards.
|
||||
|
||||
## Interactions
|
||||
|
||||
In this section we describe the "hooks" - slashing module code that runs when other events happen.
|
||||
|
||||
### Validator Bonded
|
||||
|
||||
Upon successful bonding of a validator (a given validator changing from "unbonded" state to "bonded" state,
|
||||
which may happen on delegation, on unjailing, etc), we create a new `SlashingPeriod` structure for the
|
||||
now-bonded validator, which `StartHeight` of the current block, `EndHeight` of `0` (sentinel value for not-yet-ended),
|
||||
and `SlashedSoFar` of `0`:
|
||||
|
||||
```
|
||||
onValidatorBonded(address sdk.ValAddress)
|
||||
|
||||
slashingPeriod = SlashingPeriod{
|
||||
ValidatorAddr : address,
|
||||
StartHeight : CurrentHeight,
|
||||
EndHeight : 0,
|
||||
SlashedSoFar : 0,
|
||||
}
|
||||
setSlashingPeriod(slashingPeriod)
|
||||
|
||||
return
|
||||
```
|
||||
|
||||
### Validator Unbonded
|
||||
|
||||
When a validator is unbonded, we update the in-progress `SlashingPeriod` with the current block as the `EndHeight`:
|
||||
|
||||
```
|
||||
onValidatorUnbonded(address sdk.ValAddress)
|
||||
|
||||
slashingPeriod = getSlashingPeriod(address, CurrentHeight)
|
||||
slashingPeriod.EndHeight = CurrentHeight
|
||||
setSlashingPeriod(slashingPeriod)
|
||||
|
||||
return
|
||||
```
|
||||
|
||||
### Validator Slashed
|
||||
|
||||
When a validator is slashed, we look up the appropriate `SlashingPeriod` based on the validator
|
||||
address and the time of infraction, cap the fraction slashed as `max(SlashFraction, SlashedSoFar)`
|
||||
(which may be `0`), and update the `SlashingPeriod` with the increased `SlashedSoFar`:
|
||||
|
||||
```
|
||||
beforeValidatorSlashed(address sdk.ValAddress, fraction sdk.Rat, infractionHeight int64)
|
||||
|
||||
slashingPeriod = getSlashingPeriod(address, infractionHeight)
|
||||
totalToSlash = max(slashingPeriod.SlashedSoFar, fraction)
|
||||
slashingPeriod.SlashedSoFar = totalToSlash
|
||||
setSlashingPeriod(slashingPeriod)
|
||||
|
||||
remainderToSlash = slashingPeriod.SlashedSoFar - totalToSlash
|
||||
fraction = remainderToSlash
|
||||
|
||||
continue with slashing
|
||||
```
|
||||
|
||||
##### Safety note
|
||||
|
||||
Slashing is capped fractionally per period, but the amount of total bonded stake associated with any given validator can change (by an unbounded amount) over that period.
|
||||
|
||||
For example, with MaxFractionSlashedPerPeriod = `0.5`, if a validator is initially slashed at `0.4` near the start of a period when they have 100 stake bonded,
|
||||
then later slashed at `0.4` when they have `1000` stake bonded, the total amount slashed is just `40 + 100 = 140` (since the latter slash is capped at `0.1`) -
|
||||
whereas if they had `1000` stake bonded initially, the first offense would have been slashed for `400` stake and the total amount slashed would have been `400 + 100 = 500`.
|
||||
|
||||
This means that any slashing events which utilize the slashing period (are capped-per-period) **must** *also* jail the validator when the infraction is discovered.
|
||||
Otherwise it would be possible for a validator to slash themselves intentionally at a low bond, then increase their bond but no longer be at stake since they would have already hit the `SlashedSoFar` cap.
|
||||
|
||||
## State Cleanup
|
||||
|
||||
Once no evidence for a given slashing period can possibly be valid (the end time plus the unbonding period is less than the current time),
|
||||
old slashing periods should be cleaned up. This will be implemented post-launch.
|
|
@ -54,9 +54,9 @@ Where:
|
|||
## Slashing Period
|
||||
|
||||
A slashing period is a start and end block height associated with a particular validator,
|
||||
within which only the "worst infraction counts": the total amount of slashing for
|
||||
infractions committed within the period (and discovered whenever) is capped at the
|
||||
penalty for the worst offense.
|
||||
within which only the "worst infraction counts" (see the [Overview](overview.md)): the total
|
||||
amount of slashing for infractions committed within the period (and discovered whenever) is
|
||||
capped at the penalty for the worst offense.
|
||||
|
||||
This period starts when a validator is first bonded and ends when a validator is slashed & jailed
|
||||
for any reason. When the validator rejoins the validator set (perhaps through unjailing themselves,
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
## Transactions
|
||||
|
||||
In this section we describe the processing of transactions for the `slashing` module.
|
||||
|
||||
### TxUnjail
|
||||
|
||||
If a validator was automatically unbonded due to downtime and wishes to come back online &
|
||||
possibly rejoin the bonded set, it must send `TxUnjail`:
|
||||
|
||||
```
|
||||
type TxUnjail struct {
|
||||
ValidatorAddr sdk.AccAddress
|
||||
}
|
||||
|
||||
handleMsgUnjail(tx TxUnjail)
|
||||
|
||||
validator = getValidator(tx.ValidatorAddr)
|
||||
if validator == nil
|
||||
fail with "No validator found"
|
||||
|
||||
if !validator.Jailed
|
||||
fail with "Validator not jailed, cannot unjail"
|
||||
|
||||
info = getValidatorSigningInfo(operator)
|
||||
if block time < info.JailedUntil
|
||||
fail with "Validator still jailed, cannot unjail until period has expired"
|
||||
|
||||
// Update the start height so the validator won't be immediately unbonded again
|
||||
info.StartHeight = BlockHeight
|
||||
setValidatorSigningInfo(info)
|
||||
|
||||
validator.Jailed = false
|
||||
setValidator(validator)
|
||||
|
||||
return
|
||||
```
|
||||
|
||||
If the validator has enough stake to be in the top `n = MaximumBondedValidators`, they will be automatically rebonded,
|
||||
and all delegators still delegated to the validator will be rebonded and begin to again collect
|
||||
provisions and rewards.
|
Loading…
Reference in New Issue