Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove equivocation proposal #1294

Merged
Merged
4 changes: 0 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ SDK_QUERY = third_party/proto/cosmos/base/query/v1beta1
SDK_BASE = third_party/proto/cosmos/base/v1beta1
SDK_UPGRADE = third_party/proto/cosmos/upgrade/v1beta1
SDK_STAKING = third_party/proto/cosmos/staking/v1beta1
SDK_EVIDENCE = third_party/proto/cosmos/evidence/v1beta1

GOGO_PROTO_TYPES = third_party/proto/gogoproto
CONFIO_TYPES = third_party/proto/confio
Expand All @@ -186,9 +185,6 @@ proto-update-deps:
@mkdir -p $(SDK_STAKING)
@curl -sSL $(SDK_PROTO_URL)/staking/v1beta1/staking.proto > $(SDK_STAKING)/staking.proto

@mkdir -p $(SDK_EVIDENCE)
@curl -sSL $(SDK_PROTO_URL)/evidence/v1beta1/evidence.proto > $(SDK_EVIDENCE)/evidence.proto

## Importing of tendermint protobuf definitions currently requires the
## use of `sed` in order to build properly with cosmos-sdk's proto file layout
## (which is the standard Buf.build FILE_LAYOUT)
Expand Down
23 changes: 12 additions & 11 deletions app/provider/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"os"
"path/filepath"

evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper"

appparams "github.com/cosmos/interchain-security/v2/app/params"

"github.com/cosmos/cosmos-sdk/baseapp"
Expand Down Expand Up @@ -48,7 +50,6 @@ import (
distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/cosmos-sdk/x/evidence"
evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper"
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
Expand Down Expand Up @@ -139,7 +140,6 @@ var (
ibcclientclient.UpgradeProposalHandler,
ibcproviderclient.ConsumerAdditionProposalHandler,
ibcproviderclient.ConsumerRemovalProposalHandler,
ibcproviderclient.EquivocationProposalHandler,
ibcproviderclient.ChangeRewardDenomsProposalHandler,
),
params.AppModuleBasic{},
Expand Down Expand Up @@ -390,14 +390,6 @@ func New(
scopedIBCKeeper,
)

// create evidence keeper with router
app.EvidenceKeeper = *evidencekeeper.NewKeeper(
appCodec,
keys[evidencetypes.StoreKey],
app.StakingKeeper,
app.SlashingKeeper,
)

app.ProviderKeeper = ibcproviderkeeper.NewKeeper(
appCodec,
keys[providertypes.StoreKey],
Expand All @@ -410,7 +402,6 @@ func New(
app.StakingKeeper,
app.SlashingKeeper,
app.AccountKeeper,
app.EvidenceKeeper,
app.DistrKeeper,
app.BankKeeper,
authtypes.FeeCollectorName,
Expand Down Expand Up @@ -459,6 +450,16 @@ func New(
ibcRouter.AddRoute(providertypes.ModuleName, providerModule)
app.IBCKeeper.SetRouter(ibcRouter)

// create evidence keeper with router
evidenceKeeper := evidencekeeper.NewKeeper(
appCodec,
keys[evidencetypes.StoreKey],
app.StakingKeeper,
app.SlashingKeeper,
)

app.EvidenceKeeper = *evidenceKeeper

skipGenesisInvariants := cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants))

// NOTE: Any module instantiated in the module manager that is later modified
Expand Down
147 changes: 147 additions & 0 deletions docs/docs/adrs/adr-013-equivocation-slashing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
Copy link
Contributor Author

@insumity insumity Sep 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding here so that markdown link checker does not complain.
No need to review this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this .md moved from somewhere else in this PR? I'm a bit confused

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have clarified. This file stems from the main branch but added here because I reference the adr-013-equivocation-slashing.md file from the docs/docs/features/slashing.md file. Without having the file in this PR, the markdown link checker would have complained.

sidebar_position: 14
title: Slashing on the provider for consumer equivocation
---
# ADR 013: Slashing on the provider for consumer equivocation

## Changelog
* 1st Sept. 2023: Initial draft

## Status
Proposed

## Context
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, CometBFT, and ibc-go could modify the way we slash, etc. Therefore, a future reader of this ADR, should note that when we refer to Cosmos SDK, CometBFT, and ibc-go we specifically refer to their [v0.47](https://docs.cosmos.network/v0.47/intro/overview), [v0.37](https://docs.cometbft.com/v0.37/) and [v7.3.0](https://github.com/cosmos/ibc-go/blob/v7.3.0) versions respectively.

### Single-chain slashing
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` 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, 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. The rationale behind this slashing mechanism, as mentioned in the [Cosmos SDK documentation](https://docs.cosmos.network/v0.47/modules/staking#delegator-shares)
> [...] is to simplify the accounting around slashing. Rather than iteratively slashing the tokens of every delegation entry, instead the Validators total bonded tokens can be slashed, effectively reducing the value of each issued delegator share.

This approach of slashing delegations does not utilize the
`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. 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, 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 for equivocation on the consumer
In the single-chain case, slashing requires both the `infractionHeight` and the voting `power`.
In order to slash on the provider for an equivocation on a consumer, we need to have both the provider's `infractionHeight` and voting `power`.
Note that the `infractionHeight` on the consumer chain must be mapped to a height on the provider chain.
Unless we have a way to find the corresponding `infractionHeight` and `power` on the provider chain, we cannot slash for equivocation on the consumer in the same way as we would slash in the single-chain case.


The challenge of figuring out the corresponding `infractionHeight` and `power` values on the provider chain is due to the following trust assumption:

- We trust the consensus layer and validator set of the consumer chains, _but we do not trust the application layer_.

As a result, we cannot trust anything that stems from the _application state_ of a consumer chain.

Note that when a relayer or a user sends evidence through a [MsgSubmitConsumerDoubleVoting](https://github.com/cosmos/interchain-security/pull/1232) message, the provider gets access to [DuplicateVoteEvidence](https://github.com/cometbft/cometbft/blob/v0.37.0/types/evidence.go#L35):
```protobuf
type DuplicateVoteEvidence struct {
VoteA *Vote `json:"vote_a"`
VoteB *Vote `json:"vote_b"`

// abci specific information
TotalVotingPower int64
ValidatorPower int64
Timestamp time.Time
}
```
The "abci specific information" fields cannot be trusted because they are not signed. Therefore,
we can use neither `ValidatorPower` for slashing on the provider chain, nor the `Timestamp` to check the evidence age. We can get the `infractionHeight` from the votes, but this `infractionHeight` corresponds to the infraction height on the consumer and **not** on the provider chain.
Similarly, when a relayer or a user sends evidence through a [MsgSubmitConsumerMisbehaviour](https://github.com/cosmos/interchain-security/pull/826) message, the provider gets access to [Misbehaviour](https://github.com/cosmos/ibc-go/blob/v7.3.0/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L79) that we cannot use to extract the infraction height, power, or the time on the provider chain.

## Proposed solution
As a first iteration, we propose the following approach. At the moment the provider receives evidence of equivocation on a consumer:
1. slash all the undelegations and redelegations using `slashFactor`;
2. slash all delegations using as voting `power` the sum of the voting power of the misbehaving validator and the power of all the ongoing undelegations and redelegations.

**Evidence expiration:** Additionally, because we cannot infer the actual time of the evidence (i.e., the timestamp of the evidence cannot be trusted), we do not consider _evidence expiration_ and hence old evidence is never ignored (e.g., the provider would act on 3 year-old evidence of equivocation on a consumer).
Additionally, we do not need to store equivocation evidence to avoid slashing a validator more than once, because we [do not slash](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/evidence/keeper/infraction.go#L94) tombstoned validators and we [tombstone](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/x/evidence/keeper/infraction.go#L138) a validator when slashed.

We do not act on evidence that was signed by a validator [consensus key](https://tutorials.cosmos.network/tutorials/9-path-to-prod/3-keys.html#what-validator-keys) that is _pruned_ when we receive the evidence. We prune a validator's consensus key if the validator has assigned a new consumer key (using `MsgAssignConsumerKey`) and an unbonding period on the consumer chain has elapsed (see [key assignment ADR](https://github.com/cosmos/interchain-security/blob/main/docs/docs/adrs/adr-001-key-assignment.md)). Note that the provider chain is informed that the unbonding period has elapsed on the consumer when the provider receives a `VSCMaturedPacket` and because of this, if the consumer delays the sending of a `VSCMaturedPacket`, we would delay the pruning of the key as well.

### Implementation
The following logic needs to be added to the [HandleConsumerDoubleVoting](https://github.com/cosmos/interchain-security/pull/1232) and [HandleConsumerMisbehaviour](https://github.com/cosmos/interchain-security/pull/826) methods:
```go
undelegationsInTokens := sdk.NewInt(0)
for _, v := range k.stakingKeeper.GetUnbondingDelegationsFromValidator(ctx, validatorAddress) {
for _, entry := range v.Entries {
if entry.IsMature(now) && !entry.OnHold() {
// undelegation no longer eligible for slashing, skip it
continue
}
undelegationsInTokens = undelegationsInTokens.Add(entry.InitialBalance)
}
}

redelegationsInTokens := sdk.NewInt(0)
for _, v := range k.stakingKeeper.GetRedelegationsFromSrcValidator(ctx, validatorAddress) {
for _, entry := range v.Entries {
if entry.IsMature(now) && !entry.OnHold() {
// redelegation no longer eligible for slashing, skip it
continue
}
redelegationsInTokens = redelegationsInTokens.Add(entry.InitialBalance)
}
}

infractionHeight := 0
undelegationsAndRedelegationsInPower = sdk.TokensToConsensusPower(undelegationsInTokens.Add(redelegationsInTokens))
totalPower := validator's voting power + undelegationsAndRedelegationsInPower
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/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 when the evidence was received (i.e., at evidence height) and the power of all the ongoing undelegations and redelegations.
If we assume that the `slashFactor` is `5%`, then the voting power we pass is `power + totalPower(undelegations) + totalPower(redelegations)`.
Hence, when the `Slash` method slashes all the undelegations and redelegations it would end up with `0.05 * power + 0.05 * totalPower(undelegations) + 0.05 * totalPower(redelegations) - 0.05 * totalPower(undelegations) - 0.05 * totalPower(redelegations) = 0.05 * power` and hence it would slash `5%` of the validator's power when the evidence is received.

### Positive
With the proposed approach we can quickly implement slashing functionality on the provider chain for consumer chain equivocations.
This approach does not need to change the staking module and therefore does not change in any way how slashing is performed today for a single chain.

### Negative

- We _definitely_ slash more when it comes to undelegations and redelegations because we slash for all of them without considering an `infractionHeight`.
- We _potentially_ slash more than what we would have slashed if we knew the voting `power` at the corresponding `infractionHeight` in the provider chain.
- We slash on old evidence of equivocation on a consumer.


## References
* [ADR 005: Cryptographic verification of equivocation evidence](https://github.com/cosmos/interchain-security/blob/main/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md)
* [EPIC tracking cryptographic equivocation feature](https://github.com/cosmos/interchain-security/issues/732)
* [Cosmos Hub Forum discussion on cryptographic equivocation slashing](https://forum.cosmos.network/t/cryptographic-equivocation-slashing-design/11400)
58 changes: 0 additions & 58 deletions docs/docs/features/proposals.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,37 +74,6 @@ Minimal example:
}
```

## `EquivocationProposal`
:::tip
`EquivocationProposal` will only be accepted on the provider chain if at least one of the consumer chains submits equivocation evidence to the provider.
Sending equivocation evidence to the provider is handled automatically by the interchain security protocol when an equivocation infraction is detected on the consumer chain.
:::

Proposal type used to suggest slashing a validator for double signing on consumer chain.
When proposals of this type are passed, the validator in question will be slashed for equivocation on the provider chain.

:::warning
Take note that an equivocation slash causes a validator to be tombstoned (can never re-enter the active set).
Tombstoning a validator on the provider chain will remove the validator from the validator set of all consumer chains.
:::

Minimal example:
```js
{
"title": "Validator-1 double signed on consumerchain-1",
"description": "Here is more information about the infraction so you can verify it yourself",
// the list of equivocations that will be processed
"equivocations": [
{
"height": 14444680,
"time": "2023-02-28T20:40:00.000000Z",
"power": 5500000,
"consensus_address": "<consensus address ON THE PROVIDER>"
}
]
}
```

## ChangeRewardDenomProposal
:::tip
`ChangeRewardDenomProposal` will only be accepted on the provider chain if at least one of the denomsToAdd or denomsToRemove fields is populated with at least one denom. Also, a denom cannot be repeated in both sets.
Expand All @@ -121,30 +90,3 @@ Minimal example:
"denomsToRemove": []
}
```

### Notes
When submitting equivocation evidence through an `EquivocationProposal` please take note that you need to use the consensus address (`valcons`) of the offending validator on the **provider chain**.
Besides that, the `height` and the `time` fields should be mapped to the **provider chain** to avoid your evidence being rejected.

Before submitting the proposal please check that the evidence is not outdated by comparing the infraction height with the `max_age_duration` and `max_age_num_blocks` consensus parameters of the **provider chain**.

### Gaia example:
```
➜ ~ cat genesis.json | jq ".consensus_params"
{
"block": {
...
},
"evidence": {
"max_age_duration": "172800000000000",
"max_age_num_blocks": "1000000",
"max_bytes": "50000"
},
"validator": {
...
},
"version": {}
}
```

Any `EquivocationProposal` transactions that submit evidence with `height` older than `max_age_num_blocks` and `time` older than `max_age_duration` will be considered invalid.
Loading
Loading