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

Soft opt out #833

Merged
merged 32 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
584b3f2
WIP soft opt out code with incomplete boilerplate
jtremback Apr 7, 2023
01ce4d4
proto changes
shaspitz Apr 7, 2023
a5880e3
Seems like it should work
jtremback Apr 7, 2023
19a31c8
Unit test for UpdateLargestSoftOptOutValidatorPower
jtremback Apr 7, 2023
a1da5e3
fixes and renames, unit tests work
jtremback Apr 7, 2023
ed09b43
update comment
shaspitz Apr 10, 2023
353c8db
log
shaspitz Apr 10, 2023
282716a
Update proto/interchain_security/ccv/consumer/v1/consumer.proto
shaspitz Apr 11, 2023
fef5415
better validation for soft opt out threshhold
shaspitz Apr 11, 2023
0d5b248
improve test
shaspitz Apr 11, 2023
3506858
slicestable
shaspitz Apr 11, 2023
66ee828
semantics and improved test
shaspitz Apr 11, 2023
421bee2
use correct key util
shaspitz Apr 11, 2023
9b6c50a
Merge branch 'main' into soft-opt-out
shaspitz Apr 11, 2023
edac41a
Update module.go
shaspitz Apr 11, 2023
df7ddaf
comment
shaspitz Apr 11, 2023
059c750
updated semantics
shaspitz Apr 11, 2023
f862642
separate files
shaspitz Apr 11, 2023
b4ede6b
fix TestMakeConsumerGenesis test
shaspitz Apr 11, 2023
5b86af8
fix naming
shaspitz Apr 11, 2023
171a573
change upper bound on soft opt out thresh
shaspitz Apr 12, 2023
b7b35aa
fix test
shaspitz Apr 12, 2023
741da8a
allow empty valset for tests
shaspitz Apr 12, 2023
59910f6
Merge branch 'main' into soft-opt-out
shaspitz Apr 12, 2023
65bf8da
gofumpt and fix from merge
shaspitz Apr 12, 2023
6bfb60e
Merge branch 'main' into soft-opt-out
mpoke Apr 13, 2023
16180be
Update x/ccv/consumer/types/params_test.go
mpoke Apr 13, 2023
d495656
Update x/ccv/consumer/types/params.go
mpoke Apr 13, 2023
2d60b2f
Soft opt out diff tests (#847)
shaspitz Apr 13, 2023
d686044
add comment about beginblocker order requirement for soft opt-out
sainoe Apr 13, 2023
d0bff24
Merge branch 'main' into soft-opt-out
sainoe Apr 13, 2023
8d2a613
Merge branch 'main' into soft-opt-out
sainoe Apr 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions proto/interchain_security/ccv/consumer/v1/consumer.proto
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ message Params {
// which should be smaller than that of the provider in general.
google.protobuf.Duration unbonding_period = 9
[(gogoproto.nullable) = false, (gogoproto.stdduration) = true];

// The threshold for the percentage of validators at the bottom of the set who
// can opt out of running the consumer chain without being punished. For example, a
// value of 5% means that the validators in the smallest 5% of the set can opt out
shaspitz marked this conversation as resolved.
Show resolved Hide resolved
string soft_opt_out_threshold = 10;
}

// LastTransmissionBlockHeight is the last time validator holding
Expand Down
1 change: 1 addition & 0 deletions tests/difference/core/driver/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ func (b *Builder) createConsumerGenesis(client *ibctmtypes.ClientState) *consume
consumertypes.DefaultConsumerRedistributeFrac,
consumertypes.DefaultHistoricalEntries,
b.initState.UnbondingC,
"0.05",
)
return consumertypes.NewInitialGenesisState(client, providerConsState, valUpdates, params)
}
Expand Down
12 changes: 12 additions & 0 deletions testutil/keeper/unit_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@ func GenPubKey() (crypto.PubKey, error) {
return cryptocodec.ToTmPubKeyInterface(privKey.PrivKey.PubKey())
}

// Generates a public key for unit tests (abiding by tricky interface implementations from tm/sdk) or panics
func MustGenPubKey() crypto.PubKey {
shaspitz marked this conversation as resolved.
Show resolved Hide resolved
privKey := PrivateKey{ed25519.GenPrivKey()}
key, err := cryptocodec.ToTmPubKeyInterface(privKey.PrivKey.PubKey())

if err != nil {
panic(err)
}

return key
}

// Obtains slash packet data with a newly generated key, and randomized field values
func GetNewSlashPacketData() types.SlashPacketData {
b1 := make([]byte, 8)
Expand Down
9 changes: 9 additions & 0 deletions x/ccv/consumer/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func (k Keeper) GetParams(ctx sdk.Context) types.Params {
k.GetConsumerRedistributionFrac(ctx),
k.GetHistoricalEntries(ctx),
k.GetUnbondingPeriod(ctx),
k.GetSoftOptOutThreshold(ctx),
)
}

Expand Down Expand Up @@ -106,3 +107,11 @@ func (k Keeper) GetUnbondingPeriod(ctx sdk.Context) time.Duration {
k.paramStore.Get(ctx, types.KeyConsumerUnbondingPeriod, &period)
return period
}

// GetSoftOptOutThreshold returns the percentage of validators at the bottom of the set
// that can opt out of running the consumer chain
func (k Keeper) GetSoftOptOutThreshold(ctx sdk.Context) string {
var str string
k.paramStore.Get(ctx, types.KeySoftOptOutThreshold, &str)
return str
}
3 changes: 2 additions & 1 deletion x/ccv/consumer/keeper/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ func TestParams(t *testing.T) {
types.DefaultConsumerRedistributeFrac,
types.DefaultHistoricalEntries,
types.DefaultConsumerUnbondingPeriod,
types.DefaultSoftOptOutThreshold,
) // these are the default params, IBC suite independently sets enabled=true

params := consumerKeeper.GetParams(ctx)
require.Equal(t, expParams, params)

newParams := types.NewParams(false, 1000,
"channel-2", "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm",
7*24*time.Hour, 25*time.Hour, "0.5", 500, 24*21*time.Hour)
7*24*time.Hour, 25*time.Hour, "0.5", 500, 24*21*time.Hour, "0.05")
consumerKeeper.SetParams(ctx, newParams)
params = consumerKeeper.GetParams(ctx)
require.Equal(t, newParams, params)
Expand Down
63 changes: 63 additions & 0 deletions x/ccv/consumer/keeper/validators.go
shaspitz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package keeper

import (
"encoding/binary"
"sort"
"time"

cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
Expand Down Expand Up @@ -100,13 +102,74 @@ func (k Keeper) ValidatorByConsAddr(sdk.Context, sdk.ConsAddress) stakingtypes.V
return stakingtypes.Validator{}
}

func (k Keeper) SetLargestSoftOptOutValidatorPower(ctx sdk.Context, power uint64) {
shaspitz marked this conversation as resolved.
Show resolved Hide resolved
store := ctx.KVStore(k.storeKey)
store.Set(types.SoftOptOutThresholdPowerKey(), sdk.Uint64ToBigEndian(power))
}

// UpdateLargestSoftOptOutValidatorPower sets the largest validator power that is allowed to soft opt out
// This is the largest validator power such that the sum of the power of all validators with a lower or equal power
// is less than 5% of the total power of all validators
func (k Keeper) UpdateLargestSoftOptOutValidatorPower(ctx sdk.Context) {
// get all validators
valset := k.GetAllCCValidator(ctx)

// sort validators by power ascending
sort.Slice(valset, func(i, j int) bool {
shaspitz marked this conversation as resolved.
Show resolved Hide resolved
return valset[i].Power < valset[j].Power
})

// get total power in set
totalPower := sdk.ZeroDec()
for _, val := range valset {
totalPower = totalPower.Add(sdk.NewDecFromInt(sdk.NewInt(val.Power)))
}

// get power of the biggest validator who is allowed to soft opt out
powerSum := sdk.ZeroDec()
for _, val := range valset {
powerSum = powerSum.Add(sdk.NewDecFromInt(sdk.NewInt(val.Power)))
// if powerSum / totalPower > SoftOptOutThreshold
if powerSum.Quo(totalPower).GT(sdk.MustNewDecFromStr(k.GetSoftOptOutThreshold(ctx))) {
// set largestSoftOptOutValidatorPower to the power of this validator
k.SetLargestSoftOptOutValidatorPower(ctx, uint64(val.Power))
k.Logger(ctx).Info("largest soft opt out validator power updated", "power", val.Power)
return
}
}
// This will be hit if the SoftOptOutThreshold param is equal to 1
shaspitz marked this conversation as resolved.
Show resolved Hide resolved
panic("unreachable")
}

// GetSoftOptOutThresholdPower returns the largest validator power that is allowed to soft opt out
func (k Keeper) GetSoftOptOutThresholdPower(ctx sdk.Context) int64 {
shaspitz marked this conversation as resolved.
Show resolved Hide resolved
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.SoftOptOutThresholdPowerKey())
if bz == nil {
return 0
}
return int64(binary.BigEndian.Uint64(bz))
}

// Slash queues a slashing request for the the provider chain
// All queued slashing requests will be cleared in EndBlock
func (k Keeper) Slash(ctx sdk.Context, addr sdk.ConsAddress, infractionHeight, power int64, _ sdk.Dec, infraction stakingtypes.InfractionType) {
if infraction == stakingtypes.InfractionEmpty {
return
}

// if this is a downtime infraction and the validator is allowed to
// soft opt out, do not queue a slash packet
if infraction == stakingtypes.Downtime {
Copy link
Contributor

Choose a reason for hiding this comment

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

The infractionHeight for downtime misbehavior is the current height minus two, see https://github.com/cosmos/cosmos-sdk/blob/b05b6fe651514c11af3d4160f7c75fbaad92d5db/x/slashing/keeper/infractions.go#L89. This means that SoftOptOutThresholdPower should contain info from two height ago.

Copy link
Contributor

Choose a reason for hiding this comment

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

@smarshall-spitzbart please open an issue to address this later.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually that's not needed as the ccv.BeginBlock will be called after slashing.BeginBlock, see

app.MM.SetOrderBeginBlockers(
Thanks @sainoe.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mpoke even with this order of begin blockers, wouldn't the soft opt out logic still be off by 1 block? (not 2)

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think so. Consider the following example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah makes sense, neat how that works out 👍

if power < k.GetSoftOptOutThresholdPower(ctx) {
// soft opt out
k.Logger(ctx).Debug("soft opt out",
"validator", addr,
"power", power,
)
return
}
}
// get VSC ID for infraction height
vscID := k.GetHeightValsetUpdateID(ctx, uint64(infractionHeight))

Expand Down
57 changes: 57 additions & 0 deletions x/ccv/consumer/keeper/validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,60 @@ func SetCCValidators(tb testing.TB, consumerKeeper keeper.Keeper,
consumerKeeper.SetCCValidator(ctx, ccv)
}
}

// Tests that UpdateLargestSoftOptOutValidatorPower updates the largest soft opt out validator power
// Soft opt out allows the bottom 5% of validators in the set to opt out. UpdateLargestSoftOptOutValidatorPower
// should update the largest soft opt out validator power to the power of the largest validator which is allowed to opt out
func TestUpdateLargestSoftOptOutValidatorPower(t *testing.T) {
testCases := []struct {
name string
// validators to set in store
validators []*tmtypes.Validator
// expected largest soft opt out validator power
expLargestSoftOptOutValidatorPower int64
}{
{
name: "One",
validators: []*tmtypes.Validator{
tmtypes.NewValidator(testkeeper.MustGenPubKey(), 1),
tmtypes.NewValidator(testkeeper.MustGenPubKey(), 1),
tmtypes.NewValidator(testkeeper.MustGenPubKey(), 1),
tmtypes.NewValidator(testkeeper.MustGenPubKey(), 3),
tmtypes.NewValidator(testkeeper.MustGenPubKey(), 49),
tmtypes.NewValidator(testkeeper.MustGenPubKey(), 51),
},
expLargestSoftOptOutValidatorPower: 3,
},
{
name: "Two",
validators: []*tmtypes.Validator{
tmtypes.NewValidator(testkeeper.MustGenPubKey(), 1),
tmtypes.NewValidator(testkeeper.MustGenPubKey(), 1),
tmtypes.NewValidator(testkeeper.MustGenPubKey(), 1),
tmtypes.NewValidator(testkeeper.MustGenPubKey(), 3),
tmtypes.NewValidator(testkeeper.MustGenPubKey(), 500),
},
expLargestSoftOptOutValidatorPower: 500,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
keeperParams := testkeeper.NewInMemKeeperParams(t)
// Explicitly register cdc with public key interface
keeperParams.RegisterSdkCryptoCodecInterfaces()
consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams)
consumerKeeper.SetParams(ctx, types.DefaultParams())
defer ctrl.Finish()

// set validators in store
SetCCValidators(t, consumerKeeper, ctx, tc.validators)

// update largest soft opt out validator power
consumerKeeper.UpdateLargestSoftOptOutValidatorPower(ctx)

// expect largest soft opt out validator power to be updated
require.Equal(t, tc.expLargestSoftOptOutValidatorPower, consumerKeeper.GetSoftOptOutThresholdPower(ctx))
})
}
}
3 changes: 3 additions & 0 deletions x/ccv/consumer/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ func (AppModule) ConsensusVersion() uint64 { return 1 }
// Set the VSC ID for the subsequent block to the same value as the current block
// Panic if the provider's channel was established and then closed
func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
// Set largest soft opt out validator power
am.keeper.UpdateLargestSoftOptOutValidatorPower(ctx)

channelID, found := am.keeper.GetProviderChannel(ctx)
if found && am.keeper.IsChannelClosed(ctx, channelID) {
// The CCV channel was established, but it was then closed;
Expand Down
Loading