Skip to content

Commit e3c455e

Browse files
committed
Fix: Patch Consumer initiated slashing
- Use reverse iterator to get last VSCId: [comment-ref](https://github.com) - Implement Producer chain slashing packet acknowledgement: [comment-ref](cosmos#28 (comment)) - Make unit-tests pass using custom [IBC-GO branch](https://github.com/sainoe/ibc-go/tree/sainoe/valset-update-fixes) with [cosmos#28](cosmos/ibc-go#918 (comment)) fixes
1 parent c4ce132 commit e3c455e

File tree

13 files changed

+168
-59
lines changed

13 files changed

+168
-59
lines changed

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ require (
3737
replace (
3838
github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76
3939
github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.44.1-0.20220112185710-fa19ad5f85c5
40+
// github.com/cosmos/ibc-go => github.com/sainoe/ibc-go v1.2.1-0.20220217160816-34b9d8254659
41+
github.com/cosmos/ibc-go => /Users/simon/Dev/go-workspace/src/github.com/sainoe/ibc-go
4042
github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
4143
google.golang.org/grpc => google.golang.org/grpc v1.33.2
4244
)

go.sum

-2
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,6 @@ github.com/cosmos/iavl v0.15.3/go.mod h1:OLjQiAQ4fGD2KDZooyJG9yz+p2ao2IAYSbke8mV
259259
github.com/cosmos/iavl v0.16.0/go.mod h1:2A8O/Jz9YwtjqXMO0CjnnbTYEEaovE8jWcwrakH3PoE=
260260
github.com/cosmos/iavl v0.17.1 h1:b/Cl8h1PRMvsu24+TYNlKchIu7W6tmxIBGe6E9u2Ybw=
261261
github.com/cosmos/iavl v0.17.1/go.mod h1:7aisPZK8yCpQdy3PMvKeO+bhq1NwDjUwjzxwwROUxFk=
262-
github.com/cosmos/ibc-go v1.2.1-0.20211111105346-12a60b13a024 h1:Xe7dltlisXJljiFiTDXgm5HDWDE50zU8P08hFLVLmNk=
263-
github.com/cosmos/ibc-go v1.2.1-0.20211111105346-12a60b13a024/go.mod h1:AINUrX29q811dcarbf5wzKQZKOg46FCLX4apqblzKac=
264262
github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 h1:DdzS1m6o/pCqeZ8VOAit/gyATedRgjvkVI+UCrLpyuU=
265263
github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76/go.mod h1:0mkLWIoZuQ7uBoospo5Q9zIpqq6rYCPJDSUdeCJvPM8=
266264
github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4=

x/ccv/child/keeper/hooks.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,16 @@ func (k Keeper) AfterValidatorDowntime(ctx sdk.Context, consAddr sdk.ConsAddress
2020
return
2121
}
2222

23+
// get last the valset update id
24+
valsetUpdate, err := k.GetLastUnbondingPacketData(ctx)
25+
if err != nil {
26+
return
27+
}
28+
2329
// increase jail time
2430
signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.slashingKeeper.DowntimeJailDuration(ctx))
2531

26-
// reset the missed block counters
32+
// reset missed block counters
2733
signInfo.MissedBlocksCounter = 0
2834
signInfo.IndexOffset = 0
2935
k.slashingKeeper.ClearValidatorMissedBlockBitArray(ctx, consAddr)
@@ -38,6 +44,7 @@ func (k Keeper) AfterValidatorDowntime(ctx sdk.Context, consAddr sdk.ConsAddress
3844
Address: consAddr.Bytes(),
3945
Power: power,
4046
},
47+
valsetUpdate.ValsetUpdateId,
4148
k.slashingKeeper.SlashFractionDowntime(ctx).TruncateInt64(),
4249
signInfo.JailedUntil.UnixNano(),
4350
)

x/ccv/child/keeper/keeper.go

+20-9
Original file line numberDiff line numberDiff line change
@@ -317,15 +317,26 @@ func (k Keeper) VerifyParentChain(ctx sdk.Context, channelID string) error {
317317
return nil
318318
}
319319

320-
// GetLastUnbondingPacket returns the last unbounding packet stored in lexical order
321-
func (k Keeper) GetLastUnbondingPacket(ctx sdk.Context) ccv.ValidatorSetChangePacketData {
322-
ubdPacket := &channeltypes.Packet{}
323-
k.IterateUnbondingPacket(ctx, func(seq uint64, packet channeltypes.Packet) bool {
324-
*ubdPacket = packet
325-
return false
326-
})
320+
// GetLastUnbondingPacket returns the last unbounding packet data stored in lexical order
321+
func (k Keeper) GetLastUnbondingPacketData(ctx sdk.Context) (ccv.ValidatorSetChangePacketData, error) {
322+
store := ctx.KVStore(k.storeKey)
323+
// use a reverse iterator to get the last entry
324+
iterator := sdk.KVStoreReversePrefixIterator(store, []byte(types.UnbondingPacketPrefix))
325+
if !iterator.Valid() {
326+
return ccv.ValidatorSetChangePacketData{}, sdkerrors.Wrapf(ccv.ErrInvalidChildState, "Invalid unbonding packet iterator")
327+
}
328+
329+
var packet channeltypes.Packet
330+
err := packet.Unmarshal(iterator.Value())
331+
if err != nil {
332+
return ccv.ValidatorSetChangePacketData{}, err
333+
}
334+
327335
var data ccv.ValidatorSetChangePacketData
336+
err = ccv.ModuleCdc.UnmarshalJSON(packet.GetData(), &data)
337+
if err != nil {
338+
return ccv.ValidatorSetChangePacketData{}, err
339+
}
328340

329-
ccv.ModuleCdc.UnmarshalJSON(ubdPacket.GetData(), &data)
330-
return data
341+
return data, nil
331342
}

x/ccv/child/keeper/keeper_test.go

+31-6
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,10 @@ func (suite *KeeperTestSuite) TestValidatorDowntime() {
382382
// add the validator pubkey and signing info to the store
383383
app.SlashingKeeper.AddPubkey(ctx, pubkey)
384384

385+
// set unbounding packet with valset update id
386+
vscPacket := ccv.ValidatorSetChangePacketData{ValsetUpdateId: uint64(3)}
387+
app.ChildKeeper.SetUnbondingPacket(ctx, uint64(0), channeltypes.Packet{Data: vscPacket.GetBytes()})
388+
385389
valInfo := slashingtypes.NewValidatorSigningInfo(consAddr, ctx.BlockHeight(), ctx.BlockHeight()-1,
386390
time.Time{}.UTC(), false, int64(0))
387391
app.SlashingKeeper.SetValidatorSigningInfo(ctx, consAddr, valInfo)
@@ -419,8 +423,7 @@ func (suite *KeeperTestSuite) TestAfterValidatorDowntimeHook() {
419423

420424
now := time.Now()
421425

422-
// Cover the cases when the validatir jailing duration
423-
// is either elapsed, null or still going
426+
// test cases with different jail durations
424427
testcases := []struct {
425428
jailedUntil time.Time
426429
expUpdate bool
@@ -436,11 +439,23 @@ func (suite *KeeperTestSuite) TestAfterValidatorDowntimeHook() {
436439
},
437440
}
438441

439-
// synchronize the block time with the test cases
442+
// set validator signing info to not panic
440443
ctx = ctx.WithBlockTime(now)
441444
valInfo := slashingtypes.NewValidatorSigningInfo(consAddr, int64(1), int64(1),
442445
time.Time{}.UTC(), false, int64(0))
446+
app.SlashingKeeper.SetValidatorSigningInfo(ctx, consAddr, valInfo)
447+
448+
// expect no updates when there is no unbonding packets
449+
app.ChildKeeper.AfterValidatorDowntime(ctx, consAddr, int64(1))
450+
signInfo, _ := app.SlashingKeeper.GetValidatorSigningInfo(ctx, consAddr)
451+
452+
suite.Require().True(false == !(signInfo.JailedUntil.Equal(signInfo.JailedUntil)))
443453

454+
// set unbounding packet with valset update id
455+
vscPacket := ccv.ValidatorSetChangePacketData{ValsetUpdateId: uint64(3)}
456+
app.ChildKeeper.SetUnbondingPacket(ctx, uint64(0), channeltypes.Packet{Data: vscPacket.GetBytes()})
457+
458+
// test cases
444459
for _, tc := range testcases {
445460
// set the current unjailing time
446461
valInfo.JailedUntil = tc.jailedUntil
@@ -461,8 +476,17 @@ func (suite *KeeperTestSuite) TestGetLastUnboundingPacket() {
461476
app := suite.childChain.App.(*app.App)
462477
ctx := suite.childChain.GetContext()
463478

464-
ubdPacket := app.ChildKeeper.GetLastUnbondingPacket(ctx)
465-
suite.Require().Zero(ubdPacket.ValsetUpdateId)
479+
// check if IBC packet is valid
480+
_, err := app.ChildKeeper.GetLastUnbondingPacketData(ctx)
481+
suite.NotNil(err)
482+
483+
app.ChildKeeper.SetUnbondingPacket(ctx, uint64(0), channeltypes.Packet{Sequence: 1})
484+
485+
// check if unbouding packet data is valid
486+
_, err = app.ChildKeeper.GetLastUnbondingPacketData(ctx)
487+
suite.NotNil(err)
488+
489+
// check if the last packet stored is returned
466490
for i := 0; i < 5; i++ {
467491
pd := ccv.NewValidatorSetChangePacketData(
468492
[]abci.ValidatorUpdate{},
@@ -473,6 +497,7 @@ func (suite *KeeperTestSuite) TestGetLastUnboundingPacket() {
473497
app.ChildKeeper.SetUnbondingPacket(ctx, uint64(i), packet)
474498
}
475499

476-
ubdPacket = app.ChildKeeper.GetLastUnbondingPacket(ctx)
500+
ubdPacket, err := app.ChildKeeper.GetLastUnbondingPacketData(ctx)
501+
suite.Nil(err)
477502
suite.Require().Equal(uint64(4), ubdPacket.ValsetUpdateId)
478503
}

x/ccv/child/keeper/relay.go

+7-10
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, newCha
4242
} else {
4343
pendingChanges = utils.AccumulateChanges(currentChanges.ValidatorUpdates, newChanges.ValidatorUpdates)
4444
}
45-
k.SetPendingChanges(ctx, ccv.ValidatorSetChangePacketData{ValidatorUpdates: pendingChanges})
45+
k.SetPendingChanges(ctx, ccv.ValidatorSetChangePacketData{ValidatorUpdates: pendingChanges, ValsetUpdateId: newChanges.ValsetUpdateId})
4646

4747
// Save unbonding time and packet
4848
unbondingTime := ctx.BlockTime().Add(types.UnbondingTime)
@@ -92,7 +92,7 @@ func (k Keeper) UnbondMaturePackets(ctx sdk.Context) error {
9292

9393
// SendPacket sends a packet that initiates the given validator
9494
// slashing and jailing on the provider chain.
95-
func (k Keeper) SendPacket(ctx sdk.Context, val abci.Validator, slashFraction, jailedUntil int64) error {
95+
func (k Keeper) SendPacket(ctx sdk.Context, val abci.Validator, valsetUpdateID uint64, slashFraction, jailedUntil int64) error {
9696

9797
// check the setup
9898
channelID, ok := k.GetParentChannel(ctx)
@@ -117,13 +117,7 @@ func (k Keeper) SendPacket(ctx sdk.Context, val abci.Validator, slashFraction, j
117117
)
118118
}
119119

120-
// add the last ValsetUpdateId to the packet data so that the provider
121-
// can find the block height when the downtime happened
122-
valsetUpdateId := k.GetLastUnbondingPacket(ctx).ValsetUpdateId
123-
if valsetUpdateId == 0 {
124-
return sdkerrors.Wrapf(ccv.ErrInvalidChildState, "last valset update id not set")
125-
}
126-
packetData := ccv.NewValidatorDowtimePacketData(val, valsetUpdateId, slashFraction, jailedUntil)
120+
packetData := ccv.NewValidatorDowntimePacketData(val, valsetUpdateID, slashFraction, jailedUntil)
127121
packetDataBytes := packetData.GetBytes()
128122

129123
// send ValidatorDowntime infractions in IBC packet
@@ -140,6 +134,9 @@ func (k Keeper) SendPacket(ctx sdk.Context, val abci.Validator, slashFraction, j
140134
return nil
141135
}
142136

143-
func (k Keeper) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes.Packet, ack channeltypes.Acknowledgement) error {
137+
func (k Keeper) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes.Packet, data ccv.ValidatorDowntimePacketData, ack channeltypes.Acknowledgement) error {
138+
if err := ack.GetError(); err != "" {
139+
return fmt.Errorf(err)
140+
}
144141
return nil
145142
}

x/ccv/child/keeper/relay_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/cosmos/interchain-security/x/ccv/types"
1515
ccv "github.com/cosmos/interchain-security/x/ccv/types"
1616
abci "github.com/tendermint/tendermint/abci/types"
17+
"github.com/tendermint/tendermint/libs/bytes"
1718
)
1819

1920
func (suite *KeeperTestSuite) TestOnRecvPacket() {
@@ -239,3 +240,23 @@ func (suite *KeeperTestSuite) TestUnbondMaturePackets() {
239240
suite.Require().Nil(ackBytes3, "acknowledgement written for unbonding packet 3")
240241

241242
}
243+
244+
func (suite *KeeperTestSuite) TestOnAcknowledgement() {
245+
packetData := types.NewValidatorDowntimePacketData(
246+
abci.Validator{Address: bytes.HexBytes{}, Power: int64(1)}, uint64(1), int64(4), int64(1),
247+
)
248+
249+
packet := channeltypes.NewPacket(packetData.GetBytes(), 1, parenttypes.PortID, suite.path.EndpointB.ChannelID,
250+
childtypes.PortID, suite.path.EndpointA.ChannelID, clienttypes.Height{}, uint64(time.Now().Add(60*time.Second).UnixNano()))
251+
ack := channeltypes.NewResultAcknowledgement([]byte{1})
252+
253+
// expect no error
254+
err := suite.childChain.App.(*app.App).ChildKeeper.OnAcknowledgementPacket(suite.ctx, packet, packetData, ack)
255+
suite.Nil(err)
256+
257+
// expect an error
258+
ack = channeltypes.NewErrorAcknowledgement("error")
259+
260+
err = suite.childChain.App.(*app.App).ChildKeeper.OnAcknowledgementPacket(suite.ctx, packet, packetData, ack)
261+
suite.NotNil(err)
262+
}

x/ccv/child/module.go

+37-4
Original file line numberDiff line numberDiff line change
@@ -350,16 +350,49 @@ func (am AppModule) OnRecvPacket(
350350
// OnAcknowledgementPacket implements the IBCModule interface
351351
func (am AppModule) OnAcknowledgementPacket(
352352
ctx sdk.Context,
353-
_ channeltypes.Packet,
353+
packet channeltypes.Packet,
354354
acknowledgement []byte,
355355
_ sdk.AccAddress,
356356
) (*sdk.Result, error) {
357357
var ack channeltypes.Acknowledgement
358358
if err := ccv.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil {
359-
fmt.Println(err)
360-
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal parent packet acknowledgement: %v", err)
359+
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal child packet acknowledgement: %v", err)
360+
}
361+
var data ccv.ValidatorDowntimePacketData
362+
if err := ccv.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
363+
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal child packet data: %s", err.Error())
364+
}
365+
366+
if err := am.keeper.OnAcknowledgementPacket(ctx, packet, data, ack); err != nil {
367+
return nil, err
368+
}
369+
370+
ctx.EventManager().EmitEvent(
371+
sdk.NewEvent(
372+
ccv.EventTypePacket,
373+
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
374+
sdk.NewAttribute(ccv.AttributeKeyAck, ack.String()),
375+
),
376+
)
377+
switch resp := ack.Response.(type) {
378+
case *channeltypes.Acknowledgement_Result:
379+
ctx.EventManager().EmitEvent(
380+
sdk.NewEvent(
381+
ccv.EventTypePacket,
382+
sdk.NewAttribute(ccv.AttributeKeyAckSuccess, string(resp.Result)),
383+
),
384+
)
385+
case *channeltypes.Acknowledgement_Error:
386+
ctx.EventManager().EmitEvent(
387+
sdk.NewEvent(
388+
ccv.EventTypePacket,
389+
sdk.NewAttribute(ccv.AttributeKeyAckError, resp.Error),
390+
),
391+
)
361392
}
362-
return nil, sdkerrors.Wrap(ccv.ErrInvalidChannelFlow, "cannot receive acknowledgement on child port, child chain does not send packet over channel.")
393+
return &sdk.Result{
394+
Events: ctx.EventManager().Events().ToABCIEvents(),
395+
}, nil
363396
}
364397

365398
// OnTimeoutPacket implements the IBCModule interface

x/ccv/parent/keeper/relay.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ func (k Keeper) EndBlockCallback(ctx sdk.Context) {
126126

127127
// OnRecvPacket slahes and jails the given validator in the packet data
128128
func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data ccv.ValidatorDowntimePacketData) exported.Acknowledgement {
129-
if childChannel, ok := k.GetChannelToChain(ctx, packet.DestinationChannel); !ok && childChannel != packet.DestinationChannel {
129+
if chainID, ok := k.GetChannelToChain(ctx, packet.DestinationChannel); !ok {
130130
ack := channeltypes.NewErrorAcknowledgement(
131-
fmt.Sprintf("packet sent on a channel %s other than the established child channel %s", packet.DestinationChannel, childChannel),
131+
fmt.Sprintf("chain ID doesn't exist for channel ID: %s", chainID),
132132
)
133133
chanCap, _ := k.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(packet.DestinationPort, packet.DestinationChannel))
134134
k.channelKeeper.ChanCloseInit(ctx, packet.DestinationPort, packet.DestinationChannel, chanCap)
@@ -141,7 +141,8 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data c
141141
return &ack
142142
}
143143

144-
return nil
144+
ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
145+
return ack
145146
}
146147

147148
// HandleConsumerDowntime gets the validator and the downtime infraction height from the packet data
@@ -151,11 +152,17 @@ func (k Keeper) HandleConsumerDowntime(ctx sdk.Context, downtimeData ccv.Validat
151152
consAddr := sdk.ConsAddress(downtimeData.Validator.Address)
152153

153154
// get the validator data
154-
val, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
155+
validator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
155156
if !found {
156157
return fmt.Errorf("cannot find validator with address %s", consAddr.String())
157158
}
158159

160+
// make sure the validator is not yet unbonded;
161+
// stakingKeeper.Slash() panics otherwise
162+
if validator.IsUnbonded() {
163+
return fmt.Errorf("should not be slashing unbonded validator: %s", validator.GetOperator())
164+
}
165+
159166
// get the downtime block height from the valsetUpdateID
160167
blockHeight := k.GetValsetUpdateBlockHeight(ctx, downtimeData.ValsetUpdateId)
161168
if blockHeight == 0 {
@@ -164,10 +171,10 @@ func (k Keeper) HandleConsumerDowntime(ctx sdk.Context, downtimeData ccv.Validat
164171

165172
// slash and jail the validator
166173
k.stakingKeeper.Slash(ctx, consAddr, int64(blockHeight), downtimeData.Validator.Power, sdk.NewDec(1).QuoInt64(downtimeData.SlashFraction))
167-
if !val.Jailed {
174+
if !validator.IsJailed() {
168175
k.stakingKeeper.Jail(ctx, consAddr)
169176
}
170-
// TODO: check if the missed block bits and sign info need to be reseted
177+
171178
k.slashingKeeper.JailUntil(ctx, consAddr, ctx.BlockHeader().Time.Add(time.Duration(downtimeData.JailTime)))
172179

173180
return nil

x/ccv/parent/module.go

-2
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,6 @@ func (am AppModule) OnRecvPacket(
340340
),
341341
)
342342

343-
// NOTE: In successful case, acknowledgement will be written asynchronously
344-
// after unbonding period has elapsed.
345343
return ack
346344
}
347345

0 commit comments

Comments
 (0)