packet.gno
14.72 Kb · 489 lines
1package core
2
3import (
4 "time"
5
6 "gno.land/p/onbloc/ibc/union/app"
7 "gno.land/p/onbloc/ibc/union/types"
8 core "gno.land/r/onbloc/ibc/union/core"
9)
10
11// packet.gno: the packet lifecycle. SendPacket and WriteAcknowledgement are
12// caller-authorized (port owner); the recv/ack/timeout/batch/commit-proof
13// entrypoints keep their admin gate on the proxy. Apps are notified via cross(rlm).
14
15// SendPacket commits a packet for the caller's port. Per the spec the timeout
16// height is always zero; only the timeout timestamp is meaningful.
17func (c *coreV1) SendPacket(_ int, rlm realm, sourceChannelId types.ChannelId, timeoutTimestamp types.Timestamp, data []byte) types.Packet {
18 assertIsRlmCurrent(0, rlm)
19 assertPacketDataSize(data)
20
21 if timeoutTimestamp == 0 {
22 panic(makeError(core.ErrTimeoutMustBeSet))
23 }
24
25 portId, ok := c.store.GetChannelOwner(sourceChannelId)
26 if !ok {
27 panic(makeError(core.ErrChannelNotFound, sourceChannelId.String()))
28 }
29
30 if string(portId) != rlm.Previous().PkgPath() {
31 panic(makeError(core.ErrUnauthorizedPacketSender))
32 }
33
34 channel, err := c.getChannel(sourceChannelId)
35 if err != nil {
36 panic(err)
37 }
38
39 if channel.State != types.ChannelStateOpen {
40 panic(makeError(core.ErrInvalidChannelState))
41 }
42
43 connection := c.ensureConnectionState(channel.ConnectionId)
44
45 packet := types.Packet{
46 SourceChannelId: sourceChannelId,
47 DestinationChannelId: channel.CounterpartyChannelId,
48 Data: data,
49 TimeoutHeight: 0,
50 TimeoutTimestamp: timeoutTimestamp,
51 }
52 if c.store.HasPacketCommitment(packet) {
53 panic(makeError(core.ErrPacketCommitmentAlreadyExists))
54 }
55
56 c.store.CommitPacket(0, rlm, packet)
57
58 core.EmitPacketSend(0, rlm, packet, channel, connection)
59
60 return packet
61}
62
63// PacketRecv proves a batch of packets committed on the counterparty and
64// dispatches each to its app, recording receipts and sync acks.
65func (c *coreV1) PacketRecv(_ int, rlm realm, msg types.MsgPacketRecv) {
66 assertIsRlmCurrent(0, rlm)
67
68 relayer := rlm.Previous().Address()
69
70 if len(msg.Packets) == 0 {
71 panic(makeError(core.ErrNotEnoughPackets))
72 }
73
74 destinationChannelId := msg.Packets[0].DestinationChannelId
75 channel, err := c.getChannel(destinationChannelId)
76 if err != nil {
77 panic(err)
78 }
79
80 connection := c.ensureConnectionState(channel.ConnectionId)
81
82 proofKey := types.BatchPacketsPath(core.CommitPacketsHash(msg.Packets))
83 magic := types.COMMITMENT_MAGIC
84 c.verifyMembership(connection.ClientId, types.NewHeight(msg.ProofHeight), msg.Proof, proofKey[:], magic[:])
85
86 portId, _ := c.store.GetChannelOwner(destinationChannelId)
87 a, err := c.getAppAtPort(portId)
88 if err != nil {
89 panic(err)
90 }
91
92 nanoTimestamp := uint64(time.Now().UnixNano())
93
94 // reference iterates packets.into_iter().zip(relayer_msgs) (contract.rs L1751):
95 // each packet is paired with its relayer msg, so a packet without a matching
96 // relayer msg is not dispatched (iteration stops at the shorter array).
97 for i := 0; i < len(msg.Packets) && i < len(msg.RelayerMsgs); i++ {
98 packet := msg.Packets[i]
99 relayerMsg := msg.RelayerMsgs[i]
100
101 skip := c.acceptRecvPacket(0, rlm, packet, destinationChannelId, nanoTimestamp)
102 if skip {
103 continue
104 }
105
106 res := a.OnRecvPacket(cross(rlm), packet, relayer, relayerMsg)
107 if err := c.commitSyncAck(0, rlm, packet, channel, connection, res); err != nil {
108 panic(err)
109 }
110
111 core.EmitPacketRecv(0, rlm, packet, channel, connection, relayerMsg)
112 }
113}
114
115// IntentPacketRecv settles packets on the proofless market-maker fast path.
116func (c *coreV1) IntentPacketRecv(_ int, rlm realm, msg types.MsgIntentPacketRecv) {
117 assertIsRlmCurrent(0, rlm)
118
119 marketMaker := rlm.Previous().Address()
120
121 if len(msg.Packets) == 0 {
122 panic(makeError(core.ErrNotEnoughPackets))
123 }
124
125 destinationChannelId := msg.Packets[0].DestinationChannelId
126
127 channel, err := c.getChannel(destinationChannelId)
128 if err != nil {
129 panic(err)
130 }
131 connection := c.ensureConnectionState(channel.ConnectionId)
132
133 portId, _ := c.store.GetChannelOwner(destinationChannelId)
134 a, err := c.getAppAtPort(portId)
135 if err != nil {
136 panic(err)
137 }
138
139 intentApp, ok := a.(app.IIntentApp)
140 if !ok {
141 panic(makeError(core.ErrIntentNotSupported))
142 }
143
144 nanoTimestamp := uint64(time.Now().UnixNano())
145
146 // reference iterates packets.into_iter().zip(market_maker_msgs) (contract.rs
147 // L1751, intent path): a packet without a matching market-maker msg is not
148 // dispatched (iteration stops at the shorter array).
149 for i := 0; i < len(msg.Packets) && i < len(msg.MarketMakerMessages); i++ {
150 packet := msg.Packets[i]
151 marketMakerMsg := msg.MarketMakerMessages[i]
152
153 skip := c.acceptRecvPacket(0, rlm, packet, destinationChannelId, nanoTimestamp)
154 if skip {
155 continue
156 }
157
158 res := intentApp.OnIntentRecvPacket(cross(rlm), packet, marketMaker, marketMakerMsg)
159 if err := c.commitSyncAck(0, rlm, packet, channel, connection, res); err != nil {
160 panic(err)
161 }
162
163 core.EmitIntentPacketRecv(0, rlm, packet, channel, connection, marketMakerMsg)
164 }
165}
166
167// PacketAcknowledgement proves the counterparty's acks and notifies the app.
168func (c *coreV1) PacketAcknowledgement(_ int, rlm realm, msg types.MsgPacketAcknowledgement) {
169 assertIsRlmCurrent(0, rlm)
170
171 relayer := rlm.Previous().Address()
172
173 if len(msg.Packets) == 0 {
174 panic(makeError(core.ErrNotEnoughPackets))
175 }
176
177 first := msg.Packets[0]
178 sourceChannelId := first.SourceChannelId
179 channel, err := c.getChannel(sourceChannelId)
180 if err != nil {
181 panic(err)
182 }
183
184 connection := c.ensureConnectionState(channel.ConnectionId)
185
186 commitmentKey := types.BatchReceiptsPath(core.CommitPacketsHash(msg.Packets))
187 commitmentValue := types.CommitAcks(msg.Acknowledgements)
188 c.verifyMembership(connection.ClientId, types.NewHeight(msg.ProofHeight), msg.Proof, commitmentKey[:], commitmentValue[:])
189
190 portId, _ := c.store.GetChannelOwner(sourceChannelId)
191 a, _ := c.store.GetAppByPortId(portId)
192
193 for i, ack := range msg.Acknowledgements {
194 if i >= len(msg.Packets) {
195 break
196 }
197
198 packet := msg.Packets[i]
199 if packet.SourceChannelId != first.SourceChannelId {
200 panic(makeError(core.ErrBatchSameChannelOnly))
201 }
202
203 c.markPacketAcknowledged(0, rlm, packet)
204
205 a.OnAcknowledgementPacket(cross(rlm), packet, ack, relayer)
206 core.EmitPacketAck(0, rlm, packet, channel, connection, ack)
207 }
208}
209
210// PacketTimeout proves the counterparty never received the packet and notifies
211// the app to refund.
212func (c *coreV1) PacketTimeout(_ int, rlm realm, msg types.MsgPacketTimeout) {
213 assertIsRlmCurrent(0, rlm)
214
215 relayer := rlm.Previous().Address()
216 packet := msg.Packet
217
218 channel, err := c.getChannel(packet.SourceChannelId)
219 if err != nil {
220 panic(err)
221 }
222
223 if channel.State != types.ChannelStateOpen {
224 panic(makeError(core.ErrInvalidChannelState))
225 }
226
227 connection := c.ensureConnectionState(channel.ConnectionId)
228
229 lc, err := c.getClient(connection.ClientId)
230 if err != nil {
231 panic(err)
232 }
233
234 proofTimestamp, err := lc.GetTimestampAtHeight(msg.ProofHeight)
235 if err != nil {
236 panic(err)
237 }
238
239 if proofTimestamp == 0 {
240 panic(makeError(core.ErrTimeoutProofTimestampNotFound))
241 }
242
243 commitmentKey := types.BatchReceiptsPath(core.CommitPacketHash(packet))
244 c.verifyNonMembership(connection.ClientId, types.NewHeight(msg.ProofHeight), msg.Proof, commitmentKey[:])
245
246 c.markPacketAcknowledged(0, rlm, packet)
247
248 if packet.TimeoutTimestamp == 0 {
249 panic(makeError(core.ErrTimeoutMustBeSet))
250 }
251
252 if uint64(packet.TimeoutTimestamp) > uint64(proofTimestamp) {
253 panic(makeError(core.ErrPacketTimeoutNotReached))
254 }
255
256 portId, _ := c.store.GetChannelOwner(packet.SourceChannelId)
257 a, _ := c.store.GetAppByPortId(portId)
258 a.OnTimeoutPacket(cross(rlm), packet, relayer)
259
260 core.EmitPacketTimeout(0, rlm, packet, channel, connection)
261}
262
263// markPacketAcknowledged is the source-side acknowledge/timeout state machine.
264// It verifies the packet's commitment exists and is not already acknowledged,
265// then flips it through the store's pure SetPacketAcknowledged primitive. Acks
266// and timeouts share it: both retire a sent packet exactly once.
267func (c *coreV1) markPacketAcknowledged(_ int, rlm realm, packet types.Packet) {
268 commitment, ok := c.store.PacketCommitment(packet.SourceChannelId, packet)
269 if !ok {
270 panic(makeError(core.ErrPacketCommitmentNotFound))
271 }
272
273 if commitment == types.COMMITMENT_MAGIC_ACK {
274 panic(makeError(core.ErrPacketAlreadyAcknowledged))
275 }
276
277 if commitment != types.COMMITMENT_MAGIC {
278 panic(makeError(core.ErrPacketCommitmentNotFound))
279 }
280
281 c.store.SetPacketAcknowledged(0, rlm, packet)
282}
283
284// CommitMembershipProof verifies and records a membership proof for later reuse.
285func (c *coreV1) CommitMembershipProof(_ int, rlm realm, msg types.MsgCommitMembershipProof) {
286 assertIsRlmCurrent(0, rlm)
287
288 c.verifyMembership(msg.ClientId, types.NewHeight(msg.ProofHeight), msg.Proof, msg.Path, msg.Value)
289 c.store.SaveMembershipProof(0, rlm, msg.ClientId, msg.ProofHeight, msg.Path, types.Keccak(msg.Value))
290 core.EmitCommitMembershipProof(0, rlm, msg)
291}
292
293// CommitNonMembershipProof verifies and records a non-membership proof.
294func (c *coreV1) CommitNonMembershipProof(_ int, rlm realm, msg types.MsgCommitNonMembershipProof) {
295 assertIsRlmCurrent(0, rlm)
296
297 if !c.store.ClientExists(msg.ClientId) {
298 panic(makeError(core.ErrClientNotFound, msg.ClientId.String()))
299 }
300
301 c.verifyNonMembership(msg.ClientId, types.NewHeight(msg.ProofHeight), msg.Proof, msg.Path)
302 c.store.SaveNonMembershipProof(0, rlm, msg.ClientId, msg.ProofHeight, msg.Path, types.NON_MEMBERSHIP_COMMITMENT_VALUE)
303 core.EmitCommitNonMembershipProof(0, rlm, msg)
304}
305
306// BatchSend aggregates several already-committed packets into a single batch
307// commitment.
308func (c *coreV1) BatchSend(_ int, rlm realm, msg types.MsgBatchSend) {
309 assertIsRlmCurrent(0, rlm)
310
311 if len(msg.Packets) < 2 {
312 panic(makeError(core.ErrNotEnoughPackets))
313 }
314
315 channelId := msg.Packets[0].SourceChannelId
316 batchHash := core.CommitPacketsHash(msg.Packets)
317
318 for _, packet := range msg.Packets {
319 if packet.SourceChannelId != channelId {
320 panic(makeError(core.ErrBatchSameChannelOnly))
321 }
322
323 commitment, ok := c.store.PacketCommitment(channelId, packet)
324 if !ok || commitment != types.COMMITMENT_MAGIC {
325 panic(makeError(core.ErrPacketCommitmentNotFound))
326 }
327
328 packetHash := core.CommitPacketsHash([]types.Packet{packet})
329 core.EmitBatchSend(0, rlm, channelId, packetHash, batchHash)
330 }
331
332 c.store.SaveBatchPackets(0, rlm, channelId, batchHash, types.COMMITMENT_MAGIC)
333}
334
335// BatchAcks aggregates the acks of several received packets into a batch receipt.
336func (c *coreV1) BatchAcks(_ int, rlm realm, msg types.MsgBatchAcks) {
337 assertIsRlmCurrent(0, rlm)
338
339 if len(msg.Packets) < 2 {
340 panic(makeError(core.ErrNotEnoughPackets))
341 }
342
343 channelId := msg.Packets[0].DestinationChannelId
344 batchHash := core.CommitPacketsHash(msg.Packets)
345
346 for i, ack := range msg.Acks {
347 if i >= len(msg.Packets) {
348 break
349 }
350
351 packet := msg.Packets[i]
352 if packet.DestinationChannelId != channelId {
353 panic(makeError(core.ErrBatchSameChannelOnly))
354 }
355
356 packetHash := core.CommitPacketsHash([]types.Packet{packet})
357 core.EmitBatchAcks(0, rlm, channelId, packetHash, batchHash)
358
359 commitment, ok := c.store.AcknowledgementReceipt(channelId, packet)
360 if !ok {
361 panic(makeError(core.ErrPacketCommitmentNotFound))
362 }
363
364 if commitment == types.COMMITMENT_MAGIC {
365 panic(makeError(core.ErrAcknowledgementEmpty))
366 }
367
368 if expected := types.CommitAcks([][]byte{ack}); commitment != expected {
369 panic(makeError(core.ErrAcknowledgementMismatch))
370 }
371 }
372
373 c.store.SaveBatchReceipts(0, rlm, channelId, batchHash, types.CommitAcks(msg.Acks))
374}
375
376// WriteAcknowledgement commits a (previously deferred) acknowledgement for a
377// received packet. Only the channel's port owner may write it.
378func (c *coreV1) WriteAcknowledgement(_ int, rlm realm, msg types.MsgWriteAcknowledgement) {
379 assertIsRlmCurrent(0, rlm)
380
381 if len(msg.Acknowledgement) == 0 {
382 panic(makeError(core.ErrAcknowledgementEmpty))
383 }
384
385 channelId := msg.Packet.DestinationChannelId
386 portId, ok := c.store.GetChannelOwner(channelId)
387
388 if !ok {
389 panic(makeError(core.ErrChannelNotFound, channelId.String()))
390 }
391
392 if string(portId) != rlm.Previous().PkgPath() {
393 panic(makeError(core.ErrUnauthorizedAckWriter))
394 }
395
396 commitment, ok := c.store.AcknowledgementReceipt(channelId, msg.Packet)
397 if !ok {
398 panic(makeError(core.ErrPacketReceiptNotFound))
399 }
400
401 if commitment != types.COMMITMENT_MAGIC {
402 panic(makeError(core.ErrAcknowledgementAlreadyWritten))
403 }
404
405 channel, err := c.getChannel(channelId)
406 if err != nil {
407 panic(err)
408 }
409 connection := c.ensureConnectionState(channel.ConnectionId)
410
411 c.store.CommitAcknowledgement(0, rlm, msg.Packet, msg.Acknowledgement)
412
413 core.EmitWriteAck(0, rlm, msg.Packet, channel, connection, msg.Acknowledgement)
414}
415
416// --- shared recv helpers ---
417
418// acceptRecvPacket validates recv-path invariants and records a packet receipt,
419// returning skip=true for an already-received (duplicate) packet.
420func (c *coreV1) acceptRecvPacket(_ int, rlm realm, packet types.Packet, batchChannelId types.ChannelId, now uint64) bool {
421 if packet.DestinationChannelId != batchChannelId {
422 panic(makeError(core.ErrBatchSameChannelOnly))
423 }
424
425 channel, err := c.getChannel(packet.DestinationChannelId)
426 if err != nil {
427 panic(err)
428 }
429
430 if channel.State != types.ChannelStateOpen {
431 panic(makeError(core.ErrInvalidChannelState))
432 }
433
434 if now >= uint64(packet.TimeoutTimestamp) {
435 panic(makeError(core.ErrPacketTimeoutExpired))
436 }
437
438 if c.store.HasPacketReceipt(packet) {
439 return true
440 }
441
442 c.store.SetPacketReceipt(0, rlm, packet)
443
444 return false
445}
446
447// commitSyncAck records a synchronous recv result as receipt evidence. Sync
448// success/failure must carry a non-empty acknowledgement; async is deferred.
449func (c *coreV1) commitSyncAck(_ int, rlm realm, packet types.Packet, channel types.Channel, connection types.Connection, res types.RecvPacketResult) error {
450 switch res.Status {
451 case types.PacketStatusAsync:
452 return nil
453 case types.PacketStatusSuccess, types.PacketStatusFailure:
454 if len(res.Acknowledgement) == 0 {
455 return makeError(core.ErrSyncAckEmpty)
456 }
457
458 c.store.CommitAcknowledgement(0, rlm, packet, res.Acknowledgement)
459 core.EmitWriteAck(0, rlm, packet, channel, connection, res.Acknowledgement)
460
461 return nil
462 default:
463 return makeError(core.ErrUnknownPacketStatus)
464 }
465}
466
467// verifyNonMembership checks a non-membership proof against an active light client.
468func (c *coreV1) verifyNonMembership(clientId types.ClientId, height types.Height, proof, key []byte) {
469 lc, err := c.getActiveClient(clientId)
470 if err != nil {
471 panic(err)
472 }
473
474 if err := lc.VerifyNonMembership(height.RevisionHeight, key, proof); err != nil {
475 panic(err)
476 }
477}
478
479// getAppAtPort returns the app registered for portId, or an error when no app
480// is registered. It centralizes the port -> app lookup the recv and handshake
481// callback paths share; the caller (entrypoint) decides whether a miss reverts.
482func (c *coreV1) getAppAtPort(portId types.Bytes) (app.IApp, error) {
483 a, ok := c.store.GetAppByPortId(portId)
484 if !ok {
485 return nil, makeError(core.ErrPortNotFound, string(portId))
486 }
487
488 return a, nil
489}