Search Apps Documentation Source Content File Folder Download Copy Actions Download

core.gno

15.64 Kb · 464 lines
  1package core
  2
  3import (
  4	"bytes"
  5	"chain"
  6	"encoding/hex"
  7	"time"
  8
  9	"gno.land/p/aib/ibc/host"
 10	"gno.land/p/aib/ibc/lightclient"
 11	"gno.land/p/aib/ibc/types"
 12	"gno.land/p/nt/ufmt/v0"
 13)
 14
 15const maxTimeoutDelta time.Duration = 24 * time.Hour
 16
 17func SendPacket(cur realm, msg types.MsgSendPacket) (sequence uint64) {
 18	if err := msg.ValidateBasic(); err != nil {
 19		panic(err)
 20	}
 21	clientID := msg.SourceClient
 22	c := store.getClient(clientID)
 23	if c == nil {
 24		panic(ufmt.Sprintf("client %s not found", clientID))
 25	}
 26	if status := c.lightClient.Status(); status != lightclient.Active {
 27		panic(ufmt.Sprintf("client (%s) status is %s", clientID, status))
 28	}
 29
 30	// Ensure counterparty has been registered
 31	if c.counterpartyClientID == "" {
 32		panic(ufmt.Sprintf("counterparty not found for client %s", clientID))
 33	}
 34
 35	// timeoutTimestamp must be greater than current block time and less or equal
 36	// to current block time + maxTimeoutDelta.
 37	var (
 38		timeout    = time.Unix(int64(msg.TimeoutTimestamp), 0)
 39		minTimeout = time.Now()
 40		maxTimeout = minTimeout.Add(maxTimeoutDelta)
 41	)
 42	if !timeout.After(minTimeout) {
 43		panic(ufmt.Sprintf(
 44			"timeout is less than or equal the current block timestamp, %d <= %d",
 45			msg.TimeoutTimestamp, minTimeout.Unix(),
 46		))
 47	}
 48	if timeout.After(maxTimeout) {
 49		panic(ufmt.Sprintf(
 50			"timeout is after the max allowed timeout, %d > %d",
 51			msg.TimeoutTimestamp, maxTimeout.Unix(),
 52		))
 53	}
 54
 55	// check if the latest consensus timestamp is lower than timeoutTimestamp
 56	latestTimestamp, err := c.lightClient.TimestampAtHeight(c.lightClient.LatestHeight())
 57	if err != nil {
 58		panic(err)
 59	}
 60	if latestTimestamp >= msg.TimeoutTimestamp {
 61		panic(ufmt.Errorf("latest timestamp: %d, timeout timestamp: %d",
 62			latestTimestamp, msg.TimeoutTimestamp))
 63	}
 64
 65	// construct packet from given fields
 66	sequence = uint64(c.sendSeq.Next())
 67	packet := types.NewPacket(sequence, msg.SourceClient, c.counterpartyClientID, msg.TimeoutTimestamp, msg.Payloads...)
 68	if err := packet.ValidateBasic(); err != nil {
 69		panic(ufmt.Errorf("constructed packet failed basic validation: %v", err))
 70	}
 71
 72	// set the packet commitment
 73	c.setPacketCommitment(sequence, packet)
 74
 75	// emit events
 76	chain.Emit(types.EventTypeSendPacket,
 77		types.AttributeKeySrcClient, packet.SourceClient,
 78		types.AttributeKeyDstClient, packet.DestinationClient,
 79		types.AttributeKeySequence, ufmt.Sprintf("%d", packet.Sequence),
 80		types.AttributeKeyTimeoutTimestamp, ufmt.Sprintf("%d", packet.TimeoutTimestamp),
 81		types.AttributeKeyEncodedPacketHex, hex.EncodeToString(packet.ProtoMarshal()),
 82	)
 83
 84	// Invoke registed app OnSendPacket() for each payload.SourcePort.
 85	for i, payload := range msg.Payloads {
 86		app := store.route(payload.SourcePort)
 87		err := app.OnSendPacket(
 88			cross(cur), msg.SourceClient, c.counterpartyClientID, sequence, payload,
 89		)
 90		if err != nil {
 91			panic(ufmt.Sprintf(
 92				"send packet failed for payload #%d app %q: %v", i, payload.SourcePort, err,
 93			))
 94		}
 95	}
 96
 97	return sequence
 98}
 99
100func RecvPacket(cur realm, msg types.MsgRecvPacket) types.ResponseResultType {
101	if err := msg.ValidateBasic(); err != nil {
102		panic(err)
103	}
104
105	clientID := msg.Packet.DestinationClient
106	c := store.getClient(clientID)
107	if c == nil {
108		panic(ufmt.Sprintf("client %s not found", clientID))
109	}
110	ensureAuthorizedRelayer()
111	// check client is active
112	if status := c.lightClient.Status(); status != lightclient.Active {
113		panic(ufmt.Sprintf("client (%s) status is %s", clientID, status))
114	}
115	// check counterparty
116	if c.counterpartyClientID != msg.Packet.SourceClient {
117		panic(ufmt.Sprintf(
118			"counterparty id (%s) does not match packet source id (%s)",
119			c.counterpartyClientID, msg.Packet.SourceClient,
120		))
121	}
122	currentTimestamp := uint64(time.Now().Unix())
123	if currentTimestamp >= msg.Packet.TimeoutTimestamp {
124		panic(ufmt.Sprintf(
125			"current timestamp: %d, timeout timestamp: %d",
126			currentTimestamp, msg.Packet.TimeoutTimestamp,
127		))
128	}
129
130	// REPLAY PROTECTION: Packet receipts will indicate that a packet has already
131	// been received.
132	// Packet receipts must not be pruned, unless it has been marked stale by the
133	// increase of the recvStartSequence. TODO check relevancy of comment
134	if c.hasPacketReceipt(msg.Packet.Sequence) {
135		// This error indicates that the packet has already been relayed. Core IBC
136		// will treat this error as a no-op in order to prevent an entire relay
137		// transaction from failing and consuming unnecessary fees.
138		return types.RESPONSE_NOOP
139	}
140
141	// Verify existence of the commitment bytes in the proofs.
142	var (
143		key        = host.PacketCommitmentKey(msg.Packet.SourceClient, msg.Packet.Sequence)
144		merklePath = types.BuildMerklePath(c.counterpartyMerklePrefix, key)
145		value      = types.CommitPacket(msg.Packet)
146	)
147	if err := c.lightClient.VerifyMembership(
148		msg.ProofHeight, msg.ProofCommitment, merklePath, value,
149	); err != nil {
150		panic(ufmt.Sprintf(
151			"failed packet commitment verification for client (%s): %v",
152			clientID, err,
153		))
154	}
155
156	// Set Packet Receipt to prevent timeout from occurring on counterparty
157	c.setPacketReceipt(msg.Packet.Sequence)
158
159	// Emit events
160	chain.Emit(types.EventTypeRecvPacket,
161		types.AttributeKeySrcClient, msg.Packet.SourceClient,
162		types.AttributeKeyDstClient, msg.Packet.DestinationClient,
163		types.AttributeKeySequence, ufmt.Sprintf("%d", msg.Packet.Sequence),
164		types.AttributeKeyTimeoutTimestamp, ufmt.Sprintf("%d", msg.Packet.TimeoutTimestamp),
165		types.AttributeKeyEncodedPacketHex, hex.EncodeToString(msg.Packet.ProtoMarshal()),
166	)
167
168	c.writeRecvPacketAcknowledgement(0, cur, msg.Packet)
169
170	return types.RESPONSE_SUCCESS
171}
172
173// writeRecvPacketAcknowledgement is a non-crossing helper: rlm is the caller's
174// live cur, threaded as a non-first param via the `_ int, rlm realm` shape so
175// the cross to each app's OnRecvPacket can be issued from RecvPacket's frame.
176func (c *client) writeRecvPacketAcknowledgement(_ int, rlm realm, packet types.Packet) {
177	var (
178		// build up the recv results for each application callback.
179		ack = types.Acknowledgement{
180			AppAcknowledgements: [][]byte{},
181		}
182		isSuccess = true
183	)
184
185	// Invoke registed app OnRecvPacket() for each payload.DestinationPort.
186	for i, payload := range packet.Payloads {
187		app := store.route(payload.DestinationPort)
188		res := app.OnRecvPacket(
189			cross(rlm), packet.SourceClient, packet.DestinationClient,
190			packet.Sequence, payload,
191		)
192		if res.Status == types.PacketStatus_Failure {
193			isSuccess = false
194			// construct acknowledgement with single app acknowledgement that is the
195			// sentinel error acknowledgement
196			ack = types.Acknowledgement{
197				AppAcknowledgements: [][]byte{types.UniversalErrorAcknowledgement()},
198			}
199			break
200		}
201		if res.Status == types.PacketStatus_Async {
202			if len(packet.Payloads) > 1 {
203				panic("async ack not supported for multi-payload packets")
204			}
205			c.savePendingAsyncAck(packet, app.pkgPath)
206			return
207		}
208
209		// successful app acknowledgement cannot equal sentinel error
210		// acknowledgement
211		if bytes.Equal(res.Acknowledgement, types.UniversalErrorAcknowledgement()) {
212			panic(ufmt.Sprintf(
213				"callback error for payload #%d app %q: application acknowledgement cannot be sentinel error acknowledgement",
214				i, payload.DestinationPort,
215			))
216		}
217
218		// append app acknowledgement to the overall acknowledgement
219		ack.AppAcknowledgements = append(ack.AppAcknowledgements, res.Acknowledgement)
220	}
221
222	// Sanity check to ensure returned acknowledgement and calculated isSuccess
223	// boolean matches
224	if ack.Success() != isSuccess {
225		panic("acknowledgement success flag mismatch")
226	}
227	if err := ack.Validate(); err != nil {
228		panic(err)
229	}
230	// set the acknowledgement so that it can be verified on the other side
231	c.setPacketAcknowledgement(packet.Sequence, types.CommitAcknowledgement(ack))
232	emitWriteAcknowledgement(packet, ack)
233}
234
235func WriteAcknowledgement(cur realm, clientID string, sequence uint64, ack types.Acknowledgement) {
236	c := store.getClient(clientID)
237	if c == nil {
238		panic(ufmt.Sprintf("client %s not found", clientID))
239	}
240
241	pending, found := c.getPendingAsyncAck(sequence)
242	if !found {
243		panic(ufmt.Sprintf("no pending async ack for client=%s sequence=%d", clientID, sequence))
244	}
245
246	callerPath := cur.Previous().PkgPath()
247	if callerPath != pending.appPkgPath {
248		panic(ufmt.Sprintf(
249			"caller %s is not authorized to write ack for sequence %d (expected %s)",
250			callerPath, sequence, pending.appPkgPath,
251		))
252	}
253
254	if c.hasPacketAcknowledgement(sequence) {
255		panic(ufmt.Sprintf("acknowledgement already written for sequence %d", sequence))
256	}
257	if err := ack.Validate(); err != nil {
258		panic(err)
259	}
260
261	c.setPacketAcknowledgement(sequence, types.CommitAcknowledgement(ack))
262	c.deletePendingAsyncAck(sequence)
263	emitWriteAcknowledgement(pending.packet, ack)
264}
265
266func emitWriteAcknowledgement(packet types.Packet, ack types.Acknowledgement) {
267	chain.Emit(types.EventTypeWriteAck,
268		types.AttributeKeySrcClient, packet.SourceClient,
269		types.AttributeKeyDstClient, packet.DestinationClient,
270		types.AttributeKeySequence, ufmt.Sprintf("%d", packet.Sequence),
271		types.AttributeKeyTimeoutTimestamp, ufmt.Sprintf("%d", packet.TimeoutTimestamp),
272		types.AttributeKeyEncodedPacketHex, hex.EncodeToString(packet.ProtoMarshal()),
273		types.AttributeKeyEncodedAckHex, hex.EncodeToString(ack.ProtoMarshal()),
274	)
275}
276
277func Acknowledgement(cur realm, msg types.MsgAcknowledgement) types.ResponseResultType {
278	if err := msg.ValidateBasic(); err != nil {
279		panic(err)
280	}
281
282	clientID := msg.Packet.SourceClient
283	c := store.getClient(clientID)
284	if c == nil {
285		panic(ufmt.Sprintf("client %s not found", clientID))
286	}
287	ensureAuthorizedRelayer()
288	// check client is active
289	if status := c.lightClient.Status(); status != lightclient.Active {
290		panic(ufmt.Sprintf("client (%s) status is %s", clientID, status))
291	}
292	// check counterparty
293	if c.counterpartyClientID != msg.Packet.DestinationClient {
294		panic(ufmt.Sprintf(
295			"counterparty id (%s) does not match packet destination id (%s)",
296			c.counterpartyClientID, msg.Packet.DestinationClient,
297		))
298	}
299
300	commitment := c.getPacketCommitment(msg.Packet.Sequence)
301	if len(commitment) == 0 {
302		// This error indicates that the acknowledgement has already been relayed
303		// or there is a misconfigured relayer attempting to prove an
304		// acknowledgement for a packet never sent. Core IBC will treat this error
305		// as a no-op in order to prevent an entire relay transaction from failing
306		// and consuming unnecessary fees.
307		return types.RESPONSE_NOOP
308	}
309	packetCommitment := types.CommitPacket(msg.Packet)
310	// ensure integrity of commitment
311	if !bytes.Equal(commitment, packetCommitment) {
312		h1, h2 := hex.EncodeToString(packetCommitment), hex.EncodeToString(commitment)
313		panic(ufmt.Sprintf(
314			"commitment bytes are not equal: got (%v), expected (%v)", h1, h2,
315		))
316	}
317
318	// Verify existence of the acknowledgement commitment bytes in the proofs.
319	var (
320		key        = host.PacketAcknowledgementKey(msg.Packet.DestinationClient, msg.Packet.Sequence)
321		merklePath = types.BuildMerklePath(c.counterpartyMerklePrefix, key)
322		value      = types.CommitAcknowledgement(msg.Acknowledgement)
323	)
324	if err := c.lightClient.VerifyMembership(
325		msg.ProofHeight, msg.ProofAcked, merklePath, value,
326	); err != nil {
327		panic(ufmt.Sprintf(
328			"failed packet acknowledgement verification for client (%s): %v",
329			clientID, err,
330		))
331	}
332
333	c.deletePacketCommitment(msg.Packet.Sequence)
334
335	chain.Emit(types.EventTypeAcknowledgePacket,
336		types.AttributeKeySrcClient, msg.Packet.SourceClient,
337		types.AttributeKeyDstClient, msg.Packet.DestinationClient,
338		types.AttributeKeySequence, ufmt.Sprintf("%d", msg.Packet.Sequence),
339		types.AttributeKeyTimeoutTimestamp, ufmt.Sprintf("%d", msg.Packet.TimeoutTimestamp),
340		types.AttributeKeyEncodedPacketHex, hex.EncodeToString(msg.Packet.ProtoMarshal()),
341	)
342
343	// Invoke registed app OnAcknowledgementPacket() for each payload.SourcePort.
344	recvSuccess := !bytes.Equal(msg.Acknowledgement.AppAcknowledgements[0],
345		types.UniversalErrorAcknowledgement())
346	for i, payload := range msg.Packet.Payloads {
347		app := store.route(payload.SourcePort)
348		// if recv was successful, each payload should have its own acknowledgement
349		// so we send each individual acknowledgment to the application otherwise,
350		// the acknowledgement only contains the sentinel error acknowledgement
351		// which we send to the application. The application is responsible for
352		// knowing that this is an error acknowledgement and executing the
353		// appropriate logic.
354		var ack []byte
355		if recvSuccess {
356			ack = msg.Acknowledgement.AppAcknowledgements[i]
357		} else {
358			ack = types.UniversalErrorAcknowledgement()
359		}
360		err := app.OnAcknowledgementPacket(
361			cross(cur), msg.Packet.SourceClient, msg.Packet.DestinationClient,
362			msg.Packet.Sequence, ack, payload,
363		)
364		if err != nil {
365			panic(ufmt.Sprintf(
366				"acknowledgement packet failed for payload #%d app %q: %v",
367				i, payload.SourcePort, err,
368			))
369		}
370	}
371
372	return types.RESPONSE_SUCCESS
373}
374
375func Timeout(cur realm, msg types.MsgTimeout) types.ResponseResultType {
376	if err := msg.ValidateBasic(); err != nil {
377		panic(err)
378	}
379
380	clientID := msg.Packet.SourceClient
381	c := store.getClient(clientID)
382	if c == nil {
383		panic(ufmt.Sprintf("client %s not found", clientID))
384	}
385	ensureAuthorizedRelayer()
386	// check client is active
387	if status := c.lightClient.Status(); status != lightclient.Active {
388		panic(ufmt.Sprintf("client (%s) status is %s", clientID, status))
389	}
390	// check counterparty
391	if c.counterpartyClientID != msg.Packet.DestinationClient {
392		panic(ufmt.Sprintf(
393			"counterparty id (%s) does not match packet destination id (%s)",
394			c.counterpartyClientID, msg.Packet.DestinationClient,
395		))
396	}
397	// check that timeout timestamp has passed on the other end
398	proofTimestamp, err := c.lightClient.TimestampAtHeight(msg.ProofHeight)
399	if err != nil {
400		panic(err)
401	}
402	if proofTimestamp < msg.Packet.TimeoutTimestamp {
403		panic(ufmt.Errorf("proof timestamp: %d, timeout timestamp: %d",
404			proofTimestamp, msg.Packet.TimeoutTimestamp))
405	}
406
407	commitment := c.getPacketCommitment(msg.Packet.Sequence)
408	if len(commitment) == 0 {
409		// This error indicates that the timeout has already been relayed or there
410		// is a misconfigured relayer attempting to prove a timeout for a packet
411		// never sent. Core IBC will treat this error as a no-op in order to
412		// prevent an entire relay transaction from failing and consuming
413		// unnecessary fees.
414		return types.RESPONSE_NOOP
415	}
416	packetCommitment := types.CommitPacket(msg.Packet)
417	// ensure integrity of commitment
418	if !bytes.Equal(commitment, packetCommitment) {
419		h1, h2 := hex.EncodeToString(packetCommitment), hex.EncodeToString(commitment)
420		panic(ufmt.Sprintf(
421			"commitment bytes are not equal: got (%v), expected (%v)", h1, h2,
422		))
423	}
424
425	// Verify packet receipt absence
426	var (
427		key        = host.PacketReceiptKey(msg.Packet.DestinationClient, msg.Packet.Sequence)
428		merklePath = types.BuildMerklePath(c.counterpartyMerklePrefix, key)
429	)
430	if err := c.lightClient.VerifyNonMembership(
431		msg.ProofHeight, msg.ProofUnreceived, merklePath,
432	); err != nil {
433		panic(ufmt.Sprintf(
434			"failed packet receipt absence verification for client (%s): %v",
435			clientID, err,
436		))
437	}
438
439	c.deletePacketCommitment(msg.Packet.Sequence)
440
441	chain.Emit(types.EventTypeTimeoutPacket,
442		types.AttributeKeySrcClient, msg.Packet.SourceClient,
443		types.AttributeKeyDstClient, msg.Packet.DestinationClient,
444		types.AttributeKeySequence, ufmt.Sprintf("%d", msg.Packet.Sequence),
445		types.AttributeKeyTimeoutTimestamp, ufmt.Sprintf("%d", msg.Packet.TimeoutTimestamp),
446		types.AttributeKeyEncodedPacketHex, hex.EncodeToString(msg.Packet.ProtoMarshal()),
447	)
448
449	for i, payload := range msg.Packet.Payloads {
450		app := store.route(payload.SourcePort)
451		err := app.OnTimeoutPacket(
452			cross(cur), msg.Packet.SourceClient, msg.Packet.DestinationClient,
453			msg.Packet.Sequence, payload,
454		)
455		if err != nil {
456			panic(ufmt.Sprintf(
457				"timeout packet failed for payload #%d app %q: %v",
458				i, payload.SourcePort, err,
459			))
460		}
461	}
462
463	return types.RESPONSE_SUCCESS
464}