diff --git a/docs/docs/adrs/adr-013-equivocation-slashing.md b/docs/docs/adrs/adr-013-equivocation-slashing.md index 997a0eae9e..7561e99768 100644 --- a/docs/docs/adrs/adr-013-equivocation-slashing.md +++ b/docs/docs/adrs/adr-013-equivocation-slashing.md @@ -11,63 +11,43 @@ title: ADR Template Proposed ## Context -We present some approaches on how we can slash a validator on the provider chain for -an equivocation performed on the consumer chain. Currently, we can receive [evidence of equivocation](https://github.com/cosmos/interchain-security/pull/1232), -but we do not have functionality to slash the misbehaving validator on the provider chain. -In what follows, we first explain how slashing is performed on a single chain, to show why slashing on -the provider chain for a consumer equivocation is a challenging problem before proposing a potential solution. +This ADR presents some approaches on how to slash on the provider chain validators that performed equivocations on consumer chains. +Currently, the provider chain can [receive and verify evidence of equivocation](https://github.com/cosmos/interchain-security/pull/1232), but it cannot slash the misbehaving validator. + +In the remainder of this section, we explain how slashing is performed on a single chain and show why slashing on the provider for equivocation on the consumer is challenging. + +Note that future versions of the Cosmos SDK and CometBFT could modify the way we slash, etc. Therefore, a future reader of this ADR, should note that when we refer to Cosmos SDK and CometBFT we specifically refer to their [v0.47](https://docs.cosmos.network/v0.47/) and [v0.37](https://docs.cometbft.com/v0.37/) versions respectively. ### Single-chain slashing -Slashing is implemented across the [slashing](https://docs.cosmos.network/v0.50/modules/slashing) -and [staking](https://docs.cosmos.network/v0.50/modules/staking) -modules. -The slashing module’s [keeper.go](https://github.com/cosmos/cosmos-sdk/blob/5621d9d80736d6025c0f73263947e543fe88793f/x/slashing/keeper/keeper.go#L1) simply -calls -the staking module’s [Slash](https://github.com/cosmos/cosmos-sdk/blob/5621d9d80736d6025c0f73263947e543fe88793f/x/staking/keeper/slash.go#L37) method, passing -among others, the `infractionHeight` (i.e., height at which the equivocation occurred), the validator’s `power`, and -the `slashFactor` (5% in case of equivocation in Cosmos Hub -- see `gaiad query slashing params`). +Slashing is implemented across the [slashing](https://docs.cosmos.network/v0.47/modules/slashing) +and [staking](https://docs.cosmos.network/v0.47/modules/staking) modules. +The slashing module's keeper calls the staking module's `Slash()` method, passing among others, the `infractionHeight` (i.e., the height when the equivocation occurred), the validator's `power` at the infraction height, and the `slashFactor` (currently set to `5%` in case of equivocation on the Cosmos Hub). #### Slashing undelegations and redelegations -To slash undelegations, [Slash](https://github.com/cosmos/cosmos-sdk/blob/bf249162e42ef084668b942e12538cb30f9e4846/x/staking/keeper/slash.go#L109) -goes through all undelegations and [checks](https://github.com/cosmos/cosmos-sdk/blob/bf249162e42ef084668b942e12538cb30f9e4846/x/staking/keeper/slash.go#L236) -on whether the undelegation started before or after the infraction. If the undelegation started before the `infractionHeight` -this undelegation is **not** slashed, otherwise the undelegation is [slashed](https://github.com/cosmos/cosmos-sdk/blob/bf249162e42ef084668b942e12538cb30f9e4846/x/staking/keeper/slash.go#L262) by -`slashFactor`. - -The slashing of redelegations happens in a similar way, meaning that `Slash` goes through all redelegations and checks on whether -the redelegation started before or after the `infractionHeight`. - -We believe that one of the ideas behind using `infractionHeight` to decide on whether to slash an undelegation or redelegation -has to do with the fact that we want to slash only delegators whose voting power _contributed_ to the infraction. -However, this is a rather obscure idea because a delegator `D` could start unbonding at height `H` and then a validator -could intentionally perform an equivocation in the past (before `H`). In such a case, delegator `D` would still get slashed, even though `D` -did not contribute any voting power per se for the equivocation. Furthermore, this principle is not respected in general -(see "Slashing delegations" below). +To slash undelegations, `Slash` goes through all undelegations and checks whether they started before or after the infraction occurred. If an undelegation started before the `infractionHeight`, then it is **not** slashed, otherwise it is slashed by `slashFactor`. + +The slashing of redelegations happens in a similar way, meaning that `Slash` goes through all redelegations and checks whether the redelegations started before or after the `infractionHeight`. #### Slashing delegations -Besides undelegations and redelegations, we need to slash simple delegations on the validator. -This is performed by [deducting the appropriate amount of tokens](https://github.com/cosmos/cosmos-sdk/blob/5ca405ae067e7d8df98699f675e060f70a549976/x/staking/keeper/slash.go#L165) -from the validator. Note that this deduction is computed based on the voting `power` the misbehaving validator -had at the height of the equivocation. As a result of the tokens deduction, -the [tokens per share](https://docs.cosmos.network/v0.46/modules/staking/01_state.html#delegator-shares) +Besides undelegations and redelegations, the validator's delegations need to also be slashed. +This is performed by deducting the appropriate amount of tokens from the validator. Note that this deduction is computed based on the voting `power` the misbehaving validator had at the height of the equivocation. As a result of the tokens deduction, +the [tokens per share](https://docs.cosmos.network/v0.47/modules/staking#delegator-shares) reduce and hence later on, when delegators undelegate or redelegate, the delegators retrieve back less tokens, effectively having their tokens slashed. This approach of slashing delegations does not utilize the -`infractionHeight` in any way and hence the following could occur: +`infractionHeight` in any way and hence the following scenario could occur: 1. a validator `V` performs an equivocation at a height `Hi` 2. a new delegator `D` delegates to `V` after height `Hi` - 3. we receive the evidence of the equivocation by validator `V` - 4. we slash the tokens of delegator `D` + 3. evidence of the equivocation by validator `V` is received + 4. the tokens of delegator `D` are slashed In the above scenario, delegator `D` is slashed, even though `D`'s voting power did not contribute to the infraction. #### Old evidence -In the single-chain case, we never act on old evidence (e.g., from 3 years ago). This is achieved through -[CometBFT](https://docs.cometbft.com/v0.37/spec/consensus/evidence) that filters old evidence based on the parameters -`MaxAgeNumBlocks` and `MaxAgeDuration` (see [here](https://github.com/cometbft/cometbft/blob/ae9826ed75ee411c0d809797ce209ee770c15c4f/evidence/pool.go#L266)). -In Cosmos Hub, the `MaxAgeNumBlocks` is set to 1000000 (i.e., ~70 days if we assume we need ~6 sec per block) and `MaxAgeDuration` -is set to 172800000000000 ns (i.e., 2 days). Because of this check, we can easily exclude old evidence. - +In the single-chain case, old evidence (e.g., from 3 years ago) is ignored. This is achieved through +[CometBFT](https://docs.cometbft.com/v0.37/spec/consensus/evidence) that ignores old evidence based on the parameters `MaxAgeNumBlocks` and `MaxAgeDuration` (see [here](https://github.com/cometbft/cometbft/blob/v0.37.0/evidence/pool.go#271)). +Additionally, note that when the evidence is sent by CometBFT to the application, the evidence is rechecked in the [evidence module](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/evidence/keeper/infraction.go#L54) of Cosmos SDK and if it is old, the evidence is ignored. +In Cosmos Hub, the `MaxAgeNumBlocks` is set to 1000000 (i.e., ~70 days if we assume we need ~6 sec per block) and `MaxAgeDuration` is set to 172800000000000 ns (i.e., 2 days). Because of this check, we can easily exclude old evidence. ### Slashing on the provider We see that in the single-chain slashing case, we use the `infractionHeight` and the voting `power` to be able to slash. @@ -82,8 +62,7 @@ the provider chain is easy because we could have the consumer chain send us this consider that the application on the consumer chain could be _malicious_ and hence we cannot really trust anything that stems from the _application state_ of the consumer chain. -Note that when a relayer or a user sends evidence through a [MsgSubmitConsumerDoubleVoting](https://github.com/cosmos/interchain-security/pull/1232/files) message, - we get what is contained in the [DuplicateVoteEvidence](https://github.com/cometbft/cometbft/blob/ae9826ed75ee411c0d809797ce209ee770c15c4f/types/evidence.go#L36): +Note that when a relayer or a user sends evidence through a [MsgSubmitConsumerDoubleVoting](https://github.com/cosmos/interchain-security/pull/1232/files) message, we get what is contained in the [DuplicateVoteEvidence](https://github.com/cometbft/cometbft/blob/v0.37.0/types/evidence.go#L35): ```protobuf type DuplicateVoteEvidence struct { VoteA *Vote `json:"vote_a"` @@ -97,10 +76,7 @@ type DuplicateVoteEvidence struct { ``` The "abci specific information" cannot be trusted because they are not signed. Therefore, we cannot use the `ValidatorPower` in any way for slashing in the provider chain. We can get the `infractionHeight` -from the votes but this corresponds to the infraction height on the consumer chain. Furthermore, note that the -[Timestamp](https://github.com/cometbft/cometbft/blob/ae9826ed75ee411c0d809797ce209ee770c15c4f/types/vote.go#L55) in the votes -is just the [BFT time](https://github.com/cometbft/cometbft/blob/main/spec/consensus/bft-time.md), and the time -could be anything since a misbehaving validator could have included any time in the votes. +from the votes but this corresponds to the infraction height on the consumer chain. Furthermore, note that the [Timestamp](https://github.com/cometbft/cometbft/blob/v0.37.0/types/vote.go#L55) in the votes is just the [BFT time](https://github.com/cometbft/cometbft/blob/v0.37.0/spec/consensus/bft-time.md), and the time could be anything since a misbehaving validator could have included any time in the votes. Finally, note that in the single-chain case, we trust the underlying consensus engine (CometBFT) and hence when we receive evidence from CometBFT we can act on it. In the case of provider and consumer chains however we cannot trust the evidence as is. @@ -114,7 +90,7 @@ Conceptually, we have 2 different chains and there’s no guarantee that the clo One could have BFT time of 2023 on one chain and on the other chain a BFT time of 2013. Nevertheless, the chains communicate over IBC and hence some timing constraints are imposed by IBC otherwise the chains could not communicate (e.g., light clients could expire). -IBC clients contain a `maxClockDrift` parameter but this is [only used](https://github.com/tendermint/tendermint/blob/ded310093e0d771c9ed27f296921cb6b23d99f29/light/verifier.go#L253-L256) +IBC clients contain a `maxClockDrift` parameter but this is [only used](https://github.com/cometbft/cometbft/blob/v0.37.0/light/verifier.go#L176) to reject headers that come slightly from the future. For example, assume a chain `A` that has a light client `lc` that tracks chain `B` and that has `maxClockDrift` of 10 seconds. If chain `A` has time `12:58:31` and then `lc` receives a header with time `12:58:42` (that is 11 seconds from the future), then `lc` would reject his client update. @@ -163,10 +139,9 @@ slashFraction := k.slashingKeeper.SlashFractionDoubleSign(ctx) k.stakingKeeper.Slash(ctx, validatorConsAddress, infractionHeight, totalPower, slashFraction, DoubleSign) ``` -**Infraction height:** We provide a zero `infractionHeight` to the [Slash](https://github.com/cosmos/cosmos-sdk/blob/5621d9d80736d6025c0f73263947e543fe88793f/x/staking/keeper/slash.go#L37) -method in order to slash all ongoing undelegations and redelegations (see checks in [Slash](https://github.com/cosmos/cosmos-sdk/blob/5621d9d80736d6025c0f73263947e543fe88793f/x/staking/keeper/slash.go#L107), -[SlashUnbondingDelegation](https://github.com/cosmos/cosmos-sdk/blob/5621d9d80736d6025c0f73263947e543fe88793f/x/staking/keeper/slash.go#L236), and -[SlashRedelegation](https://github.com/cosmos/cosmos-sdk/blob/5621d9d80736d6025c0f73263947e543fe88793f/x/staking/keeper/slash.go#L282)). +**Infraction height:** We provide a zero `infractionHeight` to the [Slash](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/staking/keeper/slash.go#L33) method in order to slash all ongoing undelegations and redelegations (see checks in [Slash](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/staking/keeper/slash.go#L92), +[SlashUnbondingDelegation](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/staking/keeper/slash.go#L195), and +[SlashRedelegation](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/staking/keeper/slash.go#L249)). **Power:** We pass the sum of the voting power of the misbehaving validator and the power of all the ongoing undelegations and redelegations. This is a slightly more aggressive approach than just providing @@ -220,6 +195,6 @@ This is _orthogonal_ to the above solution and describes a way to check whether ## References -* [feat: add handler for consumer double voting #1232](https://github.com/cosmos/interchain-security/pull/1232#event-10206162750) -* [Cryptographic equivocation slashing design](https://forum.cosmos.network/t/cryptographic-equivocation-slashing-design/11400/1) +* [feat: add handler for consumer double voting #1232](https://github.com/cosmos/interchain-security/pull/1232) +* [Cryptographic equivocation slashing design](https://forum.cosmos.network/t/cryptographic-equivocation-slashing-design/11400) * [Update client may cause "new header has a time from the future" chain error #1445](https://github.com/informalsystems/hermes/issues/1445)