-
Notifications
You must be signed in to change notification settings - Fork 138
/
Copy pathproposal.go
537 lines (447 loc) · 19.6 KB
/
proposal.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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
package keeper
import (
"fmt"
"strconv"
"time"
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types"
commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types"
ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types"
"github.com/cosmos/interchain-security/x/ccv/provider/types"
ccv "github.com/cosmos/interchain-security/x/ccv/types"
abci "github.com/tendermint/tendermint/abci/types"
consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types"
)
// HandleConsumerAdditionProposal will receive the consumer chain's client state from the proposal.
// If the spawn time has already passed, then set the consumer chain. Otherwise store the client
// as a pending client, and set once spawn time has passed.
//
// Note: This method implements SpawnConsumerChainProposalHandler in spec.
// See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-spccprop1
// Spec tag: [CCV-PCF-SPCCPROP.1]
func (k Keeper) HandleConsumerAdditionProposal(ctx sdk.Context, p *types.ConsumerAdditionProposal) error {
if !ctx.BlockTime().Before(p.SpawnTime) {
// lockUbdOnTimeout is set to be false, regardless of what the proposal says, until we can specify and test issues around this use case more thoroughly
return k.CreateConsumerClient(ctx, p.ChainId, p.InitialHeight, false)
}
err := k.SetPendingConsumerAdditionProp(ctx, p)
if err != nil {
return err
}
return nil
}
// CreateConsumerClient will create the CCV client for the given consumer chain. The CCV channel must be built
// on top of the CCV client to ensure connection with the right consumer chain.
//
// See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-crclient1
// Spec tag: [CCV-PCF-CRCLIENT.1]
func (k Keeper) CreateConsumerClient(ctx sdk.Context, chainID string,
initialHeight clienttypes.Height, lockUbdOnTimeout bool) error {
// check that a client for this chain does not exist
if _, found := k.GetConsumerClientId(ctx, chainID); found {
// drop the proposal
return nil
}
// Consumers always start out with the default unbonding period
consumerUnbondingPeriod := consumertypes.DefaultConsumerUnbondingPeriod
// Create client state by getting template client from parameters and filling in zeroed fields from proposal.
clientState := k.GetTemplateClient(ctx)
clientState.ChainId = chainID
clientState.LatestHeight = initialHeight
clientState.TrustingPeriod = consumerUnbondingPeriod / time.Duration(k.GetTrustingPeriodFraction(ctx))
clientState.UnbondingPeriod = consumerUnbondingPeriod
// TODO: Allow for current validators to set different keys
consensusState := ibctmtypes.NewConsensusState(
ctx.BlockTime(),
commitmenttypes.NewMerkleRoot([]byte(ibctmtypes.SentinelRoot)),
ctx.BlockHeader().NextValidatorsHash,
)
clientID, err := k.clientKeeper.CreateClient(ctx, clientState, consensusState)
if err != nil {
return err
}
k.SetConsumerClientId(ctx, chainID, clientID)
consumerGen, err := k.MakeConsumerGenesis(ctx)
if err != nil {
return err
}
err = k.SetConsumerGenesis(ctx, chainID, consumerGen)
if err != nil {
return err
}
// add the init timeout timestamp for this consumer chain
ts := ctx.BlockTime().Add(k.GetParams(ctx).InitTimeoutPeriod)
k.SetInitTimeoutTimestamp(ctx, chainID, uint64(ts.UnixNano()))
// store LockUnbondingOnTimeout flag
if lockUbdOnTimeout {
k.SetLockUnbondingOnTimeout(ctx, chainID)
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
ccv.EventTypeConsumerClientCreated,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(ccv.AttributeChainID, chainID),
sdk.NewAttribute(clienttypes.AttributeKeyClientID, clientID),
sdk.NewAttribute(ccv.AttributeInitialHeight, initialHeight.String()),
sdk.NewAttribute(ccv.AttributeInitializationTimeout, strconv.Itoa(int(ts.UnixNano()))),
sdk.NewAttribute(ccv.AttributeTrustingPeriod, clientState.TrustingPeriod.String()),
sdk.NewAttribute(ccv.AttributeUnbondingPeriod, clientState.UnbondingPeriod.String()),
),
)
return nil
}
// HandleConsumerRemovalProposal stops a consumer chain and released the outstanding unbonding operations.
// If the stop time hasn't already passed, it stores the proposal as a pending proposal.
//
// This method implements StopConsumerChainProposalHandler from spec.
// See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-stccprop1
// Spec tag: [CCV-PCF-STCCPROP.1]
func (k Keeper) HandleConsumerRemovalProposal(ctx sdk.Context, p *types.ConsumerRemovalProposal) error {
if !ctx.BlockTime().Before(p.StopTime) {
return k.StopConsumerChain(ctx, p.ChainId, false, true)
}
k.SetPendingConsumerRemovalProp(ctx, p.ChainId, p.StopTime)
return nil
}
// StopConsumerChain cleans up the states for the given consumer chain ID and, if the given lockUbd is false,
// it completes the outstanding unbonding operations lock by the consumer chain.
//
// This method implements StopConsumerChain from spec.
// See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-stcc1
// Spec tag: [CCV-PCF-STCC.1]
func (k Keeper) StopConsumerChain(ctx sdk.Context, chainID string, lockUbd, closeChan bool) (err error) {
// check that a client for chainID exists
if _, found := k.GetConsumerClientId(ctx, chainID); !found {
// drop the proposal
return nil
}
// clean up states
k.DeleteConsumerClientId(ctx, chainID)
k.DeleteConsumerGenesis(ctx, chainID)
k.DeleteLockUnbondingOnTimeout(ctx, chainID)
k.DeleteInitTimeoutTimestamp(ctx, chainID)
// close channel and delete the mappings between chain ID and channel ID
if channelID, found := k.GetChainToChannel(ctx, chainID); found {
if closeChan {
k.CloseChannel(ctx, channelID)
}
k.DeleteChainToChannel(ctx, chainID)
k.DeleteChannelToChain(ctx, channelID)
// delete VSC send timestamps
var ids []uint64
k.IterateVscSendTimestamps(ctx, chainID, func(vscID uint64, ts time.Time) (stop bool) {
ids = append(ids, vscID)
return false // do not stop the iteration
})
for _, vscID := range ids {
k.DeleteVscSendTimestamp(ctx, chainID, vscID)
}
}
k.DeleteInitChainHeight(ctx, chainID)
k.ConsumeSlashAcks(ctx, chainID)
k.DeletePendingPackets(ctx, chainID)
// release unbonding operations if they aren't locked
var vscIDs []uint64
if !lockUbd {
// iterate over the consumer chain's unbonding operation VSC ids
k.IterateOverUnbondingOpIndex(ctx, chainID, func(vscID uint64, ids []uint64) (stop bool) {
// iterate over the unbonding operations for the current VSC ID
var maturedIds []uint64
for _, id := range ids {
unbondingOp, found := k.GetUnbondingOp(ctx, id)
if !found {
err = fmt.Errorf("could not find UnbondingOp according to index - id: %d", id)
return true // stop the iteration
}
// remove consumer chain ID from unbonding op record
unbondingOp.UnbondingConsumerChains, _ = removeStringFromSlice(unbondingOp.UnbondingConsumerChains, chainID)
// If unbonding op is completely unbonded from all relevant consumer chains
if len(unbondingOp.UnbondingConsumerChains) == 0 {
// Store id of matured unbonding op for later completion of unbonding in staking module
maturedIds = append(maturedIds, unbondingOp.Id)
// Delete unbonding op
k.DeleteUnbondingOp(ctx, unbondingOp.Id)
} else {
if err := k.SetUnbondingOp(ctx, unbondingOp); err != nil {
panic(fmt.Errorf("unbonding op could not be persisted: %w", err))
}
}
}
if err := k.AppendMaturedUnbondingOps(ctx, maturedIds); err != nil {
panic(fmt.Errorf("mature unbonding ops could not be appended: %w", err))
}
vscIDs = append(vscIDs, vscID)
return false // do not stop the iteration
})
}
if err != nil {
return err
}
// clean up indexes
for _, id := range vscIDs {
k.DeleteUnbondingOpIndex(ctx, chainID, id)
}
return nil
}
// MakeConsumerGenesis constructs the consumer CCV module part of the genesis state.
func (k Keeper) MakeConsumerGenesis(ctx sdk.Context) (gen consumertypes.GenesisState, err error) {
providerUnbondingPeriod := k.stakingKeeper.UnbondingTime(ctx)
height := clienttypes.GetSelfHeight(ctx)
clientState := k.GetTemplateClient(ctx)
// this is the counter party chain ID for the consumer
clientState.ChainId = ctx.ChainID()
// this is the latest height the client was updated at, i.e.,
// the height of the latest consensus state (see below)
clientState.LatestHeight = height
clientState.TrustingPeriod = providerUnbondingPeriod / time.Duration(k.GetTrustingPeriodFraction(ctx))
clientState.UnbondingPeriod = providerUnbondingPeriod
consState, err := k.clientKeeper.GetSelfConsensusState(ctx, height)
if err != nil {
return gen, sdkerrors.Wrapf(clienttypes.ErrConsensusStateNotFound, "error %s getting self consensus state for: %s", err, height)
}
gen = *consumertypes.DefaultGenesisState()
gen.Params.Enabled = true
gen.NewChain = true
// the client state and consensus state needed by the consumer
// to create a client to the provider
gen.ProviderClientState = clientState
gen.ProviderConsensusState = consState.(*ibctmtypes.ConsensusState)
var lastPowers []stakingtypes.LastValidatorPower
k.stakingKeeper.IterateLastValidatorPowers(ctx, func(addr sdk.ValAddress, power int64) (stop bool) {
lastPowers = append(lastPowers, stakingtypes.LastValidatorPower{Address: addr.String(), Power: power})
return false
})
updates := []abci.ValidatorUpdate{}
for _, p := range lastPowers {
addr, err := sdk.ValAddressFromBech32(p.Address)
if err != nil {
panic(err)
}
val, found := k.stakingKeeper.GetValidator(ctx, addr)
if !found {
panic("Validator from LastValidatorPowers not found in staking keeper")
}
tmProtoPk, err := val.TmConsPublicKey()
if err != nil {
panic(err)
}
updates = append(updates, abci.ValidatorUpdate{
PubKey: tmProtoPk,
Power: p.Power,
})
}
gen.InitialValSet = updates
return gen, nil
}
// SetPendingConsumerAdditionProp stores a pending proposal to create a consumer chain client
func (k Keeper) SetPendingConsumerAdditionProp(ctx sdk.Context, clientInfo *types.ConsumerAdditionProposal) error {
store := ctx.KVStore(k.storeKey)
bz, err := k.cdc.Marshal(clientInfo)
if err != nil {
return err
}
store.Set(types.PendingCAPKey(clientInfo.SpawnTime, clientInfo.ChainId), bz)
return nil
}
// GetPendingConsumerAdditionProp retrieves a pending proposal to create a consumer chain client (by spawn time and chain id)
func (k Keeper) GetPendingConsumerAdditionProp(ctx sdk.Context, spawnTime time.Time,
chainID string) (prop types.ConsumerAdditionProposal, found bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.PendingCAPKey(spawnTime, chainID))
if len(bz) == 0 {
return prop, false
}
k.cdc.MustUnmarshal(bz, &prop)
return prop, true
}
// PendingConsumerAdditionPropIterator returns an iterator for iterating through pending consumer addition proposals
func (k Keeper) PendingConsumerAdditionPropIterator(ctx sdk.Context) sdk.Iterator {
store := ctx.KVStore(k.storeKey)
return sdk.KVStorePrefixIterator(store, []byte{types.PendingCAPBytePrefix})
}
// BeginBlockInit iterates over the pending consumer addition proposals in order, and creates
// clients for props in which the spawn time has passed. Executed proposals are deleted.
//
// See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-bblock-init1
// Spec tag:[CCV-PCF-BBLOCK-INIT.1]
func (k Keeper) BeginBlockInit(ctx sdk.Context) {
propsToExecute := k.ConsumerAdditionPropsToExecute(ctx)
for _, prop := range propsToExecute {
// lockUbdOnTimeout is set to be false, regardless of what the proposal says, until we can specify and test issues around this use case more thoroughly
err := k.CreateConsumerClient(ctx, prop.ChainId, prop.InitialHeight, false)
if err != nil {
panic(fmt.Errorf("consumer client could not be created: %w", err))
}
}
// delete the executed proposals
k.DeletePendingConsumerAdditionProps(ctx, propsToExecute...)
}
// ConsumerAdditionPropsToExecute iterates over the pending consumer addition proposals
// and returns an ordered list of proposals to be executed, ie. consumer clients to be created.
// A prop is included in the returned list if its proposed spawn time has passed.
//
// Note: this method is split out from BeginBlockInit to be easily unit tested.
func (k Keeper) ConsumerAdditionPropsToExecute(ctx sdk.Context) []types.ConsumerAdditionProposal {
// store the (to be) executed proposals in order
propsToExecute := []types.ConsumerAdditionProposal{}
iterator := k.PendingConsumerAdditionPropIterator(ctx)
defer iterator.Close()
k.IteratePendingConsumerAdditionProps(ctx, func(spawnTime time.Time, prop types.ConsumerAdditionProposal) (stop bool) {
if !ctx.BlockTime().Before(spawnTime) {
propsToExecute = append(propsToExecute, prop)
return false // do not stop the iteration
}
return true // stop iteration, proposals are ordered by spawn time, so no additional pending props are ready to act upon
})
return propsToExecute
}
func (k Keeper) IteratePendingConsumerAdditionProps(
ctx sdk.Context,
cb func(spawnTime time.Time, prop types.ConsumerAdditionProposal) (stop bool),
) {
iterator := k.PendingConsumerAdditionPropIterator(ctx)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
key := iterator.Key()
spawnTime, _, err := types.ParsePendingCAPKey(key)
if err != nil {
panic(fmt.Errorf("failed to parse pending client key: %w", err))
}
var prop types.ConsumerAdditionProposal
k.cdc.MustUnmarshal(iterator.Value(), &prop)
stop := cb(spawnTime, prop)
if stop {
break
}
}
}
// GetAllConsumerAdditionProps returns all consumer addition proposals separated into matured and pending.
func (k Keeper) GetAllConsumerAdditionProps(ctx sdk.Context) types.ConsumerAdditionProposals {
props := types.ConsumerAdditionProposals{}
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, []byte{types.PendingCAPBytePrefix})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var prop types.ConsumerAdditionProposal
k.cdc.MustUnmarshal(iterator.Value(), &prop)
props.Pending = append(props.Pending, &prop)
}
return props
}
// DeletePendingConsumerAdditionProps deletes the given consumer addition proposals
func (k Keeper) DeletePendingConsumerAdditionProps(ctx sdk.Context, proposals ...types.ConsumerAdditionProposal) {
store := ctx.KVStore(k.storeKey)
for _, p := range proposals {
store.Delete(types.PendingCAPKey(p.SpawnTime, p.ChainId))
}
}
// SetPendingConsumerRemovalProp stores a pending proposal to remove and stop a consumer chain
func (k Keeper) SetPendingConsumerRemovalProp(ctx sdk.Context, chainID string, timestamp time.Time) {
store := ctx.KVStore(k.storeKey)
store.Set(types.PendingCRPKey(timestamp, chainID), []byte{})
}
// GetPendingConsumerRemovalProp returns a boolean if a pending consumer removal proposal
// exists for the given consumer chain ID and timestamp
func (k Keeper) GetPendingConsumerRemovalProp(ctx sdk.Context, chainID string, timestamp time.Time) bool {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.PendingCRPKey(timestamp, chainID))
return bz != nil
}
// DeletePendingConsumerRemovalProps deletes the given pending consumer removal proposals.
// This method should be called once the proposal has been acted upon.
func (k Keeper) DeletePendingConsumerRemovalProps(ctx sdk.Context, proposals ...types.ConsumerRemovalProposal) {
store := ctx.KVStore(k.storeKey)
for _, p := range proposals {
store.Delete(types.PendingCRPKey(p.StopTime, p.ChainId))
}
}
// PendingConsumerRemovalPropIterator returns an iterator for iterating through pending consumer removal proposals
func (k Keeper) PendingConsumerRemovalPropIterator(ctx sdk.Context) sdk.Iterator {
store := ctx.KVStore(k.storeKey)
return sdk.KVStorePrefixIterator(store, []byte{types.PendingCRPBytePrefix})
}
// BeginBlockCCR iterates over the pending consumer removal proposals
// in order and stop/removes the chain if the stop time has passed,
// otherwise it will break out of loop and return. Executed proposals are deleted.
//
// See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-bblock-ccr1
// Spec tag: [CCV-PCF-BBLOCK-CCR.1]
func (k Keeper) BeginBlockCCR(ctx sdk.Context) {
propsToExecute := k.ConsumerRemovalPropsToExecute(ctx)
for _, prop := range propsToExecute {
err := k.StopConsumerChain(ctx, prop.ChainId, false, true)
if err != nil {
panic(fmt.Errorf("consumer chain failed to stop: %w", err))
}
}
// delete the executed proposals
k.DeletePendingConsumerRemovalProps(ctx, propsToExecute...)
}
// ConsumerRemovalPropsToExecute iterates over the pending consumer removal proposals
// and returns an ordered list of consumer removal proposals to be executed,
// ie. consumer chains to be stopped and removed from the provider chain.
// A prop is included in the returned list if its proposed stop time has passed.
//
// Note: this method is split out from BeginBlockCCR to be easily unit tested.
func (k Keeper) ConsumerRemovalPropsToExecute(ctx sdk.Context) []types.ConsumerRemovalProposal {
// store the (to be) executed consumer removal proposals in order
propsToExecute := []types.ConsumerRemovalProposal{}
k.IteratePendingConsumerRemovalProps(ctx, func(stopTime time.Time, prop types.ConsumerRemovalProposal) (stop bool) {
if !ctx.BlockTime().Before(stopTime) {
propsToExecute = append(propsToExecute, prop)
return false // do not stop the iteration
}
// No more proposals to check, since they're stored/ordered by timestamp.
return true // stop
})
return propsToExecute
}
func (k Keeper) IteratePendingConsumerRemovalProps(
ctx sdk.Context,
cb func(stopTime time.Time, prop types.ConsumerRemovalProposal) (stop bool),
) {
iterator := k.PendingConsumerRemovalPropIterator(ctx)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
key := iterator.Key()
stopTime, chainID, err := types.ParsePendingCRPKey(key)
if err != nil {
panic(fmt.Errorf("failed to parse pending consumer removal proposal key: %w", err))
}
stop := cb(stopTime, types.ConsumerRemovalProposal{ChainId: chainID, StopTime: stopTime})
if stop {
break
}
}
}
// GetAllConsumerRemovalProps returns all consumer removal proposals separated into matured and pending.
func (k Keeper) GetAllConsumerRemovalProps(ctx sdk.Context) types.ConsumerRemovalProposals {
props := types.ConsumerRemovalProposals{}
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, []byte{types.PendingCRPBytePrefix})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
key := iterator.Key()
stopTime, chainID, err := types.ParsePendingCRPKey(key)
if err != nil {
panic(fmt.Errorf("failed to parse pending consumer removal proposal key: %w", err))
}
props.Pending = append(props.Pending,
&types.ConsumerRemovalProposal{ChainId: chainID, StopTime: stopTime})
}
return props
}
// CloseChannel closes the channel for the given channel ID on the condition
// that the channel exists and isn't already in the CLOSED state
func (k Keeper) CloseChannel(ctx sdk.Context, channelID string) {
channel, found := k.channelKeeper.GetChannel(ctx, ccv.ProviderPortID, channelID)
if found && channel.State != channeltypes.CLOSED {
err := k.chanCloseInit(ctx, channelID)
if err != nil {
panic(fmt.Errorf("channel (id: %s) could not be closed: %w", channelID, err))
}
}
}