Skip to content

Commit

Permalink
Customizable Slashing and Jailing
Browse files Browse the repository at this point in the history
  • Loading branch information
stana-miric committed Nov 15, 2024
1 parent af5d7a4 commit 9fa5ec3
Show file tree
Hide file tree
Showing 23 changed files with 1,825 additions and 503 deletions.
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 = 8
[ (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;
}

// 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
24 changes: 22 additions & 2 deletions x/ccv/provider/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,16 @@ where create_consumer.json has the following structure:
"allow_inactive_vals": false,
"prioritylist": ["cosmosvalcons..."]
},
"infraction_parameters": {
"double_sign": {
"slash_fraction": "0.05",
"jail_duration": "9223372036854775807"
},
"downtime": {
"slash_fraction": "0.0001",
"jail_duration": "600000000000"
}
},
"allowlisted_reward_denoms": {
"denoms": ["ibc/...", "ibc/..."]
}
Expand Down Expand Up @@ -291,7 +301,7 @@ The parameters not provided are set to their zero value.
}

msg, err := types.NewMsgCreateConsumer(submitter, consCreate.ChainId, consCreate.Metadata, consCreate.InitializationParameters,
consCreate.PowerShapingParameters, consCreate.AllowlistedRewardDenoms)
consCreate.PowerShapingParameters, consCreate.AllowlistedRewardDenoms, consCreate.InfractionParameters)
if err != nil {
return err
}
Expand Down Expand Up @@ -356,6 +366,16 @@ where update_consumer.json has the following structure:
"allow_inactive_vals": false,
"prioritylist": ["cosmosvalcons..."]
},
"infraction_parameters": {
"double_sign": {
"slash_fraction": "0.05",
"jail_duration": "9223372036854775807"
},
"downtime": {
"slash_fraction": "0.0001",
"jail_duration": "600000000000"
}
},
"allowlisted_reward_denoms": {
"denoms": ["ibc/...", "ibc/..."]
}
Expand Down Expand Up @@ -398,7 +418,7 @@ If one of the fields is missing, it will be set to its zero value.
}

msg, err := types.NewMsgUpdateConsumer(owner, consUpdate.ConsumerId, consUpdate.NewOwnerAddress, consUpdate.Metadata,
consUpdate.InitializationParameters, consUpdate.PowerShapingParameters, consUpdate.AllowlistedRewardDenoms, consUpdate.NewChainId)
consUpdate.InitializationParameters, consUpdate.PowerShapingParameters, consUpdate.AllowlistedRewardDenoms, consUpdate.NewChainId, consUpdate.InfractionParameters)
if err != nil {
return err
}
Expand Down
33 changes: 20 additions & 13 deletions x/ccv/provider/keeper/consumer_equivocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"
evidencetypes "cosmossdk.io/x/evidence/types"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -75,10 +74,16 @@ func (k Keeper) HandleConsumerDoubleVoting(
types.NewConsumerConsAddress(sdk.ConsAddress(evidence.VoteA.ValidatorAddress.Bytes())),
)

if err = k.SlashValidator(ctx, providerAddr); err != nil {
// get the consumer's infraction parameters
infractionParams, err := k.GetInfractionParameters(ctx, consumerId)
if err != nil {
return err
}

if err = k.SlashValidator(ctx, providerAddr, infractionParams.DoubleSign, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN); err != nil {
return err
}
if err = k.JailAndTombstoneValidator(ctx, providerAddr); err != nil {
if err = k.JailAndTombstoneValidator(ctx, providerAddr, infractionParams.DoubleSign); err != nil {
return err
}

Expand Down Expand Up @@ -186,19 +191,24 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, consumerId string, m

provAddrs := make([]types.ProviderConsAddress, 0, len(byzantineValidators))

infractionParams, err := k.GetInfractionParameters(ctx, consumerId)
if err != nil {
return err
}

// slash, jail, and tombstone the Byzantine validators
for _, v := range byzantineValidators {
providerAddr := k.GetProviderAddrFromConsumerAddr(
ctx,
consumerId,
types.NewConsumerConsAddress(sdk.ConsAddress(v.Address.Bytes())),
)
err := k.SlashValidator(ctx, providerAddr)
err := k.SlashValidator(ctx, providerAddr, infractionParams.DoubleSign, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN)
if err != nil {
logger.Error("failed to slash validator: %s", err)
continue
}
err = k.JailAndTombstoneValidator(ctx, providerAddr)
err = k.JailAndTombstoneValidator(ctx, providerAddr, infractionParams.DoubleSign)
// JailAndTombstoneValidator should never return an error if
// SlashValidator succeeded because both methods fail if the malicious
// validator is either or both !found, unbonded and tombstoned.
Expand Down Expand Up @@ -411,7 +421,7 @@ func verifyLightBlockCommitSig(lightBlock tmtypes.LightBlock, sigIdx int) error
//

// JailAndTombstoneValidator jails and tombstones the validator with the given provider consensus address
func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress) error {
func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress, jailingParams *types.SlashJailParameters) error {
validator, err := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr())
if err != nil && errors.Is(err, stakingtypes.ErrNoValidatorFound) {
return errorsmod.Wrapf(slashingtypes.ErrNoValidatorForAddress, "provider consensus address: %s", providerAddr.String())
Expand All @@ -435,7 +445,8 @@ func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.Pr
}
}

err = k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime)
jailEndTime := ctx.BlockTime().Add(jailingParams.JailDuration)
err = k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), jailEndTime)
if err != nil {
return fmt.Errorf("fail to set jail duration for validator: %s: %s", providerAddr.String(), err)
}
Expand Down Expand Up @@ -481,7 +492,7 @@ func (k Keeper) ComputePowerToSlash(ctx sdk.Context, validator stakingtypes.Vali
}

// SlashValidator slashes validator with given provider Address
func (k Keeper) SlashValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress) error {
func (k Keeper) SlashValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress, slashingParams *types.SlashJailParameters, slashingReason stakingtypes.Infraction) error {
validator, err := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr())
if err != nil && errors.Is(err, stakingtypes.ErrNoValidatorFound) {
return errorsmod.Wrapf(slashingtypes.ErrNoValidatorForAddress, "provider consensus address: %s", providerAddr.String())
Expand Down Expand Up @@ -518,16 +529,12 @@ func (k Keeper) SlashValidator(ctx sdk.Context, providerAddr types.ProviderConsA
powerReduction := k.stakingKeeper.PowerReduction(ctx)
totalPower := k.ComputePowerToSlash(ctx, validator, undelegations, redelegations, lastPower, powerReduction)

slashFraction, err := k.slashingKeeper.SlashFractionDoubleSign(ctx)
if err != nil {
return err
}
consAdrr, err := validator.GetConsAddr()
if err != nil {
return err
}

_, err = k.stakingKeeper.SlashWithInfractionReason(ctx, consAdrr, 0, totalPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN)
_, err = k.stakingKeeper.SlashWithInfractionReason(ctx, consAdrr, 0, totalPower, slashingParams.SlashFraction, slashingReason)
return err
}

Expand Down
31 changes: 21 additions & 10 deletions x/ccv/provider/keeper/consumer_equivocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/stretchr/testify/require"

"cosmossdk.io/math"
evidencetypes "cosmossdk.io/x/evidence/types"

cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
Expand Down Expand Up @@ -383,6 +382,7 @@ func TestJailAndTombstoneValidator(t *testing.T) {
func(ctx sdk.Context, mocks testkeeper.MockedKeepers,
provAddr types.ProviderConsAddress,
) []*gomock.Call {
jailEndTime := ctx.BlockTime().Add(getTestInfractionParameters().DoubleSign.JailDuration)
return []*gomock.Call{
mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(
ctx, providerConsAddr.ToSdkConsAddr()).Return(
Expand All @@ -393,7 +393,7 @@ func TestJailAndTombstoneValidator(t *testing.T) {
false,
).Times(1),
mocks.MockSlashingKeeper.EXPECT().JailUntil(
ctx, providerConsAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime).
ctx, providerConsAddr.ToSdkConsAddr(), jailEndTime).
Times(1),
mocks.MockSlashingKeeper.EXPECT().Tombstone(
ctx, providerConsAddr.ToSdkConsAddr()).
Expand All @@ -407,6 +407,7 @@ func TestJailAndTombstoneValidator(t *testing.T) {
func(ctx sdk.Context, mocks testkeeper.MockedKeepers,
provAddr types.ProviderConsAddress,
) []*gomock.Call {
jailEndTime := ctx.BlockTime().Add(getTestInfractionParameters().DoubleSign.JailDuration)
return []*gomock.Call{
mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(
ctx, providerConsAddr.ToSdkConsAddr()).Return(
Expand All @@ -420,7 +421,7 @@ func TestJailAndTombstoneValidator(t *testing.T) {
ctx, providerConsAddr.ToSdkConsAddr()).
Times(1),
mocks.MockSlashingKeeper.EXPECT().JailUntil(
ctx, providerConsAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime).
ctx, providerConsAddr.ToSdkConsAddr(), jailEndTime).
Times(1),
mocks.MockSlashingKeeper.EXPECT().Tombstone(
ctx, providerConsAddr.ToSdkConsAddr()).
Expand All @@ -438,7 +439,7 @@ func TestJailAndTombstoneValidator(t *testing.T) {
gomock.InOrder(tc.expectedCalls(ctx, mocks, tc.provAddr)...)

// Execute method and assert expected mock calls
providerKeeper.JailAndTombstoneValidator(ctx, tc.provAddr)
providerKeeper.JailAndTombstoneValidator(ctx, tc.provAddr, getTestInfractionParameters().DoubleSign)

ctrl.Finish()
}
Expand Down Expand Up @@ -677,7 +678,7 @@ func TestSlashValidator(t *testing.T) {
currentPower := int64(3000)

powerReduction := math.NewInt(2)
slashFraction, _ := math.LegacyNewDecFromStr("0.5")
slashFraction := getTestInfractionParameters().DoubleSign.SlashFraction

// the call to `Slash` should provide an `infractionHeight` of 0 and an expected power of
// (750 (undelegations) + 750 (redelegations)) / 2 (= powerReduction) + 3000 (currentPower) = 3750
Expand Down Expand Up @@ -732,16 +733,13 @@ func TestSlashValidator(t *testing.T) {
}
return sum, nil
}).AnyTimes(),
mocks.MockSlashingKeeper.EXPECT().
SlashFractionDoubleSign(ctx).
Return(slashFraction, nil),
mocks.MockStakingKeeper.EXPECT().
SlashWithInfractionReason(ctx, consAddr, expectedInfractionHeight, expectedSlashPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN).Return(math.NewInt(expectedSlashPower), nil).
Times(1),
}

gomock.InOrder(expectedCalls...)
keeper.SlashValidator(ctx, providerAddr)
keeper.SlashValidator(ctx, providerAddr, getTestInfractionParameters().DoubleSign, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN)
}

// TestSlashValidatorDoesNotSlashIfValidatorIsUnbonded asserts that `SlashValidator` does not call
Expand All @@ -768,7 +766,7 @@ func TestSlashValidatorDoesNotSlashIfValidatorIsUnbonded(t *testing.T) {
}

gomock.InOrder(expectedCalls...)
keeper.SlashValidator(ctx, providerAddr)
keeper.SlashValidator(ctx, providerAddr, getTestInfractionParameters().DoubleSign, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN)
}

func TestEquivocationEvidenceMinHeightCRUD(t *testing.T) {
Expand All @@ -788,3 +786,16 @@ func TestEquivocationEvidenceMinHeightCRUD(t *testing.T) {
height = keeper.GetEquivocationEvidenceMinHeight(ctx, chainID)
require.Zero(t, height, "equivocation evidence min height should be 0")
}

func getTestInfractionParameters() *types.InfractionParameters {
return &types.InfractionParameters{
DoubleSign: &types.SlashJailParameters{
JailDuration: 1200 * time.Second,
SlashFraction: math.LegacyNewDecWithPrec(5, 1), // 0.5
},
Downtime: &types.SlashJailParameters{
JailDuration: 600 * time.Second,
SlashFraction: math.LegacyNewDec(0),
},
}
}
2 changes: 2 additions & 0 deletions x/ccv/provider/keeper/consumer_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@ func (k Keeper) DeleteConsumerChain(ctx sdk.Context, consumerId string) (err err

k.DeleteConsumerRemovalTime(ctx, consumerId)

k.RemoveConsumerInfractionQueuedData(ctx, consumerId)

// TODO (PERMISSIONLESS) add newly-added state to be deleted

// Note that we do not delete ConsumerIdToChainIdKey and ConsumerIdToPhase, as well
Expand Down
29 changes: 21 additions & 8 deletions x/ccv/provider/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ func (k Keeper) GetConsumerChain(ctx sdk.Context, consumerId string) (types.Chai
return types.Chain{}, fmt.Errorf("cannot find power shaping parameters for consumer (%s): %s", consumerId, err.Error())
}

infractionParameters, err := k.GetInfractionParameters(ctx, consumerId)
if err != nil {
return types.Chain{}, fmt.Errorf("cannot find infraction parameters for consumer (%s): %s", consumerId, err.Error())
}

// Get the minimal power in the top N for the consumer chain
minPowerInTopN, found := k.GetMinimumPowerInTopN(ctx, consumerId)
if !found {
Expand Down Expand Up @@ -150,6 +155,7 @@ func (k Keeper) GetConsumerChain(ctx sdk.Context, consumerId string) (types.Chai
ConsumerId: consumerId,
AllowlistedRewardDenoms: &types.AllowlistedRewardDenoms{Denoms: allowlistedRewardDenoms},
Prioritylist: strPrioritylist,
InfractionParameters: &infractionParameters,
}, nil
}

Expand Down Expand Up @@ -618,18 +624,25 @@ func (k Keeper) QueryConsumerChain(goCtx context.Context, req *types.QueryConsum
initParams, _ := k.GetConsumerInitializationParameters(ctx, consumerId)
powerParams, _ := k.GetConsumerPowerShapingParameters(ctx, consumerId)

infractionParams, err := k.GetInfractionParameters(ctx, consumerId)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "cannot retrieve infraction parameters for consumer id: %s", consumerId)
}

return &types.QueryConsumerChainResponse{
ChainId: chainId,
ConsumerId: consumerId,
OwnerAddress: ownerAddress,
Phase: phase.String(),
Metadata: metadata,
InitParams: &initParams,
PowerShapingParams: &powerParams,
ChainId: chainId,
ConsumerId: consumerId,
OwnerAddress: ownerAddress,
Phase: phase.String(),
Metadata: metadata,
InitParams: &initParams,
PowerShapingParams: &powerParams,
InfractionParameters: &infractionParams,
}, nil
}

// QueryConsumerGenesisTime returns the genesis time
// QueryConsumerGenesisTime returns the genesis time
//
// of the consumer chain associated with the provided consumer id
func (k Keeper) QueryConsumerGenesisTime(goCtx context.Context, req *types.QueryConsumerGenesisTimeRequest) (*types.QueryConsumerGenesisTimeResponse, error) {
if req == nil {
Expand Down
Loading

0 comments on commit 9fa5ec3

Please sign in to comment.