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

feat!: Customizable Slashing and Jailing #2403

Merged
merged 15 commits into from
Dec 3, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `[x/provider]` Enable the customization of the slashing and jailing conditions for infractions committed by validators on consumer chains (as per [ADR 020](https://cosmos.github.io/interchain-security/adrs/adr-020-cutomizable_slashing_and_jailing)). Every consumer chain can decide the punishment for every type of infraction.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `[x/provider]` Enable the customization of the slashing and jailing conditions for infractions committed by validators on consumer chains (as per [ADR 020](https://cosmos.github.io/interchain-security/adrs/adr-020-cutomizable_slashing_and_jailing)). Every consumer chain can decide the punishment for every type of infraction.
47 changes: 45 additions & 2 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,43 @@
# Upgrading Replicated Security
# Upgrading Interchain Security

## Unreleased

### Provider
stana-miric marked this conversation as resolved.
Show resolved Hide resolved

```golang
func SetConsumerInfractionParams(ctx sdk.Context, pk providerkeeper.Keeper) error {
infractionParameters := DefaultInfractionParams()

activeConsumerIds := pk.GetAllActiveConsumerIds(ctx)
for _, consumerId := range activeConsumerIds {
if err := pk.SetInfractionParameters(ctx, consumerId, infractionParameters); err != nil {
return err
}
}

return nil
}

func DefaultInfractionParams() providertypes.InfractionParameters {
return providertypes.InfractionParameters{
DoubleSign: &providertypes.SlashJailParameters{
JailDuration: time.Duration(1<<63 - 1), // the largest value a time.Duration can hold 9223372036854775807 (approximately 292 years)
SlashFraction: math.LegacyNewDecWithPrec(5, 2), // 0.05
},
Downtime: &providertypes.SlashJailParameters{
JailDuration: 600 * time.Second,
SlashFraction: math.LegacyNewDec(0), // no slashing for downtime on the consumer
},
}
}
```

## v6.3.x

Upgrading from `v6.2.0` will not require state migration. To upgrade from lower versions, please check the sections below.

## v6.2.x

### Consumer

Upgrading a consumer from v4.4.x to v4.5.x and from v5.x or v6.1.x to v6.2.x requires state migrations. The following migrators should be added to the upgrade handler of the consumer chain:
Expand All @@ -22,6 +58,13 @@ func InitializeConsumerId(ctx sdk.Context, consumerKeeper consumerkeeper.Keeper)
}
```

## [v6.1.x](https://github.com/cosmos/interchain-security/releases/tag/v6.1.0)

Upgrading from `v6.0.0` will not require state migration.


## [v6.0.x](https://github.com/cosmos/interchain-security/releases/tag/v6.0.0)

### Provider

Upgrading a provider from v5.1.x requires state migrations. The following migrators should be added to the upgrade handler of the provider chain:
Expand Down Expand Up @@ -378,4 +421,4 @@ Upgrading a provider from `v1.1.0-multiden` to `v2.0.0` will require state migra

### Consumer

Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state migrations.
Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state migrations.
40 changes: 34 additions & 6 deletions docs/docs/build/modules/02-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,8 +482,8 @@ message MsgChangeRewardDenoms {
`MsgCreateConsumer` enables a user to create a consumer chain.

Both the `chain_id` and `metadata` fields are mandatory.
The `initialization_parameters`, `power_shaping_parameters`, and `allowlisted_reward_denoms` fields are optional.
The parameters not provided are set to their zero value.
The `initialization_parameters`, `power_shaping_parameters`, `infraction_parameters` and `allowlisted_reward_denoms` fields are optional.
The parameters not provided are set to their zero value. If `infraction_parameters` are not set, the default values currently configured on the provider are used.

The owner of the created consumer chain is the submitter of the message.
This message cannot be submitted as part of a governance proposal, i.e., the submitter cannot be the gov module account address.
Expand Down Expand Up @@ -516,6 +516,9 @@ message MsgCreateConsumer {

// allowlisted reward denoms by the consumer chain
AllowlistedRewardDenoms allowlisted_reward_denoms = 6;

// infraction parameters for slashing and jailing
InfractionParameters infraction_parameters = 7;
}
```

Expand All @@ -524,11 +527,11 @@ message MsgCreateConsumer {
`MsgUpdateConsumer` enables the owner of a consumer chain to update its parameters (e.g., set a new owner).

Note that only the `owner` (i.e., signer) and `consumer_id` fields are mandatory.
The others field are optional. Not providing one of them will leave the existing values unchanged.
Providing one of `metadata`, `initialization_parameters`, `power_shaping_parameters`, or `allowlisted_reward_denoms`
The others field are optional. Not providing one of them will leave the existing values unchanged. For `infraction_parameters` it is possible to update separately `downtime` and `double_sign` parameters by providing just desired one.
Providing one of `metadata`, `initialization_parameters`, `power_shaping_parameters`, `infraction_parameters.double_sign`, `infraction_parameters.downtime` or `allowlisted_reward_denoms`
will update all the containing fields.
If one of the containing fields is missing, it will be set to its zero value.
For example, updating the `initialization_parameters` without specifying the `spawn_time`, will set the `spawn_time` to zero.
For example, updating the `initialization_parameters` without specifying the `spawn_time`, will set the `spawn_time` to zero.

If the `initialization_parameters` field is set and `initialization_parameters.spawn_time > 0`, then the consumer chain will be scheduled to launch at `spawn_time`.
Updating the `spawn_time` from a positive value to zero will remove the consumer chain from the list of scheduled to launch chains.
Expand Down Expand Up @@ -568,6 +571,9 @@ message MsgUpdateConsumer {

// to update the chain id of the chain (can only be updated if the chain has not yet launched)
string new_chain_id = 8;

// infraction parameters for slashing and jailing
InfractionParameters infraction_parameters = 9;
}
```

Expand Down Expand Up @@ -829,6 +835,7 @@ In the `BeginBlock` of the provider module the following actions are performed:
- Remove every stopped consumer chain for which the removal time has passed.
- Replenish the throttling meter if necessary.
- Distribute ICS rewards to the opted in validators.
- Update consumer infraction parameters with the queued infraction parameters that were added to the queue before a time period greater than the unbonding time.

Note that for every consumer chain, the computation of its initial validator set is based on the consumer's [power shaping parameters](../../features/power-shaping.md)
and the [validators that opted in on that consumer](../../features/partial-set-security.md).
Expand Down Expand Up @@ -2873,7 +2880,17 @@ grpcurl -plaintext -d '{"consumer_id": "0"}' localhost:9090 interchain_security.
"validatorSetCap": 50,
"minStake": "1000",
"allowInactiveVals": true
}
},
"infraction_parameters":{
"double_sign":{
"slash_fraction":"0.050000000000000000",
"jail_duration":"9223372036.854775807s"
},
"downtime":{
"slash_fraction":"0.000000000000000000",
"jail_duration":"600s"
}
}
}
```

Expand Down Expand Up @@ -3572,6 +3589,17 @@ Output:
"minStake": "1000",
"allowInactiveVals": true
}
,
"infraction_parameters":{
"double_sign":{
"slash_fraction":"0.050000000000000000",
"jail_duration":"9223372036.854775807s"
},
"downtime":{
"slash_fraction":"0.000000000000000000",
"jail_duration":"600s"
}
}
}
```

Expand Down
10 changes: 7 additions & 3 deletions docs/docs/features/slashing.md
stana-miric marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ The ICS protocol differentiates between downtime and equivocation infractions.
## Downtime Infractions

Downtime infractions are reported by consumer chains and are acted upon on the provider as soon as they are received.
Instead of slashing, the provider will **_only jail_** offending validator for the duration of time established by the provider chain parameters.
Note that validators are only jailed for downtime on consumer chains that they opted in to validate on,
The provider will jail and slash the offending validator. The jailing duration and slashing fraction are determined by the consumer's downtime infraction parameters on the provider chain.
By default, validators are **_only jailed_** for downtime on consumer chains that they opted in to validate on,
or in the case of Top N chains, where they are automatically opted in by being in the Top N% of the validator set on the provider.

For preventing malicious consumer chains from harming the provider, [slash throttling](../adrs/adr-002-throttle.md) (also known as _jail throttling_) ensures that only a fraction of the provider validator set can be jailed at any given time.
Expand All @@ -24,7 +24,7 @@ For preventing malicious consumer chains from harming the provider, [slash throt

Equivocation infractions are reported by external agents (e.g., relayers) that can submit to the provider evidence of light client or double signing attacks observed on a consumer chain.
The evidence is submitted by sending `MsgSubmitConsumerMisbehaviour` or `MsgSubmitConsumerDoubleVoting` messages to the provider.
When valid evidence is received, the malicious validators are slashed, jailed, and tombstoned on the provider.
When valid evidence is received, the malicious validators are slashed, jailed, and tombstoned on the provider. The jailing duration and slashing fraction are determined by the consumer's double sign infraction parameters on the provider chain.
This is enabled through the _cryptographic verification of equivocation_ feature.
For more details, see [ADR-005](../adrs/adr-005-cryptographic-equivocation-verification.md) and [ADR-013](../adrs/adr-013-equivocation-slashing.md).

Expand Down Expand Up @@ -597,3 +597,7 @@ The following command demonstrates how to run a Hermes instance in _evidence mod
hermes evidence --chain <CONSUMER-CHAIN-ID>
```
Note that `hermes evidence` takes a `--check-past-blocks` option giving the possibility to look for older evidence (default is 100).

### Infraction parameters

Jailing and slashing for misbehavior on a consumer chain are governed by parameters defined on the provider chain for that specific consumer chain. To create or update these infraction parameters, use the MsgCreateConsumer or MsgUpdateConsumer messages. When creating a consumer chain, if custom infraction parameters are not specified, default values from the provider are applied. For updates, parameters can be modified immediately if the chain is in the pre-launch phase. If the chain has already launched, the update will be scheduled to take effect after the unbonding period expires. This ensures that changes are applied seamlessly based on the chain's lifecycle.
19 changes: 19 additions & 0 deletions proto/interchain_security/ccv/provider/v1/provider.proto
Original file line number Diff line number Diff line change
Expand Up @@ -556,3 +556,22 @@ enum ConsumerPhase {
message AllowlistedRewardDenoms {
repeated string denoms = 1;
}

//
message InfractionParameters {
SlashJailParameters double_sign = 1;
SlashJailParameters downtime = 2;
}

//
message SlashJailParameters {
bytes slash_fraction = 1 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true
];
// for permanent jailing use 9223372036854775807 which is the largest value a time.Duration can hold (approximately 292 years)
google.protobuf.Duration jail_duration = 2
[ (gogoproto.nullable) = false, (gogoproto.stdduration) = true ];
}
3 changes: 3 additions & 0 deletions proto/interchain_security/ccv/provider/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ message Chain {
// filled with these validators first, and other validators will be added to the validator set only if there are
// not enough eligible priority validators.
repeated string prioritylist = 15;
// Infraction parameters for slashing and jailing
InfractionParameters infraction_parameters = 16;
}

message QueryValidatorConsumerAddrRequest {
Expand Down Expand Up @@ -397,6 +399,7 @@ message QueryConsumerChainResponse {
ConsumerMetadata metadata = 5 [ (gogoproto.nullable) = false ];
ConsumerInitializationParameters init_params = 6;
PowerShapingParameters power_shaping_params = 7;
InfractionParameters infraction_parameters = 8;
}

message QueryConsumerGenesisTimeRequest {
Expand Down
6 changes: 6 additions & 0 deletions proto/interchain_security/ccv/provider/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,9 @@ message MsgCreateConsumer {

// allowlisted reward denoms of the consumer
AllowlistedRewardDenoms allowlisted_reward_denoms = 6;

// infraction parameters for slashing and jailing
InfractionParameters infraction_parameters = 7;
stana-miric marked this conversation as resolved.
Show resolved Hide resolved
}

// MsgCreateConsumerResponse defines response type for MsgCreateConsumer
Expand Down Expand Up @@ -399,6 +402,9 @@ message MsgUpdateConsumer {
// the chain id CANNOT be updated.
// This field is optional and can remain empty (i.e., `new_chain_id = ""`) or correspond to the chain id the chain already has.
string new_chain_id = 8;

// infraction parameters for slashing and jailing
InfractionParameters infraction_parameters = 9;
}

// MsgUpdateConsumerResponse defines response type for MsgUpdateConsumer messages
Expand Down
7 changes: 4 additions & 3 deletions tests/integration/double_vote.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,10 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() {

// verifies that the val gets slashed and has fewer tokens after the slashing
val, _ := s.providerApp.GetTestStakingKeeper().GetValidator(provCtx, provAddr.ToSdkConsAddr().Bytes())
slashFraction, err := s.providerApp.GetTestSlashingKeeper().SlashFractionDoubleSign(provCtx)
infractionParam, err := s.providerApp.GetProviderKeeper().GetInfractionParameters(provCtx, tc.consumerId)
s.Require().NoError(err)
actualTokens := math.LegacyNewDecFromInt(val.GetTokens())
s.Require().True(initialTokens.Sub(initialTokens.Mul(slashFraction)).Equal(actualTokens))
s.Require().True(initialTokens.Sub(initialTokens.Mul(infractionParam.DoubleSign.SlashFraction)).Equal(actualTokens))
} else {
s.Require().Error(err)

Expand Down Expand Up @@ -409,8 +409,9 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegationsAndRele
)
s.Require().NoError(err)

slashFraction, err := s.providerApp.GetTestSlashingKeeper().SlashFractionDoubleSign(s.providerCtx())
infractionParam, err := s.providerApp.GetProviderKeeper().GetInfractionParameters(s.providerCtx(), s.getFirstBundle().ConsumerId)
s.Require().NoError(err)
slashFraction := infractionParam.DoubleSign.SlashFraction

// check undelegations are slashed
ubds, _ = s.providerApp.GetTestStakingKeeper().GetUnbondingDelegation(s.providerCtx(), delAddr, valAddr)
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/misbehaviour.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() {
s.Require().True(s.providerApp.GetTestSlashingKeeper().IsTombstoned(s.providerCtx(), provAddr.ToSdkConsAddr()))

validator, _ := s.providerApp.GetTestStakingKeeper().GetValidator(s.providerCtx(), provAddr.ToSdkConsAddr().Bytes())
slashFraction, err := s.providerApp.GetTestSlashingKeeper().SlashFractionDoubleSign(s.providerCtx())
infractionParam, err := s.providerApp.GetProviderKeeper().GetInfractionParameters(s.providerCtx(), s.getFirstBundle().ConsumerId)
s.Require().NoError(err)
slashFraction := infractionParam.DoubleSign.SlashFraction
actualTokens := math.LegacyNewDecFromInt(validator.GetTokens())
s.Require().True(initialTokens.Sub(initialTokens.Mul(slashFraction)).Equal(actualTokens))
}
Expand Down
4 changes: 4 additions & 0 deletions testutil/ibc_testing/generic_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp](
powerShapingParameters := testkeeper.GetTestPowerShapingParameters()
powerShapingParameters.Top_N = consumerTopNParams[index] // isn't used in CreateConsumerClient

infractionPrameters := testkeeper.GetTestInfractionParameters()

consumerId := providerKeeper.FetchAndIncrementConsumerId(providerChain.GetContext())
providerKeeper.SetConsumerChainId(providerChain.GetContext(), consumerId, chainID)
err := providerKeeper.SetConsumerMetadata(providerChain.GetContext(), consumerId, consumerMetadata)
Expand All @@ -164,6 +166,8 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp](
s.Require().NoError(err)
err = providerKeeper.SetConsumerPowerShapingParameters(providerChain.GetContext(), consumerId, powerShapingParameters)
s.Require().NoError(err)
err = providerKeeper.SetInfractionParameters(providerChain.GetContext(), consumerId, infractionPrameters)
s.Require().NoError(err)
providerKeeper.SetConsumerPhase(providerChain.GetContext(), consumerId, providertypes.CONSUMER_PHASE_INITIALIZED)
if chainID == firstConsumerChainID {
FirstConsumerID = consumerId
Expand Down
5 changes: 4 additions & 1 deletion testutil/keeper/expectations.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,16 @@ func GetMocksForHandleSlashPacket(ctx sdk.Context, mocks MockedKeepers,
}

if expectJailing {
// slash
calls = append(calls, mocks.MockStakingKeeper.EXPECT().SlashWithInfractionReason(ctx, expectedProviderValConsAddr.ToSdkConsAddr(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any()).Return(math.NewInt(0), nil).Times(1))
// jail
calls = append(calls, mocks.MockStakingKeeper.EXPECT().Jail(
gomock.Eq(ctx),
gomock.Eq(expectedProviderValConsAddr.ToSdkConsAddr()),
).Return(nil))

// JailUntil is set in this code path.
calls = append(calls, mocks.MockSlashingKeeper.EXPECT().DowntimeJailDuration(ctx).Return(time.Hour, nil).Times(1))
calls = append(calls, mocks.MockSlashingKeeper.EXPECT().JailUntil(ctx,
expectedProviderValConsAddr.ToSdkConsAddr(), gomock.Any()).Return(nil).Times(1))
}
Expand Down
14 changes: 14 additions & 0 deletions testutil/keeper/unit_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/require"

"cosmossdk.io/log"
math "cosmossdk.io/math"
"cosmossdk.io/store"
"cosmossdk.io/store/metrics"
storetypes "cosmossdk.io/store/types"
Expand Down Expand Up @@ -300,6 +301,19 @@ func GetTestInitializationParameters() providertypes.ConsumerInitializationParam
}
}

func GetTestInfractionParameters() providertypes.InfractionParameters {
return providertypes.InfractionParameters{
DoubleSign: &providertypes.SlashJailParameters{
JailDuration: 1200 * time.Second,
SlashFraction: math.LegacyNewDecWithPrec(5, 1), // 0.5
},
Downtime: &providertypes.SlashJailParameters{
JailDuration: 600 * time.Second,
SlashFraction: math.LegacyNewDec(0),
},
}
}

func GetTestPowerShapingParameters() providertypes.PowerShapingParameters {
return providertypes.PowerShapingParameters{
Top_N: 0,
Expand Down
Loading