From 4cc2054d7bf79c387af1f0dcc8203e4ac3f9797c Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 23 Aug 2018 13:43:01 +0200 Subject: [PATCH] Address @rigelrozanski comments --- docs/spec/slashing/README.md | 24 +-- docs/spec/slashing/begin-block.md | 4 +- docs/spec/slashing/future-improvements.md | 4 + docs/spec/slashing/hooks.md | 58 ++++++++ docs/spec/slashing/overview.md | 68 +++++++++ docs/spec/slashing/state-machine.md | 174 ---------------------- docs/spec/slashing/state.md | 6 +- docs/spec/slashing/transactions.md | 40 +++++ 8 files changed, 189 insertions(+), 189 deletions(-) create mode 100644 docs/spec/slashing/future-improvements.md create mode 100644 docs/spec/slashing/hooks.md create mode 100644 docs/spec/slashing/overview.md delete mode 100644 docs/spec/slashing/state-machine.md create mode 100644 docs/spec/slashing/transactions.md diff --git a/docs/spec/slashing/README.md b/docs/spec/slashing/README.md index 7e8b780a2..d50d95d36 100644 --- a/docs/spec/slashing/README.md +++ b/docs/spec/slashing/README.md @@ -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) diff --git a/docs/spec/slashing/begin-block.md b/docs/spec/slashing/begin-block.md index 0ab888317..375e19185 100644 --- a/docs/spec/slashing/begin-block.md +++ b/docs/spec/slashing/begin-block.md @@ -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). diff --git a/docs/spec/slashing/future-improvements.md b/docs/spec/slashing/future-improvements.md new file mode 100644 index 000000000..84be139e8 --- /dev/null +++ b/docs/spec/slashing/future-improvements.md @@ -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. diff --git a/docs/spec/slashing/hooks.md b/docs/spec/slashing/hooks.md new file mode 100644 index 000000000..36dde61f9 --- /dev/null +++ b/docs/spec/slashing/hooks.md @@ -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 +``` diff --git a/docs/spec/slashing/overview.md b/docs/spec/slashing/overview.md new file mode 100644 index 000000000..d9a8f39c1 --- /dev/null +++ b/docs/spec/slashing/overview.md @@ -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 +*Cn* : infraction `n` committed +*Dn* : infraction `n` discovered +*Vb* : validator bonded +*Vu* : validator unbonded + +*Single infraction* + +<-----------------> +[----------C1----D1,Vu-----] + +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* + +<----------------------------> +[----------C1--C2---C3---D1,D2,D3Vu-----] + +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* + + +<---------------------------->                        <--------------> +[----------C1--C2---C3---D1,D2,D3Vu---Vb---C4----D4,Vu--] + +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. diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md deleted file mode 100644 index e35b6b179..000000000 --- a/docs/spec/slashing/state-machine.md +++ /dev/null @@ -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 -*Cn* : infraction `n` committed -*Dn* : infraction `n` discovered -*Vb* : validator bonded -*Vu* : validator unbonded - -*Single infraction* - -<-----------------> -[----------C1----D1,Vu-----] - -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* - -<----------------------------> -[----------C1--C2---C3---D1,D2,D3Vu-----] - -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* - - -<---------------------------->                        <--------------> -[----------C1--C2---C3---D1,D2,D3Vu---Vb---C4----D4,Vu--] - -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. diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index b64e99040..ae426db7b 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -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, diff --git a/docs/spec/slashing/transactions.md b/docs/spec/slashing/transactions.md new file mode 100644 index 000000000..b1d523139 --- /dev/null +++ b/docs/spec/slashing/transactions.md @@ -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.