Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}