diff --git a/.changelog/unreleased/features/2403-customizable-slashing-jailing.md b/.changelog/unreleased/features/2403-customizable-slashing-jailing.md new file mode 100644 index 0000000000..733860a5c7 --- /dev/null +++ b/.changelog/unreleased/features/2403-customizable-slashing-jailing.md @@ -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. \ No newline at end of file diff --git a/.changelog/unreleased/features/provider/2403-customizable-slashing-jailing.md b/.changelog/unreleased/features/provider/2403-customizable-slashing-jailing.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/.changelog/unreleased/state-breaking/2403-customizable-slashing-jailing.md b/.changelog/unreleased/state-breaking/2403-customizable-slashing-jailing.md index 2115bfdc14..733860a5c7 100644 --- a/.changelog/unreleased/state-breaking/2403-customizable-slashing-jailing.md +++ b/.changelog/unreleased/state-breaking/2403-customizable-slashing-jailing.md @@ -1,2 +1 @@ -- Allow consumer chains to customize the slashing and jailing conditions. Every consumer chain can decide the punishment for every type of infraction. - ([\#2403](https://github.com/cosmos/interchain-security/pull/2403)) \ No newline at end of file +- `[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. \ No newline at end of file diff --git a/docs/docs/build/modules/02-provider.md b/docs/docs/build/modules/02-provider.md index ef7d475193..baecb4d64e 100644 --- a/docs/docs/build/modules/02-provider.md +++ b/docs/docs/build/modules/02-provider.md @@ -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. @@ -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; } ``` @@ -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. @@ -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; } ``` @@ -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). @@ -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" + } + } } ``` @@ -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" + } + } } ``` diff --git a/docs/docs/features/slashing.md b/docs/docs/features/slashing.md index 36451c2562..6bb4f25299 100644 --- a/docs/docs/features/slashing.md +++ b/docs/docs/features/slashing.md @@ -15,7 +15,7 @@ The ICS protocol differentiates between downtime and equivocation infractions. Downtime infractions are reported by consumer chains and are acted upon on the provider as soon as they are received. 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. -Note that validators are only slashed and jailed for downtime on consumer chains that they opted in to validate on, +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. diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index 4cec258646..a99778ba32 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -572,6 +572,6 @@ message SlashJailParameters { (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 = 8 + google.protobuf.Duration jail_duration = 2 [ (gogoproto.nullable) = false, (gogoproto.stdduration) = true ]; } \ No newline at end of file diff --git a/testutil/keeper/expectations.go b/testutil/keeper/expectations.go index 52221cf283..3ae55eebf1 100644 --- a/testutil/keeper/expectations.go +++ b/testutil/keeper/expectations.go @@ -102,12 +102,12 @@ func GetMocksForHandleSlashPacket(ctx sdk.Context, mocks MockedKeepers, mocks.MockSlashingKeeper.EXPECT().IsTombstoned(ctx, expectedProviderValConsAddr.ToSdkConsAddr()).Return(false).Times(1), - - mocks.MockStakingKeeper.EXPECT().SlashWithInfractionReason(ctx, expectedProviderValConsAddr.ToSdkConsAddr(), gomock.Any(), - gomock.Any(), gomock.Any(), gomock.Any()).Return(math.NewInt(0), nil).Times(1), } 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), diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index c82f901154..273d347c2a 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -634,10 +634,11 @@ func (k msgServer) UpdateConsumer(goCtx context.Context, msg *types.MsgUpdateCon return &resp, errorsmod.Wrapf(types.ErrInvalidConsumerInfractionParameters, "cannot set infraction parameters") } - } - if err = k.Keeper.UpdateQueuedInfractionParams(ctx, consumerId, newInfractionParams); err != nil { - return &resp, errorsmod.Wrapf(types.ErrInvalidConsumerInfractionParameters, - "cannot update consumer infraction time queue") + } else { + if err = k.Keeper.UpdateQueuedInfractionParams(ctx, consumerId, newInfractionParams); err != nil { + return &resp, errorsmod.Wrapf(types.ErrInvalidConsumerInfractionParameters, + "cannot update consumer infraction time queue") + } } } diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 790bf8b10b..33a7e05e4c 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -484,16 +484,16 @@ func (k Keeper) HandleSlashPacket(ctx sdk.Context, consumerId string, data ccv.S return } - // slash validator - _, err = k.stakingKeeper.SlashWithInfractionReason(ctx, providerConsAddr.ToSdkConsAddr(), int64(infractionHeight), - data.Validator.Power, infractionParams.Downtime.SlashFraction, stakingtypes.Infraction_INFRACTION_DOWNTIME) - if err != nil { - k.Logger(ctx).Error("failed to slash vaidator", providerConsAddr.ToSdkConsAddr().String(), "err", err.Error()) - return - } - - // jail validator if !validator.IsJailed() { + // slash validator + _, err = k.stakingKeeper.SlashWithInfractionReason(ctx, providerConsAddr.ToSdkConsAddr(), int64(infractionHeight), + data.Validator.Power, infractionParams.Downtime.SlashFraction, stakingtypes.Infraction_INFRACTION_DOWNTIME) + if err != nil { + k.Logger(ctx).Error("failed to slash vaidator", providerConsAddr.ToSdkConsAddr().String(), "err", err.Error()) + return + } + + // jail validator err := k.stakingKeeper.Jail(ctx, providerConsAddr.ToSdkConsAddr()) if err != nil { k.Logger(ctx).Error("failed to jail vaidator", providerConsAddr.ToSdkConsAddr().String(), "err", err.Error()) diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index eb9b7a61ec..b45af3359f 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -1942,7 +1942,7 @@ func (m *InfractionParameters) GetDowntime() *SlashJailParameters { type SlashJailParameters struct { SlashFraction cosmossdk_io_math.LegacyDec `protobuf:"bytes,1,opt,name=slash_fraction,json=slashFraction,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"slash_fraction"` // for permanent jailing use 9223372036854775807 which is the largest value a time.Duration can hold (approximately 292 years) - JailDuration time.Duration `protobuf:"bytes,8,opt,name=jail_duration,json=jailDuration,proto3,stdduration" json:"jail_duration"` + JailDuration time.Duration `protobuf:"bytes,2,opt,name=jail_duration,json=jailDuration,proto3,stdduration" json:"jail_duration"` } func (m *SlashJailParameters) Reset() { *m = SlashJailParameters{} } @@ -2020,7 +2020,7 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 2413 bytes of a gzipped FileDescriptorProto + // 2414 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x19, 0x4b, 0x6c, 0x1b, 0xc7, 0x55, 0x2b, 0x52, 0x12, 0x39, 0xd4, 0x87, 0x1a, 0x29, 0x32, 0x25, 0x2b, 0x24, 0xbd, 0x69, 0x02, 0x35, 0xae, 0xc9, 0x48, 0x01, 0x02, 0xc3, 0x6d, 0x10, 0x50, 0x24, 0x63, 0xd1, 0x1f, 0x99, 0x5d, @@ -2159,19 +2159,19 @@ var fileDescriptor_f22ec409a72b7b72 = []byte{ 0x60, 0xd1, 0x9f, 0xa5, 0xa2, 0x90, 0x93, 0xf5, 0x70, 0xf7, 0x3d, 0x11, 0xbd, 0x7f, 0x7f, 0x5c, 0xb8, 0xec, 0x97, 0x0a, 0x66, 0x1e, 0x95, 0x08, 0x2d, 0xdb, 0x88, 0xf7, 0x4a, 0x77, 0x70, 0x17, 0x19, 0xc3, 0x1a, 0x36, 0xfe, 0xfa, 0xc5, 0x35, 0x10, 0x14, 0xa0, 0x1a, 0x36, 0xfc, 0xd2, 0xb1, - 0x20, 0xa5, 0x45, 0x91, 0xb9, 0x07, 0x16, 0x3e, 0x46, 0xc4, 0xd2, 0xc3, 0x1f, 0x39, 0x9e, 0xe7, - 0x41, 0x7b, 0x5e, 0x70, 0x86, 0xf0, 0xb7, 0xff, 0xa8, 0x80, 0x85, 0xa8, 0x6f, 0xeb, 0x21, 0x86, - 0x61, 0x1e, 0x6c, 0x54, 0xef, 0xed, 0xb7, 0xee, 0xdf, 0xad, 0x6b, 0x7a, 0x73, 0xaf, 0xd2, 0xaa, - 0xeb, 0xf7, 0xf7, 0x5b, 0xcd, 0x7a, 0xb5, 0xf1, 0x61, 0xa3, 0x5e, 0xcb, 0x4e, 0xc1, 0xd7, 0xc1, - 0xfa, 0x09, 0xbc, 0x56, 0xbf, 0xd9, 0x68, 0xb5, 0xeb, 0x5a, 0xbd, 0x96, 0x55, 0xce, 0x60, 0x6f, - 0xec, 0x37, 0xda, 0x8d, 0xca, 0x9d, 0xc6, 0x83, 0x7a, 0x2d, 0x3b, 0x0d, 0x2f, 0x83, 0x4b, 0x27, - 0xf0, 0x77, 0x2a, 0xf7, 0xf7, 0xab, 0x7b, 0xf5, 0x5a, 0x36, 0x01, 0x37, 0xc0, 0xda, 0x09, 0x64, - 0xab, 0x7d, 0xaf, 0xd9, 0xac, 0xd7, 0xb2, 0xc9, 0x33, 0x70, 0xb5, 0xfa, 0x9d, 0x7a, 0xbb, 0x5e, - 0xcb, 0xce, 0x6c, 0x24, 0x3f, 0xf9, 0x55, 0x7e, 0x6a, 0xf7, 0xa3, 0xaf, 0x9e, 0xe4, 0x95, 0xaf, - 0x9f, 0xe4, 0x95, 0x7f, 0x3c, 0xc9, 0x2b, 0x9f, 0x3e, 0xcd, 0x4f, 0x7d, 0xfd, 0x34, 0x3f, 0xf5, - 0xb7, 0xa7, 0xf9, 0xa9, 0x07, 0xef, 0x9f, 0xae, 0xd5, 0xa3, 0xbb, 0xbf, 0x16, 0xfd, 0x38, 0x33, - 0x78, 0xaf, 0xfc, 0x68, 0xfc, 0x97, 0x31, 0x59, 0xc6, 0x3b, 0xb3, 0xf2, 0x3c, 0xdf, 0xfd, 0x5f, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x35, 0xc8, 0xcf, 0x24, 0x4a, 0x1b, 0x00, 0x00, + 0x20, 0xa5, 0x45, 0x91, 0xb9, 0x07, 0x16, 0x3e, 0x46, 0xc4, 0xd2, 0xc3, 0x1f, 0x39, 0x02, 0x8b, + 0x26, 0x4a, 0x1b, 0xf3, 0x82, 0x33, 0x84, 0xbf, 0xfd, 0x47, 0x05, 0x2c, 0x44, 0x7d, 0x5b, 0x0f, + 0x31, 0x0c, 0xf3, 0x60, 0xa3, 0x7a, 0x6f, 0xbf, 0x75, 0xff, 0x6e, 0x5d, 0xd3, 0x9b, 0x7b, 0x95, + 0x56, 0x5d, 0xbf, 0xbf, 0xdf, 0x6a, 0xd6, 0xab, 0x8d, 0x0f, 0x1b, 0xf5, 0x5a, 0x76, 0x0a, 0xbe, + 0x0e, 0xd6, 0x4f, 0xe0, 0xb5, 0xfa, 0xcd, 0x46, 0xab, 0x5d, 0xd7, 0xea, 0xb5, 0xac, 0x72, 0x06, + 0x7b, 0x63, 0xbf, 0xd1, 0x6e, 0x54, 0xee, 0x34, 0x1e, 0xd4, 0x6b, 0xd9, 0x69, 0x78, 0x19, 0x5c, + 0x3a, 0x81, 0xbf, 0x53, 0xb9, 0xbf, 0x5f, 0xdd, 0xab, 0xd7, 0xb2, 0x09, 0xb8, 0x01, 0xd6, 0x4e, + 0x20, 0x5b, 0xed, 0x7b, 0xcd, 0x66, 0xbd, 0x96, 0x4d, 0x9e, 0x81, 0xab, 0xd5, 0xef, 0xd4, 0xdb, + 0xf5, 0x5a, 0x76, 0x66, 0x23, 0xf9, 0xc9, 0xaf, 0xf2, 0x53, 0xbb, 0x1f, 0x7d, 0xf5, 0x24, 0xaf, + 0x7c, 0xfd, 0x24, 0xaf, 0xfc, 0xe3, 0x49, 0x5e, 0xf9, 0xf4, 0x69, 0x7e, 0xea, 0xeb, 0xa7, 0xf9, + 0xa9, 0xbf, 0x3d, 0xcd, 0x4f, 0x3d, 0x78, 0xff, 0x74, 0xad, 0x1e, 0xdd, 0xfd, 0xb5, 0xe8, 0xc7, + 0x99, 0xc1, 0x7b, 0xe5, 0x47, 0xe3, 0xbf, 0x8c, 0xc9, 0x32, 0xde, 0x99, 0x95, 0xe7, 0xf9, 0xee, + 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf1, 0x1b, 0x2e, 0x6d, 0x4a, 0x1b, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -3604,7 +3604,7 @@ func (m *SlashJailParameters) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= n24 i = encodeVarintProvider(dAtA, i, uint64(n24)) i-- - dAtA[i] = 0x42 + dAtA[i] = 0x12 { size := m.SlashFraction.Size() i -= size @@ -8682,7 +8682,7 @@ func (m *SlashJailParameters) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 8: + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field JailDuration", wireType) }