-
Notifications
You must be signed in to change notification settings - Fork 148
/
Copy pathrelay.go
256 lines (229 loc) · 10.1 KB
/
relay.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
package keeper
import (
"fmt"
"strconv"
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types"
"github.com/cosmos/ibc-go/v4/modules/core/exported"
"github.com/cosmos/interchain-security/v2/x/ccv/consumer/types"
ccv "github.com/cosmos/interchain-security/v2/x/ccv/types"
abci "github.com/tendermint/tendermint/abci/types"
)
// OnRecvVSCPacket sets the pending validator set changes that will be flushed to ABCI on Endblock
// and set the maturity time for the packet. Once the maturity time elapses, a VSCMatured packet is
// sent back to the provider chain.
//
// Note: CCV uses an ordered IBC channel, meaning VSC packet changes will be accumulated (and later
// processed by ApplyCCValidatorChanges) s.t. more recent val power changes overwrite older ones.
func (k Keeper) OnRecvVSCPacket(ctx sdk.Context, packet channeltypes.Packet, newChanges ccv.ValidatorSetChangePacketData) exported.Acknowledgement {
// get the provider channel
providerChannel, found := k.GetProviderChannel(ctx)
if found && providerChannel != packet.DestinationChannel {
// VSC packet was sent on a channel different than the provider channel;
// this should never happen
panic(fmt.Errorf("VSCPacket received on unknown channel %s; expected: %s",
packet.DestinationChannel, providerChannel))
}
if !found {
// the first packet from the provider chain
// - mark the CCV channel as established
k.SetProviderChannel(ctx, packet.DestinationChannel)
k.Logger(ctx).Info("CCV channel established", "port", packet.DestinationPort, "channel", packet.DestinationChannel)
// emit event on first VSC packet to signal that CCV is working
ctx.EventManager().EmitEvent(
sdk.NewEvent(
ccv.EventTypeChannelEstablished,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(channeltypes.AttributeKeyChannelID, packet.DestinationChannel),
sdk.NewAttribute(channeltypes.AttributeKeyPortID, packet.DestinationPort),
),
)
}
// Set pending changes by accumulating changes from this packet with all prior changes
currentValUpdates := []abci.ValidatorUpdate{}
currentChanges, exists := k.GetPendingChanges(ctx)
if exists {
currentValUpdates = currentChanges.ValidatorUpdates
}
pendingChanges := ccv.AccumulateChanges(currentValUpdates, newChanges.ValidatorUpdates)
k.SetPendingChanges(ctx, ccv.ValidatorSetChangePacketData{
ValidatorUpdates: pendingChanges,
})
// Save maturity time and packet
maturityTime := ctx.BlockTime().Add(k.GetUnbondingPeriod(ctx))
k.SetPacketMaturityTime(ctx, newChanges.ValsetUpdateId, maturityTime)
k.Logger(ctx).Debug("packet maturity time was set",
"vscID", newChanges.ValsetUpdateId,
"maturity time (utc)", maturityTime.UTC(),
"maturity time (nano)", uint64(maturityTime.UnixNano()),
)
// set height to VSC id mapping
blockHeight := uint64(ctx.BlockHeight()) + 1
k.SetHeightValsetUpdateID(ctx, blockHeight, newChanges.ValsetUpdateId)
k.Logger(ctx).Debug("block height was mapped to vscID", "height", blockHeight, "vscID", newChanges.ValsetUpdateId)
// remove outstanding slashing flags of the validators
// for which the slashing was acknowledged by the provider chain
for _, addr := range newChanges.GetSlashAcks() {
k.DeleteOutstandingDowntime(ctx, addr)
}
k.Logger(ctx).Info("finished receiving/handling VSCPacket",
"vscID", newChanges.ValsetUpdateId,
"len updates", len(newChanges.ValidatorUpdates),
"len slash acks", len(newChanges.SlashAcks),
)
ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
return ack
}
// QueueVSCMaturedPackets appends matured VSCs to an internal queue.
//
// Note: Per spec, a VSC reaching maturity on a consumer chain means that all the unbonding
// operations that resulted in validator updates included in that VSC have matured on
// the consumer chain.
func (k Keeper) QueueVSCMaturedPackets(ctx sdk.Context) {
for _, maturityTime := range k.GetElapsedPacketMaturityTimes(ctx) {
// construct validator set change packet data
vscPacket := ccv.NewVSCMaturedPacketData(maturityTime.VscId)
// Append VSCMatured packet to pending packets.
// Sending packets is attempted each EndBlock.
// Unsent packets remain in the queue until sent.
k.AppendPendingPacket(ctx, ccv.ConsumerPacketData{
Type: ccv.VscMaturedPacket,
Data: &ccv.ConsumerPacketData_VscMaturedPacketData{VscMaturedPacketData: vscPacket},
})
k.DeletePacketMaturityTimes(ctx, maturityTime.VscId, maturityTime.MaturityTime)
k.Logger(ctx).Info("VSCMaturedPacket enqueued", "vscID", vscPacket.ValsetUpdateId)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
ccv.EventTypeVSCMatured,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(ccv.AttributeChainID, ctx.ChainID()),
sdk.NewAttribute(ccv.AttributeConsumerHeight, strconv.Itoa(int(ctx.BlockHeight()))),
sdk.NewAttribute(ccv.AttributeValSetUpdateID, strconv.Itoa(int(maturityTime.VscId))),
sdk.NewAttribute(ccv.AttributeTimestamp, ctx.BlockTime().String()),
),
)
}
}
// QueueSlashPacket appends a slash packet containing the given validator data and slashing info to queue.
func (k Keeper) QueueSlashPacket(ctx sdk.Context, validator abci.Validator, valsetUpdateID uint64, infraction stakingtypes.InfractionType) {
consAddr := sdk.ConsAddress(validator.Address)
downtime := infraction == stakingtypes.Downtime
// return if an outstanding downtime request is set for the validator
if downtime && k.OutstandingDowntime(ctx, consAddr) {
return
}
if downtime {
// set outstanding downtime to not send multiple
// slashing requests for the same downtime infraction
k.SetOutstandingDowntime(ctx, consAddr)
}
// construct slash packet data
slashPacket := ccv.NewSlashPacketData(validator, valsetUpdateID, infraction)
// append the Slash packet data to pending data packets
// to be sent once the CCV channel is established
k.AppendPendingPacket(ctx, ccv.ConsumerPacketData{
Type: ccv.SlashPacket,
Data: &ccv.ConsumerPacketData_SlashPacketData{
SlashPacketData: slashPacket,
},
})
k.Logger(ctx).Info("SlashPacket enqueued",
"vscID", slashPacket.ValsetUpdateId,
"validator cons addr", sdk.ConsAddress(slashPacket.Validator.Address).String(),
"infraction", slashPacket.Infraction,
)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
ccv.EventTypeConsumerSlashRequest,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(ccv.AttributeValidatorAddress, sdk.ConsAddress(validator.Address).String()),
sdk.NewAttribute(ccv.AttributeValSetUpdateID, strconv.Itoa(int(valsetUpdateID))),
sdk.NewAttribute(ccv.AttributeInfractionType, infraction.String()),
),
)
}
// SendPackets iterates queued packets and sends them in FIFO order.
// received VSC packets in order, and write acknowledgements for all matured VSC packets.
//
// This method is a no-op if there is no established channel to provider or the queue is empty.
//
// Note: Per spec, a VSC reaching maturity on a consumer chain means that all the unbonding
// operations that resulted in validator updates included in that VSC have matured on
// the consumer chain.
func (k Keeper) SendPackets(ctx sdk.Context) {
channelID, ok := k.GetProviderChannel(ctx)
if !ok {
return
}
pending := k.GetPendingPackets(ctx)
for _, p := range pending.GetList() {
// send packet over IBC
err := ccv.SendIBCPacket(
ctx,
k.scopedKeeper,
k.channelKeeper,
channelID, // source channel id
ccv.ConsumerPortID, // source port id
p.GetBytes(),
k.GetCCVTimeoutPeriod(ctx),
)
if err != nil {
if clienttypes.ErrClientNotActive.Is(err) {
// IBC client is expired!
// leave the packet data stored to be sent once the client is upgraded
k.Logger(ctx).Info("IBC client is expired, cannot send IBC packet; leaving packet data stored:", "type", p.Type.String())
return
}
// Not able to send packet over IBC!
// Leave the packet data stored for the sent to be retried in the next block.
// Note that if VSCMaturedPackets are not sent for long enough, the provider
// will remove the consumer anyway.
k.Logger(ctx).Error("cannot send IBC packet; leaving packet data stored:", "type", p.Type.String(), "err", err.Error())
return
}
}
// clear pending data packets
k.DeletePendingDataPackets(ctx)
}
// OnAcknowledgementPacket executes application logic for acknowledgments of sent VSCMatured and Slash packets
// in conjunction with the ibc module's execution of "acknowledgePacket",
// according to https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics#processing-acknowledgements
func (k Keeper) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes.Packet, ack channeltypes.Acknowledgement) error {
if err := ack.GetError(); err != "" {
// Reasons for ErrorAcknowledgment
// - packet data could not be successfully decoded
// - invalid Slash packet
// None of these should ever happen.
k.Logger(ctx).Error(
"recv ErrorAcknowledgement",
"channel", packet.SourceChannel,
"error", err,
)
// Initiate ChanCloseInit using packet source (non-counterparty) port and channel
err := k.ChanCloseInit(ctx, packet.SourcePort, packet.SourceChannel)
if err != nil {
return fmt.Errorf("ChanCloseInit(%s) failed: %s", packet.SourceChannel, err.Error())
}
// check if there is an established CCV channel to provider
channelID, found := k.GetProviderChannel(ctx)
if !found {
return errorsmod.Wrapf(types.ErrNoProposerChannelId, "recv ErrorAcknowledgement on non-established channel %s", packet.SourceChannel)
}
if channelID != packet.SourceChannel {
// Close the established CCV channel as well
return k.ChanCloseInit(ctx, ccv.ConsumerPortID, channelID)
}
}
return nil
}
// IsChannelClosed returns a boolean whether a given channel is in the CLOSED state
func (k Keeper) IsChannelClosed(ctx sdk.Context, channelID string) bool {
channel, found := k.channelKeeper.GetChannel(ctx, ccv.ConsumerPortID, channelID)
if !found || channel.State == channeltypes.CLOSED {
return true
}
return false
}