From 4f8c9e4917feb8208bf32852c209431196b2101e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 13 Aug 2018 14:51:01 +0200 Subject: [PATCH 01/24] Update transactions.md --- docs/spec/slashing/transactions.md | 42 +++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/docs/spec/slashing/transactions.md b/docs/spec/slashing/transactions.md index cdf495e4d220..33e3bd65e556 100644 --- a/docs/spec/slashing/transactions.md +++ b/docs/spec/slashing/transactions.md @@ -1,19 +1,41 @@ +## Transaction Overview -### TxProveLive +In this section we describe the processing of transactions for the `slashing` module. -If a validator was automatically unbonded due to liveness issues and wishes to -assert it is still online, it can send `TxProveLive`: +### 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`: ```golang -type TxProveLive struct { - PubKey crypto.PubKey +type TxUnjail struct { + ValidatorAddr sdk.AccAddress } ``` -All delegators in the temporary unbonding pool which have not -transacted to move will be bonded back to the now-live validator and begin to -once again collect provisions and rewards. +All delegators still delegated to the validator will be rebonded and begin +to again collect provisions and rewards. -``` -TODO: pseudo-code +```golang +handleMsgUnjail(operator sdk.AccAddress) + + validator := getValidator(operator) + if validator == nil + fail with "No validator found" + + if !validator.Jailed + fail with "Validator not jailed, cannot unjail" + + info := getValidatorSigningInfo(operator) + if BlockHeader.Time.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 ``` From 84e9b215b290030c76ed5135a687f312d3e1e7f7 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 13 Aug 2018 14:54:42 +0200 Subject: [PATCH 02/24] Fix typo --- docs/spec/slashing/transactions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/spec/slashing/transactions.md b/docs/spec/slashing/transactions.md index 33e3bd65e556..f241b12d27ff 100644 --- a/docs/spec/slashing/transactions.md +++ b/docs/spec/slashing/transactions.md @@ -17,9 +17,9 @@ All delegators still delegated to the validator will be rebonded and begin to again collect provisions and rewards. ```golang -handleMsgUnjail(operator sdk.AccAddress) +handleMsgUnjail(tx TxUnjail) - validator := getValidator(operator) + validator := getValidator(tx.ValidatorAddr) if validator == nil fail with "No validator found" From 98a278d5646f277690decc994dd4cafe608657d9 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 13 Aug 2018 14:56:10 +0200 Subject: [PATCH 03/24] Reorganize sections --- docs/spec/slashing/transactions.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/spec/slashing/transactions.md b/docs/spec/slashing/transactions.md index f241b12d27ff..c6a9470fc6ec 100644 --- a/docs/spec/slashing/transactions.md +++ b/docs/spec/slashing/transactions.md @@ -11,12 +11,7 @@ possibly rejoin the bonded set, it must send `TxUnjail`: type TxUnjail struct { ValidatorAddr sdk.AccAddress } -``` - -All delegators still delegated to the validator will be rebonded and begin -to again collect provisions and rewards. -```golang handleMsgUnjail(tx TxUnjail) validator := getValidator(tx.ValidatorAddr) @@ -39,3 +34,7 @@ handleMsgUnjail(tx TxUnjail) return ``` + +If the validotor 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. From ff01cbb093f5a54772773ea636a5b93e9ce7be54 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 13 Aug 2018 15:48:23 +0200 Subject: [PATCH 04/24] Update state.md --- docs/spec/slashing/state.md | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index 1df9d5022ded..f4412067bf62 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -36,10 +36,11 @@ The information stored for tracking validator liveness is as follows: ```go type ValidatorSigningInfo struct { - StartHeight int64 - IndexOffset int64 - JailedUntil int64 - SignedBlocksCounter int64 + StartHeight int64 // Height at which the validator became able to sign blocks + IndexOffset int64 // Offset into the signed block bit array + JailedUntil int64 // Block height until which the validator is jailed, + // or sentinel value of 0 for not jailed + SignedBlocksCounter int64 // Running counter of signed blocks } ``` @@ -49,3 +50,29 @@ Where: * `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. + +### Slashing Period + +A slashing period is a start and end time 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. + +This period starts when a validator is first bonded and ends when a validator is slashed & jailed +for double-signing (but does not end if they are slashed & jailed for just missing blocks). +When the validator voluntarily unjails themselves (and possibly changes signing keys), they reset the period. + +Slashing periods are indexed in the store as follows: + +- SlashingPeriod: ` 0x03 | ValTendermintAddr -> amino(slashingPeriod) ` + +This allows us to look up slashing period by validator address, the only lookup necessary. + +```go +type SlashingPeriod struct { + ValidatorAddr sdk.ValAddress // Tendermint address of the validator + StartHeight int64 // Block height at which slashing period begin + EndHeight int64 // Block height at which slashing period ended + SlashedSoFar sdk.Rat // Fraction slashed so far, cumulative +} +``` From 53fa4a28dcb3e72f54d4a793e9195190af903123 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 13 Aug 2018 15:55:30 +0200 Subject: [PATCH 05/24] Start update of state-machine.md --- .../{transactions.md => state-machine.md} | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) rename docs/spec/slashing/{transactions.md => state-machine.md} (70%) diff --git a/docs/spec/slashing/transactions.md b/docs/spec/slashing/state-machine.md similarity index 70% rename from docs/spec/slashing/transactions.md rename to docs/spec/slashing/state-machine.md index c6a9470fc6ec..67a5ce39aa87 100644 --- a/docs/spec/slashing/transactions.md +++ b/docs/spec/slashing/state-machine.md @@ -1,8 +1,10 @@ -## Transaction Overview +## Transaction & State Machine Interaction Overview + +### Transactions In this section we describe the processing of transactions for the `slashing` module. -### TxUnjail +#### 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`: @@ -35,6 +37,19 @@ handleMsgUnjail(tx TxUnjail) return ``` -If the validotor has enough stake to be in the top hundred, they will be automatically rebonded, +If the validater 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 + +#### Validator Bonded + +#### Validator Slashed + +#### Validator Unjailed + +#### Slashing Period 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. From a8af4a4fadc4c275570289e3e703b763149fcb72 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 13 Aug 2018 16:04:35 +0200 Subject: [PATCH 06/24] End block to begin block, add README --- docs/spec/slashing/README.md | 29 +++++++++++++++++++ .../slashing/{end_block.md => begin-block.md} | 9 +++--- 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 docs/spec/slashing/README.md rename docs/spec/slashing/{end_block.md => begin-block.md} (95%) diff --git a/docs/spec/slashing/README.md b/docs/spec/slashing/README.md new file mode 100644 index 000000000000..23e5e604a412 --- /dev/null +++ b/docs/spec/slashing/README.md @@ -0,0 +1,29 @@ +# Slashing module specification + +## Abstract + +This section specifies the slashing module of the Cosmos SDK, which implements functionality +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. + +This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosystem. + +## Contents + +1. **[State](state.md)** + 1. SigningInfo + 1. SlashingPeriod +1. **[State Machine](state-machine.md)** + 1. Transactions + 1. Unjail + 1. Interactions + 1. Validator Bonded + 1. Validator Slashed + 1. Validator Unjailed + 1. Slashing Period Cleanup +1. **[Begin Block](begin-block.md)** + 1. Evidence handling & slashing + 1. Uptime/downtime tracking & slashing diff --git a/docs/spec/slashing/end_block.md b/docs/spec/slashing/begin-block.md similarity index 95% rename from docs/spec/slashing/end_block.md rename to docs/spec/slashing/begin-block.md index 3eec27372c2a..bdd7d9692006 100644 --- a/docs/spec/slashing/end_block.md +++ b/docs/spec/slashing/begin-block.md @@ -1,12 +1,13 @@ -# End-Block +# Begin-Block -## Slashing +## Evidence handling & 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/tendermint/blob/develop/abci/types/types.proto#L259), so the validator an be accordingly punished. +Evidence](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto#L259) in `abci.RequestBeginBlock` +so that the validator an be accordingly punished. For some `evidence` to be valid, it must satisfy: @@ -75,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. -## Automatic Unbonding +## Uptime/downtime tracking & slashing At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded: From 07a7db7fdad9e596d158ccdbea476c3f3734da5d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 13 Aug 2018 16:12:59 +0200 Subject: [PATCH 07/24] Update links --- docs/spec/slashing/README.md | 12 ++++++------ docs/spec/slashing/begin-block.md | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/spec/slashing/README.md b/docs/spec/slashing/README.md index 23e5e604a412..da3069ffcda5 100644 --- a/docs/spec/slashing/README.md +++ b/docs/spec/slashing/README.md @@ -14,16 +14,16 @@ This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosyste ## Contents 1. **[State](state.md)** - 1. SigningInfo - 1. SlashingPeriod + 1. [SigningInfo](state.md#signing-info) + 1. [SlashingPeriod](state.md#slashing-period) 1. **[State Machine](state-machine.md)** - 1. Transactions + 1. [Transactions](state-machine.md#transactions) 1. Unjail - 1. Interactions + 1. [Interactions](state-machine.md#interactions) 1. Validator Bonded 1. Validator Slashed 1. Validator Unjailed 1. Slashing Period Cleanup 1. **[Begin Block](begin-block.md)** - 1. Evidence handling & slashing - 1. Uptime/downtime tracking & slashing + 1. [Evidence handling](begin-block.md#evidence-handling) + 1. [Uptime tracking](begin-block.md#uptime-tracking) diff --git a/docs/spec/slashing/begin-block.md b/docs/spec/slashing/begin-block.md index bdd7d9692006..9c2e5c12105d 100644 --- a/docs/spec/slashing/begin-block.md +++ b/docs/spec/slashing/begin-block.md @@ -1,6 +1,6 @@ # Begin-Block -## Evidence handling & slashing +## Evidence handling Tendermint blocks can include [Evidence](https://github.com/tendermint/tendermint/blob/develop/docs/spec/blockchain/blockchain.md#evidence), which indicates that a validator @@ -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. -## Uptime/downtime tracking & slashing +## Uptime tracking At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded: From 2445718295931fe953a60c6ac9ee8a014c4ce85f Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 13 Aug 2018 17:03:37 +0200 Subject: [PATCH 08/24] State machine contd. --- docs/spec/slashing/README.md | 4 ++-- docs/spec/slashing/state-machine.md | 31 ++++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/spec/slashing/README.md b/docs/spec/slashing/README.md index da3069ffcda5..7e8b780a2d96 100644 --- a/docs/spec/slashing/README.md +++ b/docs/spec/slashing/README.md @@ -21,9 +21,9 @@ This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosyste 1. Unjail 1. [Interactions](state-machine.md#interactions) 1. Validator Bonded + 1. Validator Unbonding 1. Validator Slashed - 1. Validator Unjailed - 1. Slashing Period Cleanup + 1. [State Cleanup](state-machine.md#state-cleanup) 1. **[Begin Block](begin-block.md)** 1. [Evidence handling](begin-block.md#evidence-handling) 1. [Uptime tracking](begin-block.md#uptime-tracking) diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md index 67a5ce39aa87..22987bb6e0ef 100644 --- a/docs/spec/slashing/state-machine.md +++ b/docs/spec/slashing/state-machine.md @@ -43,13 +43,38 @@ 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, wich `StartHeight` of the current block, `EndHeight` of `0` (sentinel value for not-yet-ended), +and `SlashedSoFar` of `0`: + +```golang +onValidatorBonded(address sdk.ValAddress) +``` + +#### Validator Unbonded + +When a validator is unbonded, we update the in-progress `SlashingPeriod` with the current block as the `EndHeight`: + +```golang +onValidatorUnbonded(address sdk.ValAddress) +``` + #### Validator Slashed -#### Validator Unjailed +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`: + +```golang +beforeValidatorSlashed(address sdk.ValAddress, fraction sdk.Rat) +``` -#### Slashing Period Cleanup +### 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. +old slashing periods should be cleaned up. This will be implemented post-launch. From 8b7d6e0979a950e6124381ecee5f40321b6a687a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 13 Aug 2018 17:52:58 +0200 Subject: [PATCH 09/24] Update state machine, contd. --- docs/spec/slashing/state-machine.md | 28 +++++++++++++++++++++++++++- docs/spec/slashing/state.md | 6 ++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md index 22987bb6e0ef..77b41f9b5df5 100644 --- a/docs/spec/slashing/state-machine.md +++ b/docs/spec/slashing/state-machine.md @@ -54,6 +54,16 @@ and `SlashedSoFar` of `0`: ```golang onValidatorBonded(address sdk.ValAddress) + + slashingPeriod := SlashingPeriod{ + ValidatorAddr : address, + StartHeight : CurrentHeight, + EndHeight : 0, + SlashedSoFar : 0, + } + setSlashingPeriod(slashingPeriod) + + return ``` #### Validator Unbonded @@ -62,6 +72,12 @@ When a validator is unbonded, we update the in-progress `SlashingPeriod` with th ```golang onValidatorUnbonded(address sdk.ValAddress) + + slashingPeriod = getSlashingPeriod(address, CurrentHeight) + slashingPeriod.EndHeight = CurrentHeight + setSlashingPeriod(slashingPeriod) + + return ``` #### Validator Slashed @@ -71,7 +87,17 @@ address and the time of infraction, cap the fraction slashed as `max(SlashFracti (which may be `0`), and update the `SlashingPeriod` with the increased `SlashedSoFar`: ```golang -beforeValidatorSlashed(address sdk.ValAddress, fraction sdk.Rat) +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 ``` ### State Cleanup diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index f4412067bf62..07bf3261cfb8 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -64,9 +64,11 @@ When the validator voluntarily unjails themselves (and possibly changes signing Slashing periods are indexed in the store as follows: -- SlashingPeriod: ` 0x03 | ValTendermintAddr -> amino(slashingPeriod) ` +- SlashingPeriod: ` 0x03 | ValTendermintAddr | StartHeight -> amino(slashingPeriod) ` -This allows us to look up slashing period by validator address, the only lookup necessary. +This allows us to look up slashing period by validator address, the only lookup necessary, +and iterate over start height to efficiently retrieve the most recent slashing period(s) +or those beginning after a given height. ```go type SlashingPeriod struct { From 52475b1684f94a3d5fc7876cdeeb9c017324e8b6 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 14 Aug 2018 11:31:39 +0200 Subject: [PATCH 10/24] Fix minor typos --- docs/spec/slashing/state-machine.md | 2 +- docs/spec/slashing/state.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md index 77b41f9b5df5..1105cededac4 100644 --- a/docs/spec/slashing/state-machine.md +++ b/docs/spec/slashing/state-machine.md @@ -49,7 +49,7 @@ In this section we describe the "hooks" - slashing module code that runs when ot 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, wich `StartHeight` of the current block, `EndHeight` of `0` (sentinel value for not-yet-ended), +now-bonded validator, which `StartHeight` of the current block, `EndHeight` of `0` (sentinel value for not-yet-ended), and `SlashedSoFar` of `0`: ```golang diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index 07bf3261cfb8..cf39a8493e25 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -38,7 +38,7 @@ The information stored for tracking validator liveness is as follows: type ValidatorSigningInfo struct { StartHeight int64 // Height at which the validator became able to sign blocks IndexOffset int64 // Offset into the signed block bit array - JailedUntil int64 // Block height until which the validator is jailed, + JailedUntilHeight int64 // Block height until which the validator is jailed, // or sentinel value of 0 for not jailed SignedBlocksCounter int64 // Running counter of signed blocks } @@ -53,7 +53,7 @@ Where: ### Slashing Period -A slashing period is a start and end time associated with a particular validator, +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. @@ -66,7 +66,7 @@ Slashing periods are indexed in the store as follows: - SlashingPeriod: ` 0x03 | ValTendermintAddr | StartHeight -> amino(slashingPeriod) ` -This allows us to look up slashing period by validator address, the only lookup necessary, +This allows us to look up slashing period by a validator's address, the only lookup necessary, and iterate over start height to efficiently retrieve the most recent slashing period(s) or those beginning after a given height. From a2463d038b285923c3f06dd597bc05ce63c8b2c9 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 14 Aug 2018 18:04:32 +0200 Subject: [PATCH 11/24] Clarify points from PR review --- docs/spec/slashing/state.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index cf39a8493e25..050f4c9ae444 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -59,8 +59,8 @@ infractions committed within the period (and discovered whenever) is capped at t penalty for the worst offense. This period starts when a validator is first bonded and ends when a validator is slashed & jailed -for double-signing (but does not end if they are slashed & jailed for just missing blocks). -When the validator voluntarily unjails themselves (and possibly changes signing keys), they reset the period. +for any reason. When the validator rejoins the validator set (perhaps through unjailing themselves, +and perhaps also changing signing keys), they enter into a new period. Slashing periods are indexed in the store as follows: From 21be609f5281e44985c248433692a30cbd4fbe69 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 14 Aug 2018 19:04:57 +0200 Subject: [PATCH 12/24] Changes WIP --- docs/spec/slashing/state-machine.md | 20 +++++++++++++++++--- docs/spec/slashing/state.md | 4 +++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md index 1105cededac4..023d0a78b677 100644 --- a/docs/spec/slashing/state-machine.md +++ b/docs/spec/slashing/state-machine.md @@ -80,6 +80,20 @@ onValidatorUnbonded(address sdk.ValAddress) return ``` +#### Validator Power Changed + +When a validator's power changes, we update the in-progress `SlashingPeriod` with the validator's current power: + +```golang +onValidatorPowerChanged(address sdk.ValAddress, stakeBonded sdk.Rat) + + slashingPeriod = getSlashingPeriod(address, CurrentHeight) + slashingPeriod.MaxStakeBonded = max(slashingPeriod.MaxStakeBonded, stakeBonded) + setSlashingPeriod(slashingPeriod) + + return +``` + #### Validator Slashed When a validator is slashed, we look up the appropriate `SlashingPeriod` based on the validator @@ -90,11 +104,11 @@ address and the time of infraction, cap the fraction slashed as `max(SlashFracti beforeValidatorSlashed(address sdk.ValAddress, fraction sdk.Rat, infractionHeight int64) slashingPeriod = getSlashingPeriod(address, infractionHeight) - totalToSlash = max(slashingPeriod.SlashedSoFar, fraction) - slashingPeriod.SlashedSoFar = totalToSlash + totalFractionToSlash = max(slashingPeriod.SlashedSoFar, fraction) + slashingPeriod.FractionSlashedSoFar = totalToSlash setSlashingPeriod(slashingPeriod) - remainderToSlash = slashingPeriod.SlashedSoFar - totalToSlash + remainderToSlash = slashingPeriod.FractionSlashedSoFar - totalToSlash fraction = remainderToSlash continue with slashing diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index 050f4c9ae444..a8a8344769bd 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -75,6 +75,8 @@ type SlashingPeriod struct { ValidatorAddr sdk.ValAddress // Tendermint address of the validator StartHeight int64 // Block height at which slashing period begin EndHeight int64 // Block height at which slashing period ended - SlashedSoFar sdk.Rat // Fraction slashed so far, cumulative + MaxBondedStake sdk.Rat // Maximum bonded stake during period + StakeSlashedSoFar sdk.Rat // Amount of stake slashed so far + FractionSlashedSoFar sdk.Rat // Fraction slashed so far, cumulative } ``` From 79e3c0536778b526ed36ab403e13532875e59dab Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 20 Aug 2018 15:01:18 +0200 Subject: [PATCH 13/24] Revert "Changes WIP" - we decided not to do this This reverts commit 21be609f5281e44985c248433692a30cbd4fbe69. --- docs/spec/slashing/state-machine.md | 20 +++----------------- docs/spec/slashing/state.md | 4 +--- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md index 023d0a78b677..1105cededac4 100644 --- a/docs/spec/slashing/state-machine.md +++ b/docs/spec/slashing/state-machine.md @@ -80,20 +80,6 @@ onValidatorUnbonded(address sdk.ValAddress) return ``` -#### Validator Power Changed - -When a validator's power changes, we update the in-progress `SlashingPeriod` with the validator's current power: - -```golang -onValidatorPowerChanged(address sdk.ValAddress, stakeBonded sdk.Rat) - - slashingPeriod = getSlashingPeriod(address, CurrentHeight) - slashingPeriod.MaxStakeBonded = max(slashingPeriod.MaxStakeBonded, stakeBonded) - setSlashingPeriod(slashingPeriod) - - return -``` - #### Validator Slashed When a validator is slashed, we look up the appropriate `SlashingPeriod` based on the validator @@ -104,11 +90,11 @@ address and the time of infraction, cap the fraction slashed as `max(SlashFracti beforeValidatorSlashed(address sdk.ValAddress, fraction sdk.Rat, infractionHeight int64) slashingPeriod = getSlashingPeriod(address, infractionHeight) - totalFractionToSlash = max(slashingPeriod.SlashedSoFar, fraction) - slashingPeriod.FractionSlashedSoFar = totalToSlash + totalToSlash = max(slashingPeriod.SlashedSoFar, fraction) + slashingPeriod.SlashedSoFar = totalToSlash setSlashingPeriod(slashingPeriod) - remainderToSlash = slashingPeriod.FractionSlashedSoFar - totalToSlash + remainderToSlash = slashingPeriod.SlashedSoFar - totalToSlash fraction = remainderToSlash continue with slashing diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index a8a8344769bd..050f4c9ae444 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -75,8 +75,6 @@ type SlashingPeriod struct { ValidatorAddr sdk.ValAddress // Tendermint address of the validator StartHeight int64 // Block height at which slashing period begin EndHeight int64 // Block height at which slashing period ended - MaxBondedStake sdk.Rat // Maximum bonded stake during period - StakeSlashedSoFar sdk.Rat // Amount of stake slashed so far - FractionSlashedSoFar sdk.Rat // Fraction slashed so far, cumulative + SlashedSoFar sdk.Rat // Fraction slashed so far, cumulative } ``` From 94dc512034640629234e411c76d11c704126b580 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 20 Aug 2018 15:07:23 +0200 Subject: [PATCH 14/24] Fix typos --- docs/spec/slashing/begin-block.md | 2 +- docs/spec/slashing/state-machine.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/spec/slashing/begin-block.md b/docs/spec/slashing/begin-block.md index 9c2e5c12105d..750228367aa2 100644 --- a/docs/spec/slashing/begin-block.md +++ b/docs/spec/slashing/begin-block.md @@ -4,7 +4,7 @@ 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 +committed malicious behavior. The relevant information is forwarded to the application as [ABCI Evidence](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto#L259) in `abci.RequestBeginBlock` so that the validator an be accordingly punished. diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md index 1105cededac4..b6bdf5820d4f 100644 --- a/docs/spec/slashing/state-machine.md +++ b/docs/spec/slashing/state-machine.md @@ -37,7 +37,7 @@ handleMsgUnjail(tx TxUnjail) return ``` -If the validater has enough stake to be in the top hundred, they will be automatically rebonded, +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. From e3cb1e12742347ff57dc500bcbb6da43b2d2e3ae Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 20 Aug 2018 15:13:17 +0200 Subject: [PATCH 15/24] Add safety note --- docs/spec/slashing/state-machine.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md index b6bdf5820d4f..df849398bdf0 100644 --- a/docs/spec/slashing/state-machine.md +++ b/docs/spec/slashing/state-machine.md @@ -100,6 +100,17 @@ beforeValidatorSlashed(address sdk.ValAddress, fraction sdk.Rat, infractionHeigh 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 steak bonded, +then later slashed at `0.4` when they have `1000` steak bonded, the total amount slashed is just `40 + 100 = 140` (since the latter slash is capped at `0.1`) - +whereas if they had `1000` steak bonded initially, the total amount slashed would have been `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), From b8d6465613b9ba8f5ab730bff215a0f3f35c9ae2 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 20 Aug 2018 16:58:24 +0200 Subject: [PATCH 16/24] Conceptual overview & ASCII diagrams of slashing period --- docs/spec/slashing/state-machine.md | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md index df849398bdf0..46566caa67dd 100644 --- a/docs/spec/slashing/state-machine.md +++ b/docs/spec/slashing/state-machine.md @@ -1,5 +1,62 @@ ## Transaction & 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. From da92b1bb1dafb3b8e15014242e038df00702800a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 20 Aug 2018 18:28:40 +0200 Subject: [PATCH 17/24] h_n => h_n-1 --- docs/spec/slashing/state-machine.md | 24 ++++++++++++------------ docs/spec/slashing/state.md | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md index 46566caa67dd..56eb82b46bae 100644 --- a/docs/spec/slashing/state-machine.md +++ b/docs/spec/slashing/state-machine.md @@ -1,14 +1,14 @@ -## Transaction & State Machine Interaction Overview +# State Machine Interaction Overview -### Conceptual overview +## Conceptual overview -#### States +### 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 +### 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, @@ -20,7 +20,7 @@ A new slashing period starts whenever a validator is bonded and ends whenever th 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 +#### ASCII timelines *Code* @@ -57,11 +57,11 @@ Multiple infractions are committed within a single slashing period then later di 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 +## Transactions In this section we describe the processing of transactions for the `slashing` module. -#### TxUnjail +### 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`: @@ -98,11 +98,11 @@ If the validator has enough stake to be in the top hundred, they will be automat and all delegators still delegated to the validator will be rebonded and begin to again collect provisions and rewards. -### Interactions +## Interactions In this section we describe the "hooks" - slashing module code that runs when other events happen. -#### Validator Bonded +### 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 @@ -123,7 +123,7 @@ onValidatorBonded(address sdk.ValAddress) return ``` -#### Validator Unbonded +### Validator Unbonded When a validator is unbonded, we update the in-progress `SlashingPeriod` with the current block as the `EndHeight`: @@ -137,7 +137,7 @@ onValidatorUnbonded(address sdk.ValAddress) return ``` -#### Validator Slashed +### 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)` @@ -168,7 +168,7 @@ whereas if they had `1000` steak bonded initially, the total amount slashed woul 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 +## 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 050f4c9ae444..34e436e37524 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -1,6 +1,6 @@ -## State +# State -### Signing Info +## 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. @@ -51,7 +51,7 @@ Where: * `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. -### Slashing Period +## 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 From d0c87ff5bcf5ed00e5698e12976f682023c43dc0 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 20 Aug 2018 18:31:55 +0200 Subject: [PATCH 18/24] Go => pseudocode --- docs/spec/slashing/state-machine.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md index 56eb82b46bae..42b8a7f70b93 100644 --- a/docs/spec/slashing/state-machine.md +++ b/docs/spec/slashing/state-machine.md @@ -66,22 +66,22 @@ In this section we describe the processing of transactions for the `slashing` mo If a validator was automatically unbonded due to downtime and wishes to come back online & possibly rejoin the bonded set, it must send `TxUnjail`: -```golang +``` type TxUnjail struct { ValidatorAddr sdk.AccAddress } handleMsgUnjail(tx TxUnjail) - validator := getValidator(tx.ValidatorAddr) - if validator == nil + validator = getValidator(tx.ValidatorAddr) + if validator is nil fail with "No validator found" - if !validator.Jailed + if validator is not jailed fail with "Validator not jailed, cannot unjail" - info := getValidatorSigningInfo(operator) - if BlockHeader.Time.Before(info.JailedUntil) + 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 @@ -109,10 +109,10 @@ which may happen on delegation, on unjailing, etc), we create a new `SlashingPer now-bonded validator, which `StartHeight` of the current block, `EndHeight` of `0` (sentinel value for not-yet-ended), and `SlashedSoFar` of `0`: -```golang +``` onValidatorBonded(address sdk.ValAddress) - slashingPeriod := SlashingPeriod{ + slashingPeriod = SlashingPeriod{ ValidatorAddr : address, StartHeight : CurrentHeight, EndHeight : 0, @@ -127,7 +127,7 @@ onValidatorBonded(address sdk.ValAddress) When a validator is unbonded, we update the in-progress `SlashingPeriod` with the current block as the `EndHeight`: -```golang +``` onValidatorUnbonded(address sdk.ValAddress) slashingPeriod = getSlashingPeriod(address, CurrentHeight) @@ -143,7 +143,7 @@ When a validator is slashed, we look up the appropriate `SlashingPeriod` based o 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`: -```golang +``` beforeValidatorSlashed(address sdk.ValAddress, fraction sdk.Rat, infractionHeight int64) slashingPeriod = getSlashingPeriod(address, infractionHeight) From c9e5745cd7da94e28821ddf66d97d04931d425a9 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 20 Aug 2018 18:34:03 +0200 Subject: [PATCH 19/24] Clarify example --- docs/spec/slashing/state-machine.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/spec/slashing/state-machine.md b/docs/spec/slashing/state-machine.md index 42b8a7f70b93..e35b6b1794df 100644 --- a/docs/spec/slashing/state-machine.md +++ b/docs/spec/slashing/state-machine.md @@ -161,9 +161,9 @@ beforeValidatorSlashed(address sdk.ValAddress, fraction sdk.Rat, infractionHeigh 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 steak bonded, -then later slashed at `0.4` when they have `1000` steak bonded, the total amount slashed is just `40 + 100 = 140` (since the latter slash is capped at `0.1`) - -whereas if they had `1000` steak bonded initially, the total amount slashed would have been `500`. +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. From f18895532d279a19d9d59c3ba545d4694c6b8e04 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 20 Aug 2018 18:37:16 +0200 Subject: [PATCH 20/24] Clarify which offenses utilize the slashing period --- docs/spec/slashing/begin-block.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/spec/slashing/begin-block.md b/docs/spec/slashing/begin-block.md index 750228367aa2..0ab88831799a 100644 --- a/docs/spec/slashing/begin-block.md +++ b/docs/spec/slashing/begin-block.md @@ -76,6 +76,8 @@ 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). + ## Uptime tracking At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded: @@ -114,3 +116,5 @@ 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). From 73e1965195e5a10105b04ca2f7f8b5bc5147f604 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 22 Aug 2018 18:30:34 +0200 Subject: [PATCH 21/24] Update PENDING.md --- PENDING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/PENDING.md b/PENDING.md index 7ef999a7ff0c..068e452dda81 100644 --- a/PENDING.md +++ b/PENDING.md @@ -17,6 +17,7 @@ BREAKING CHANGES * Gaia * Make the transient store key use a distinct store key. [#2013](https://github.com/cosmos/cosmos-sdk/pull/2013) * [x/stake] \#1901 Validator type's Owner field renamed to Operator; Validator's GetOwner() renamed accordingly to comply with the SDK's Validator interface. + * [docs] [#2001](https://github.com/cosmos/cosmos-sdk/pull/2001) Update slashing spec for slashing period * SDK * [core] \#1807 Switch from use of rational to decimal From 4cc2054d7bf79c387af1f0dcc8203e4ac3f9797c Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 23 Aug 2018 13:43:01 +0200 Subject: [PATCH 22/24] 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 7e8b780a2d96..d50d95d36e7c 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 0ab88831799a..375e191859a4 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 000000000000..84be139e8cf9 --- /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 000000000000..36dde61f940c --- /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 000000000000..d9a8f39c151f --- /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 e35b6b1794df..000000000000 --- 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 b64e990403b1..ae426db7b392 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 000000000000..b1d523139f61 --- /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. From 4475a8c7e8d568349618e02e3e23c78e32067333 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 23 Aug 2018 13:46:44 +0200 Subject: [PATCH 23/24] Fixup links --- docs/spec/slashing/README.md | 10 +++++----- docs/spec/slashing/transactions.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/spec/slashing/README.md b/docs/spec/slashing/README.md index d50d95d36e7c..dee91eb33078 100644 --- a/docs/spec/slashing/README.md +++ b/docs/spec/slashing/README.md @@ -16,16 +16,16 @@ This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosyste ## Contents +1. **[Overview](overview.md)** 1. **[State](state.md)** 1. [SigningInfo](state.md#signing-info) 1. [SlashingPeriod](state.md#slashing-period) -1. **[Overview](overview.md)** 1. **[Transactions](transactions.md)** - 1. Unjail + 1. [Unjail](transactions.md#unjail) 1. **[Hooks](hooks.md)** - 1. Validator Bonded - 1. Validator Unbonding - 1. Validator Slashed + 1. [Validator Bonded](hooks.md#validator-bonded) + 1. [Validator Unbonded](hooks.md#validator-unbonded) + 1. [Validator Slashed](hooks.md#validator-slashed) 1. **[Begin Block](begin-block.md)** 1. [Evidence handling](begin-block.md#evidence-handling) 1. [Uptime tracking](begin-block.md#uptime-tracking) diff --git a/docs/spec/slashing/transactions.md b/docs/spec/slashing/transactions.md index b1d523139f61..be33ee096539 100644 --- a/docs/spec/slashing/transactions.md +++ b/docs/spec/slashing/transactions.md @@ -2,7 +2,7 @@ In this section we describe the processing of transactions for the `slashing` module. -### TxUnjail +### Unjail If a validator was automatically unbonded due to downtime and wishes to come back online & possibly rejoin the bonded set, it must send `TxUnjail`: From 63a85aaacf341a9a7a04d6953e869fa7b964ee7c Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 23 Aug 2018 22:45:38 +0200 Subject: [PATCH 24/24] Change ASCII diagram slightly --- docs/spec/slashing/overview.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/spec/slashing/overview.md b/docs/spec/slashing/overview.md index d9a8f39c151f..aa42f6193985 100644 --- a/docs/spec/slashing/overview.md +++ b/docs/spec/slashing/overview.md @@ -41,7 +41,7 @@ A single infraction is committed then later discovered, at which point the valid *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. @@ -49,7 +49,7 @@ Multiple infractions are committed within a single slashing period then later di *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.