-
Notifications
You must be signed in to change notification settings - Fork 110
/
validators.go
347 lines (293 loc) · 12.7 KB
/
validators.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
package keeper
import (
"context"
"errors"
"time"
"cosmossdk.io/math"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cosmos/interchain-security/v5/x/ccv/consumer/types"
)
//
// TODO: make unit tests for all of: MVP consumer, democ consumer, and pre-ccv consumer
// for previously unimplemented methods, if they're implemented to solve the above issue.
//
// ApplyCCValidatorChanges applies the given changes to the cross-chain validators states
// and returns updates to forward to tendermint.
func (k Keeper) ApplyCCValidatorChanges(ctx sdk.Context, changes []abci.ValidatorUpdate) []abci.ValidatorUpdate {
ret := []abci.ValidatorUpdate{}
for _, change := range changes {
// convert TM pubkey to SDK pubkey
pubkey, err := cryptocodec.FromCmtProtoPublicKey(change.GetPubKey())
if err != nil {
// An error here would indicate that the validator updates
// received from the provider are invalid.
panic(err)
}
addr := pubkey.Address()
val, found := k.GetCCValidator(ctx, addr)
if found {
// update or delete an existing validator
if change.Power < 1 {
k.DeleteCCValidator(ctx, addr)
} else {
val.Power = change.Power
k.SetCCValidator(ctx, val)
}
} else if 0 < change.Power {
// create a new validator
consAddr := sdk.ConsAddress(addr)
ccVal, err := types.NewCCValidator(addr, change.Power, pubkey)
if err != nil {
// An error here would indicate that the validator updates
// received from the provider are invalid.
panic(err)
}
k.SetCCValidator(ctx, ccVal)
err = k.AfterValidatorBonded(ctx, consAddr, nil)
if err != nil {
// AfterValidatorBonded is called by the Slashing module and should not return an error.
panic(err)
}
// Sanity check: making sure the outstanding downtime flag is not
// set for this new validator. This is especially useful to deal with
// https://github.com/cosmos/interchain-security/issues/1569.
k.DeleteOutstandingDowntime(ctx, consAddr)
} else {
// edge case: we received an update for 0 power
// but the validator is already deleted. Do not forward
// to tendermint.
continue
}
ret = append(ret, change)
}
return ret
}
// IterateValidators - unimplemented on CCV keeper but perform a no-op in order to pass the slashing module InitGenesis.
// It is allowed since the condition verifying validator public keys in HandleValidatorSignature (x/slashing/keeper/infractions.go) is removed
// therefore it isn't required to store any validator public keys to the slashing states during genesis.
func (k Keeper) IterateValidators(context.Context, func(index int64, validator stakingtypes.ValidatorI) (stop bool)) error {
return nil
}
// Validator - unimplemented on CCV keeper
func (k Keeper) Validator(ctx context.Context, addr sdk.ValAddress) (stakingtypes.ValidatorI, error) {
panic("unimplemented on CCV keeper")
}
// IsJailed returns the outstanding slashing flag for the given validator address
func (k Keeper) IsValidatorJailed(goCtx context.Context, addr sdk.ConsAddress) (bool, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
// if the changeover is not complete for prev standalone chain,
// return the standalone staking keeper's jailed status
if k.IsPrevStandaloneChain(ctx) && !k.ChangeoverIsComplete(ctx) {
return k.standaloneStakingKeeper.IsValidatorJailed(ctx, addr)
}
// Otherwise, return the ccv consumer keeper's notion of a validator being jailed
return k.OutstandingDowntime(ctx, addr), nil
}
// ValidatorByConsAddr returns an empty validator
func (k Keeper) ValidatorByConsAddr(context.Context, sdk.ConsAddress) (stakingtypes.ValidatorI, error) {
/*
NOTE:
The evidence module will call this function when it handles equivocation evidence.
The returned value must not be nil and must not have an UNBONDED validator status,
or evidence will reject it.
Also, the slashing module will cal lthis function when it observes downtime. In that case
the only requirement on the returned value is that it isn't null.
*/
return stakingtypes.Validator{}, nil
}
// Calls SlashWithInfractionReason with Infraction_INFRACTION_UNSPECIFIED.
// ConsumerKeeper must implement StakingKeeper interface.
// This function should not be called anywhere
func (k Keeper) Slash(ctx context.Context, addr sdk.ConsAddress, infractionHeight, power int64, slashFactor math.LegacyDec) (math.Int, error) {
return k.SlashWithInfractionReason(ctx, addr, infractionHeight, power, slashFactor, stakingtypes.Infraction_INFRACTION_UNSPECIFIED)
}
// Slash queues a slashing request for the the provider chain
// All queued slashing requests will be cleared in EndBlock
// Called by Slashing keeper in SlashWithInfractionReason
func (k Keeper) SlashWithInfractionReason(goCtx context.Context, addr sdk.ConsAddress, infractionHeight, power int64, slashFactor math.LegacyDec, infraction stakingtypes.Infraction) (math.Int, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
if infraction == stakingtypes.Infraction_INFRACTION_UNSPECIFIED {
return math.ZeroInt(), nil
}
// If this is a previously standalone chain and infraction happened before the changeover was completed,
// slash only on the standalone staking keeper.
if k.IsPrevStandaloneChain(ctx) && infractionHeight < k.FirstConsumerHeight(ctx) {
// Slash for a standalone chain does not require an infraction reason so we pass in Infraction_INFRACTION_UNSPECIFIED
return k.standaloneStakingKeeper.SlashWithInfractionReason(ctx, addr, infractionHeight, power, slashFactor, stakingtypes.Infraction_INFRACTION_UNSPECIFIED)
}
// Otherwise infraction happened after the changeover was completed.
// if this is a downtime infraction and the validator is allowed to
// soft opt out, do not queue a slash packet
if infraction == stakingtypes.Infraction_INFRACTION_DOWNTIME {
if power < k.GetSmallestNonOptOutPower(ctx) {
// soft opt out
k.Logger(ctx).Debug("soft opt out",
"validator", addr,
"power", power,
)
return math.ZeroInt(), nil
}
}
// get VSC ID for infraction height
vscID := k.GetHeightValsetUpdateID(ctx, uint64(infractionHeight))
k.Logger(ctx).Debug("vscID obtained from mapped infraction height",
"infraction height", infractionHeight,
"vscID", vscID,
)
// this is the most important step in the function
// everything else is just here to implement StakingKeeper interface
// IBC packets are created from slash data and sent to the provider during EndBlock
k.QueueSlashPacket(
ctx,
abci.Validator{
Address: addr.Bytes(),
Power: power,
},
vscID,
infraction,
)
// Only return to comply with the interface restriction
return math.ZeroInt(), nil
}
// Jail - unimplemented on CCV keeper
//
// This method should be a no-op even during a standalone to consumer changeover.
// Once the upgrade has happened as a part of the changeover,
// the provider validator set will soon be in effect, and jailing is n/a.
func (k Keeper) Jail(context.Context, sdk.ConsAddress) error { return nil }
// Unjail - unimplemented on CCV keeper
//
// This method should be a no-op even during a standalone to consumer changeover.
// Once the upgrade has happened as a part of the changeover,
// the provider validator set will soon be in effect, and jailing is n/a.
func (k Keeper) Unjail(context.Context, sdk.ConsAddress) error { return nil }
// Delegation - unimplemented on CCV keeper
func (k Keeper) Delegation(ctx context.Context, addr sdk.AccAddress, valAddr sdk.ValAddress) (stakingtypes.DelegationI, error) {
panic("unimplemented on CCV keeper")
}
// MaxValidators - unimplemented on CCV keeper
func (k Keeper) MaxValidators(context.Context) (uint32, error) {
panic("unimplemented on CCV keeper")
}
// UnbondingTime returns consumer unbonding period, satisfying the staking keeper interface
func (k Keeper) UnbondingTime(goCtx context.Context) (time.Duration, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
return k.GetUnbondingPeriod(ctx), nil
}
// GetHistoricalInfo gets the historical info at a given height
func (k Keeper) GetHistoricalInfo(goCtx context.Context, height int64) (stakingtypes.HistoricalInfo, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
store := ctx.KVStore(k.storeKey)
key := types.HistoricalInfoKey(height)
value := store.Get(key)
if value == nil {
return stakingtypes.HistoricalInfo{}, stakingtypes.ErrNoHistoricalInfo
}
return stakingtypes.UnmarshalHistoricalInfo(k.cdc, value)
}
// SetHistoricalInfo sets the historical info at a given height
func (k Keeper) SetHistoricalInfo(goCtx context.Context, height int64, hi *stakingtypes.HistoricalInfo) {
ctx := sdk.UnwrapSDKContext(goCtx)
store := ctx.KVStore(k.storeKey)
key := types.HistoricalInfoKey(height)
value := k.cdc.MustMarshal(hi)
store.Set(key, value)
}
// DeleteHistoricalInfo deletes the historical info at a given height
func (k Keeper) DeleteHistoricalInfo(goCtx context.Context, height int64) error {
ctx := sdk.UnwrapSDKContext(goCtx)
store := ctx.KVStore(k.storeKey)
key := types.HistoricalInfoKey(height)
store.Delete(key)
return nil
}
// TrackHistoricalInfo saves the latest historical-info and deletes the oldest
// heights that are below pruning height
func (k Keeper) TrackHistoricalInfo(goCtx context.Context) error {
ctx := sdk.UnwrapSDKContext(goCtx)
numHistoricalEntries := k.GetHistoricalEntries(ctx)
// Prune store to ensure we only have parameter-defined historical entries.
// In most cases, this will involve removing a single historical entry.
// In the rare scenario when the historical entries gets reduced to a lower value k'
// from the original value k. k - k' entries must be deleted from the store.
// Since the entries to be deleted are always in a continuous range, we can iterate
// over the historical entries starting from the most recent version to be pruned
// and then return at the first empty entry.
for i := ctx.BlockHeight() - numHistoricalEntries; i >= 0; i-- {
_, err := k.GetHistoricalInfo(ctx, i)
if err != nil {
if errors.Is(err, stakingtypes.ErrNoHistoricalInfo) {
break
}
return err
}
if err = k.DeleteHistoricalInfo(ctx, i); err != nil {
return err
}
}
// if there is no need to persist historicalInfo, return
if numHistoricalEntries == 0 {
return nil
}
// Create HistoricalInfo struct
lastVals := []stakingtypes.Validator{}
for _, v := range k.GetAllCCValidator(ctx) {
pk, err := v.ConsPubKey()
if err != nil {
// This should never happen as the pubkey is assumed
// to be stored correctly in ApplyCCValidatorChanges.
panic(err)
}
val, err := stakingtypes.NewValidator(sdk.ValAddress(pk.Address()).String(), pk, stakingtypes.Description{})
if err != nil {
// This should never happen as the pubkey is assumed
// to be stored correctly in ApplyCCValidatorChanges.
panic(err)
}
// Set validator to bonded status
val.Status = stakingtypes.Bonded
// Compute tokens from voting power
val.Tokens = sdk.TokensFromConsensusPower(v.Power, sdk.DefaultPowerReduction)
lastVals = append(lastVals, val)
}
// Create historical info entry which sorts the validator set by voting power
historicalEntry := stakingtypes.NewHistoricalInfo(ctx.BlockHeader(), stakingtypes.Validators{Validators: lastVals, ValidatorCodec: k.validatorAddressCodec}, sdk.DefaultPowerReduction)
// Set latest HistoricalInfo at current height
k.SetHistoricalInfo(ctx, ctx.BlockHeight(), &historicalEntry)
return nil
}
// MustGetCurrentValidatorsAsABCIUpdates gets all cross-chain validators converted
// to the ABCI validator update type. It panics in case of failure.
func (k Keeper) MustGetCurrentValidatorsAsABCIUpdates(ctx sdk.Context) []abci.ValidatorUpdate {
vals := k.GetAllCCValidator(ctx)
valUpdates := make([]abci.ValidatorUpdate, 0, len(vals))
for _, v := range vals {
pk, err := v.ConsPubKey()
if err != nil {
// This should never happen as the pubkey is assumed
// to be stored correctly in ApplyCCValidatorChanges.
panic(err)
}
tmPK, err := cryptocodec.ToCmtProtoPublicKey(pk)
if err != nil {
// This should never happen as the pubkey is assumed
// to be stored correctly in ApplyCCValidatorChanges.
panic(err)
}
valUpdates = append(valUpdates, abci.ValidatorUpdate{PubKey: tmPK, Power: v.Power})
}
return valUpdates
}
// implement interface method needed for x/genutil in sdk v47
// returns empty updates and err
func (k Keeper) ApplyAndReturnValidatorSetUpdates(context.Context) (updates []abci.ValidatorUpdate, err error) {
return
}
// GetAllValidators is needed to implement StakingKeeper as expected by the Slashing module since cosmos-sdk/v0.47.x.
// Use GetAllCCValidator in places where access to all cross-chain validators is needed.
func (k Keeper) GetAllValidators(ctx context.Context) ([]stakingtypes.Validator, error) {
return []stakingtypes.Validator{}, nil
}