-
Notifications
You must be signed in to change notification settings - Fork 138
/
Copy pathpartial_set_security.go
139 lines (119 loc) · 5.18 KB
/
partial_set_security.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package keeper
import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/cosmos/interchain-security/v4/x/ccv/provider/types"
"sort"
)
// HandleOptIn prepares validator `providerAddr` to opt in to `chainID` with an optional `consumerKey` consumer public key.
// Note that the validator only opts in at the end of an epoch.
func (k Keeper) HandleOptIn(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress, consumerKey *string) error {
if !k.IsConsumerProposedOrRegistered(ctx, chainID) {
return errorsmod.Wrapf(
types.ErrUnknownConsumerChainId,
"opting in to an unknown consumer chain, with id: %s", chainID)
}
k.SetOptedIn(ctx, chainID, providerAddr)
if consumerKey != nil {
consumerTMPublicKey, err := k.ParseConsumerKey(*consumerKey)
if err != nil {
return err
}
validator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.Address)
if !found {
return stakingtypes.ErrNoValidatorFound
}
err = k.AssignConsumerKey(ctx, chainID, validator, consumerTMPublicKey)
if err != nil {
return err
}
}
return nil
}
// HandleOptOut prepares validator `providerAddr` to opt out from running `chainID`.
// Note that the validator only opts out at the end of an epoch.
func (k Keeper) HandleOptOut(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress) error {
if _, found := k.GetConsumerClientId(ctx, chainID); !found {
// A validator can only opt out from a running chain. We check this by checking the consumer client id, because
// `SetConsumerClientId` is set when the chain starts in `CreateConsumerClientInCachedCtx` of `BeginBlockInit`.
return errorsmod.Wrapf(
types.ErrUnknownConsumerChainId,
"opting out of an unknown or not running consumer chain, with id: %s", chainID)
}
if topN, found := k.GetTopN(ctx, chainID); found {
// a validator cannot opt out from a Top N chain if the validator is in the Top N validators
validator, validatorFound := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr())
if !validatorFound {
return errorsmod.Wrapf(
stakingtypes.ErrNoValidatorFound,
"validator with consensus address %s could not be found", providerAddr.ToSdkConsAddr())
}
power := k.stakingKeeper.GetLastValidatorPower(ctx, validator.GetOperator())
minPowerToOptIn := k.ComputeMinPowerToOptIn(ctx, k.stakingKeeper.GetLastValidators(ctx), topN)
if power >= minPowerToOptIn {
return errorsmod.Wrapf(
types.ErrCannotOptOutFromTopN,
"validator with power (%d) cannot opt out from Top N chain because all validators"+
"with at least %d power have to validate", power, minPowerToOptIn)
}
}
k.DeleteOptedIn(ctx, chainID, providerAddr)
return nil
}
// OptInTopNValidators opts in to `chainID` all the `bondedValidators` that have at least `minPowerToOptIn` power
func (k Keeper) OptInTopNValidators(ctx sdk.Context, chainID string, bondedValidators []stakingtypes.Validator, minPowerToOptIn int64) {
for _, val := range bondedValidators {
power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator())
if power >= minPowerToOptIn {
consAddr, err := val.GetConsAddr()
if err != nil {
k.Logger(ctx).Error("could not retrieve validators consensus address",
"validator", val,
"error", err)
continue
}
// if validator already exists it gets overwritten
k.SetOptedIn(ctx, chainID, types.NewProviderConsAddress(consAddr))
} // else validators that do not belong to the top N validators but were opted in, remain opted in
}
}
// ComputeMinPowerToOptIn returns the minimum power needed for a validator (from the bonded validators)
// to belong to the `topN` validators
func (k Keeper) ComputeMinPowerToOptIn(ctx sdk.Context, bondedValidators []stakingtypes.Validator, topN uint32) int64 {
totalPower := sdk.ZeroDec()
var powers []int64
for _, val := range bondedValidators {
power := k.stakingKeeper.GetLastValidatorPower(ctx, val.GetOperator())
powers = append(powers, power)
totalPower = totalPower.Add(sdk.NewDecFromInt(sdk.NewInt(power)))
}
// sort by powers descending
sort.Slice(powers, func(i, j int) bool {
return powers[i] > powers[j]
})
topNThreshold := sdk.NewDecFromInt(sdk.NewInt(int64(topN))).QuoInt64(int64(100))
powerSum := sdk.ZeroDec()
for _, power := range powers {
powerSum = powerSum.Add(sdk.NewDecFromInt(sdk.NewInt(power)))
if powerSum.Quo(totalPower).GTE(topNThreshold) {
return power
}
}
// We should never reach this point because the topN can be up to 1.0 (100%) and in the above `for` loop we
// perform an equal comparison as well (`GTE`). In any case, we do not have to panic here because we can return 0
// as the smallest possible power.
k.Logger(ctx).Error("should never reach this point",
"topN", topN,
"totalPower", totalPower,
"powerSum", powerSum)
return 0
}
// ShouldConsiderOnlyOptIn returns true if `validator` is opted in, in `chainID.
func (k Keeper) ShouldConsiderOnlyOptIn(ctx sdk.Context, chainID string, validator stakingtypes.Validator) bool {
consAddr, err := validator.GetConsAddr()
if err != nil {
return false
}
return k.IsOptedIn(ctx, chainID, types.NewProviderConsAddress(consAddr))
}