channel.gno
9.69 Kb · 325 lines
1package core
2
3import (
4 "gno.land/p/onbloc/ibc/union/types"
5 core "gno.land/r/onbloc/ibc/union/core"
6)
7
8// channel.gno: channel handshake and close logic. The admin gate stays on the
9// proxy entrypoint; the Force* recovery variants remain proxy admin operations.
10// Each handshake step dispatches the matching callback to the channel's app.
11
12// ChannelOpenInit starts a channel in the Init state and notifies its app. The
13// created channel id is observable through the emitted event, not returned.
14func (c *coreV1) ChannelOpenInit(_ int, rlm realm, msg types.MsgChannelOpenInit) {
15 assertIsRlmCurrent(0, rlm)
16
17 relayer := rlm.Previous().Address()
18 portId := types.Bytes(types.CloneBytes(msg.PortId))
19 connection := c.ensureConnectionState(msg.ConnectionId)
20 channelId := c.store.NextChannelId(0, rlm)
21
22 channel := core.NewChannel(
23 types.ChannelStateInit,
24 msg.ConnectionId,
25 0,
26 types.Bytes(types.CloneBytes(msg.CounterpartyPortId)),
27 msg.Version,
28 )
29 c.store.CreateChannel(0, rlm, channelId, channel, portId)
30
31 a, err := c.getAppAtPort(portId)
32 if err != nil {
33 panic(err)
34 }
35
36 a.OnChannelOpenInit(cross(rlm), msg.ConnectionId, channelId, msg.Version, relayer)
37 core.EmitChannelOpenInit(0, rlm, portId, channelId, channel, connection)
38}
39
40// ChannelOpenTry proves a counterparty Init and opens the local channel in
41// TryOpen, notifying its app.
42func (c *coreV1) ChannelOpenTry(_ int, rlm realm, msg types.MsgChannelOpenTry) {
43 assertIsRlmCurrent(0, rlm)
44
45 c.channelOpenTry(0, rlm, msg, true)
46}
47
48// ForceChannelOpenTry is the admin recovery variant of ChannelOpenTry that skips
49// counterparty proof verification.
50func (c *coreV1) ForceChannelOpenTry(_ int, rlm realm, msg types.MsgChannelOpenTry) {
51 assertIsRlmCurrent(0, rlm)
52
53 c.channelOpenTry(0, rlm, msg, false)
54}
55
56func (c *coreV1) channelOpenTry(_ int, rlm realm, msg types.MsgChannelOpenTry, verify bool) {
57 relayer := rlm.Previous().Address()
58 portId := types.Bytes(types.CloneBytes(msg.PortId))
59 if msg.Channel.State != types.ChannelStateTryOpen {
60 panic(makeError(core.ErrInvalidChannelState))
61 }
62
63 if msg.Channel.CounterpartyChannelId == 0 {
64 panic(makeError(core.ErrInvalidCounterpartyChannelId))
65 }
66
67 connection := c.ensureConnectionState(msg.Channel.ConnectionId)
68 channelId := c.store.NextChannelId(0, rlm)
69
70 if verify {
71 expectedChannel := core.NewChannel(
72 types.ChannelStateInit,
73 connection.CounterpartyConnectionId,
74 0,
75 portId,
76 msg.CounterpartyVersion,
77 )
78 key := types.ChannelPath(msg.Channel.CounterpartyChannelId)
79 value := core.ChannelValue(expectedChannel)
80 c.verifyMembership(connection.ClientId, types.NewHeight(msg.ProofHeight), msg.ProofInit, key[:], value[:])
81 }
82
83 c.store.CreateChannel(0, rlm, channelId, msg.Channel, portId)
84
85 a, err := c.getAppAtPort(portId)
86 if err != nil {
87 panic(err)
88 }
89
90 a.OnChannelOpenTry(cross(rlm), msg.Channel.ConnectionId, channelId, msg.Channel.Version, msg.CounterpartyVersion, relayer)
91
92 core.EmitChannelOpenTry(0, rlm, portId, channelId, msg.Channel, msg.CounterpartyVersion, connection)
93}
94
95// ChannelOpenAck proves the counterparty TryOpen and moves the local Init
96// channel to Open, notifying its app.
97func (c *coreV1) ChannelOpenAck(_ int, rlm realm, msg types.MsgChannelOpenAck) {
98 assertIsRlmCurrent(0, rlm)
99
100 c.channelOpenAck(0, rlm, msg, true)
101}
102
103// ForceChannelOpenAck is the admin recovery variant of ChannelOpenAck that skips
104// counterparty proof verification.
105func (c *coreV1) ForceChannelOpenAck(_ int, rlm realm, msg types.MsgChannelOpenAck) {
106 assertIsRlmCurrent(0, rlm)
107
108 c.channelOpenAck(0, rlm, msg, false)
109}
110
111func (c *coreV1) channelOpenAck(_ int, rlm realm, msg types.MsgChannelOpenAck, verify bool) {
112 relayer := rlm.Previous().Address()
113
114 channel, err := c.getChannel(msg.ChannelId)
115 if err != nil {
116 panic(err)
117 }
118
119 if channel.State != types.ChannelStateInit {
120 panic(makeError(core.ErrInvalidChannelState))
121 }
122
123 connection := c.ensureConnectionState(channel.ConnectionId)
124 portId, _ := c.store.GetChannelOwner(msg.ChannelId)
125
126 if verify {
127 expectedChannel := core.NewChannel(
128 types.ChannelStateTryOpen,
129 connection.CounterpartyConnectionId,
130 msg.ChannelId,
131 portId,
132 msg.CounterpartyVersion,
133 )
134 key := types.ChannelPath(msg.CounterpartyChannelId)
135 value := core.ChannelValue(expectedChannel)
136 c.verifyMembership(connection.ClientId, types.NewHeight(msg.ProofHeight), msg.ProofTry, key[:], value[:])
137 }
138
139 updated := core.NewChannel(
140 types.ChannelStateOpen,
141 channel.ConnectionId,
142 msg.CounterpartyChannelId,
143 channel.CounterpartyPortId.Clone(),
144 msg.CounterpartyVersion,
145 )
146 c.store.SaveChannel(0, rlm, msg.ChannelId, updated)
147
148 a, err := c.getAppAtPort(portId)
149 if err != nil {
150 panic(err)
151 }
152
153 a.OnChannelOpenAck(cross(rlm), msg.ChannelId, msg.CounterpartyChannelId, msg.CounterpartyVersion, relayer)
154
155 core.EmitChannelOpenAck(0, rlm, portId, msg.ChannelId, updated, connection)
156}
157
158// ChannelOpenConfirm proves the counterparty Open and moves the local TryOpen
159// channel to Open, notifying its app.
160func (c *coreV1) ChannelOpenConfirm(_ int, rlm realm, msg types.MsgChannelOpenConfirm) {
161 assertIsRlmCurrent(0, rlm)
162
163 c.channelOpenConfirm(0, rlm, msg, true)
164}
165
166// ForceChannelOpenConfirm is the admin recovery variant of ChannelOpenConfirm
167// that skips counterparty proof verification.
168func (c *coreV1) ForceChannelOpenConfirm(_ int, rlm realm, msg types.MsgChannelOpenConfirm) {
169 assertIsRlmCurrent(0, rlm)
170
171 c.channelOpenConfirm(0, rlm, msg, false)
172}
173
174func (c *coreV1) channelOpenConfirm(_ int, rlm realm, msg types.MsgChannelOpenConfirm, verify bool) {
175 relayer := rlm.Previous().Address()
176
177 channel, err := c.getChannel(msg.ChannelId)
178 if err != nil {
179 panic(err)
180 }
181
182 if channel.State != types.ChannelStateTryOpen {
183 panic(makeError(core.ErrInvalidChannelState))
184 }
185
186 if channel.CounterpartyChannelId == 0 {
187 panic(makeError(core.ErrInvalidCounterpartyChannelId))
188 }
189
190 connection := c.ensureConnectionState(channel.ConnectionId)
191 portId, _ := c.store.GetChannelOwner(msg.ChannelId)
192
193 if verify {
194 expectedChannel := core.NewChannel(
195 types.ChannelStateOpen,
196 connection.CounterpartyConnectionId,
197 msg.ChannelId,
198 portId,
199 channel.Version,
200 )
201 key := types.ChannelPath(channel.CounterpartyChannelId)
202 value := core.ChannelValue(expectedChannel)
203 c.verifyMembership(connection.ClientId, types.NewHeight(msg.ProofHeight), msg.ProofAck, key[:], value[:])
204 }
205
206 updated := core.NewChannel(
207 types.ChannelStateOpen,
208 channel.ConnectionId,
209 channel.CounterpartyChannelId,
210 channel.CounterpartyPortId.Clone(),
211 channel.Version,
212 )
213 c.store.SaveChannel(0, rlm, msg.ChannelId, updated)
214
215 a, err := c.getAppAtPort(portId)
216 if err != nil {
217 panic(err)
218 }
219
220 a.OnChannelOpenConfirm(cross(rlm), msg.ChannelId, relayer)
221
222 core.EmitChannelOpenConfirm(0, rlm, portId, msg.ChannelId, updated, connection)
223}
224
225// ChannelCloseInit closes an Open channel locally and notifies its app.
226func (c *coreV1) ChannelCloseInit(_ int, rlm realm, msg types.MsgChannelCloseInit) {
227 assertIsRlmCurrent(0, rlm)
228
229 relayer := rlm.Previous().Address()
230
231 channel, err := c.getChannel(msg.ChannelId)
232 if err != nil {
233 panic(err)
234 }
235
236 if channel.State != types.ChannelStateOpen {
237 panic(makeError(core.ErrInvalidChannelState))
238 }
239
240 if channel.CounterpartyChannelId == 0 {
241 panic(makeError(core.ErrInvalidCounterpartyChannelId))
242 }
243
244 connection := c.ensureConnectionState(channel.ConnectionId)
245 portId, _ := c.store.GetChannelOwner(msg.ChannelId)
246
247 updated := c.closedChannel(channel)
248 c.store.SaveChannel(0, rlm, msg.ChannelId, updated)
249
250 a, ok := c.store.GetAppByPortId(portId)
251 if !ok {
252 panic("port " + string(portId) + " not found")
253 }
254 a.OnChannelCloseInit(cross(rlm), msg.ChannelId, relayer)
255 core.EmitChannelCloseInit(0, rlm, portId, msg.ChannelId, updated, connection)
256}
257
258// ChannelCloseConfirm proves the counterparty Closed and closes the local
259// channel, notifying its app.
260func (c *coreV1) ChannelCloseConfirm(_ int, rlm realm, msg types.MsgChannelCloseConfirm) {
261 assertIsRlmCurrent(0, rlm)
262
263 relayer := rlm.Previous().Address()
264
265 channel, err := c.getChannel(msg.ChannelId)
266 if err != nil {
267 panic(err)
268 }
269
270 if channel.State != types.ChannelStateOpen {
271 panic(makeError(core.ErrInvalidChannelState))
272 }
273
274 if channel.CounterpartyChannelId == 0 {
275 panic(makeError(core.ErrInvalidCounterpartyChannelId))
276 }
277
278 connection := c.ensureConnectionState(channel.ConnectionId)
279 portId, _ := c.store.GetChannelOwner(msg.ChannelId)
280
281 expectedChannel := core.NewChannel(
282 types.ChannelStateClosed,
283 connection.CounterpartyConnectionId,
284 msg.ChannelId,
285 portId,
286 channel.Version,
287 )
288 key := types.ChannelPath(channel.CounterpartyChannelId)
289 value := core.ChannelValue(expectedChannel)
290 c.verifyMembership(connection.ClientId, types.NewHeight(msg.ProofHeight), msg.ProofInit, key[:], value[:])
291
292 updated := c.closedChannel(channel)
293 c.store.SaveChannel(0, rlm, msg.ChannelId, updated)
294
295 a, ok := c.store.GetAppByPortId(portId)
296 if !ok {
297 panic("port " + string(portId) + " not found")
298 }
299 a.OnChannelCloseConfirm(cross(rlm), msg.ChannelId, relayer)
300 core.EmitChannelCloseConfirm(0, rlm, portId, msg.ChannelId, updated, connection)
301}
302
303// closedChannel returns a Closed copy of channel without mutating the store-owned
304// value.
305func (c *coreV1) closedChannel(channel types.Channel) types.Channel {
306 return core.NewChannel(
307 types.ChannelStateClosed,
308 channel.ConnectionId,
309 channel.CounterpartyChannelId,
310 channel.CounterpartyPortId.Clone(),
311 channel.Version,
312 )
313}
314
315// getChannel returns the stored channel for channelId, or an error when it is
316// missing. It centralizes the lookup shared by the handshake and packet paths;
317// the caller (entrypoint) decides whether a miss reverts.
318func (c *coreV1) getChannel(channelId types.ChannelId) (types.Channel, error) {
319 channel, ok := c.store.GetChannel(channelId)
320 if !ok {
321 return types.Channel{}, makeError(core.ErrChannelNotFound, channelId.String())
322 }
323
324 return channel, nil
325}