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

core.gno

16.07 Kb · 471 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	//
187	// Packet.ValidateBasic() guarantees exactly one payload, so the loop never
188	// runs more than once. It matters: the loop breaks on the first failure
189	// without reverting the callbacks that already succeeded, so with several
190	// payloads the packet could commit a partial state change while being
191	// acknowledged (and refunded by the counterparty) as failed. Any move to
192	// multi-payload packets has to make this loop atomic first.
193	for i, payload := range packet.Payloads {
194		app := store.route(payload.DestinationPort)
195		res := app.OnRecvPacket(
196			cross(rlm), packet.SourceClient, packet.DestinationClient,
197			packet.Sequence, payload,
198		)
199		if res.Status == types.PacketStatus_Failure {
200			isSuccess = false
201			// construct acknowledgement with single app acknowledgement that is the
202			// sentinel error acknowledgement
203			ack = types.Acknowledgement{
204				AppAcknowledgements: [][]byte{types.UniversalErrorAcknowledgement()},
205			}
206			break
207		}
208		if res.Status == types.PacketStatus_Async {
209			if len(packet.Payloads) > 1 {
210				panic("async ack not supported for multi-payload packets")
211			}
212			c.savePendingAsyncAck(packet, app.pkgPath)
213			return
214		}
215
216		// successful app acknowledgement cannot equal sentinel error
217		// acknowledgement
218		if bytes.Equal(res.Acknowledgement, types.UniversalErrorAcknowledgement()) {
219			panic(ufmt.Sprintf(
220				"callback error for payload #%d app %q: application acknowledgement cannot be sentinel error acknowledgement",
221				i, payload.DestinationPort,
222			))
223		}
224
225		// append app acknowledgement to the overall acknowledgement
226		ack.AppAcknowledgements = append(ack.AppAcknowledgements, res.Acknowledgement)
227	}
228
229	// Sanity check to ensure returned acknowledgement and calculated isSuccess
230	// boolean matches
231	if ack.Success() != isSuccess {
232		panic("acknowledgement success flag mismatch")
233	}
234	if err := ack.Validate(); err != nil {
235		panic(err)
236	}
237	// set the acknowledgement so that it can be verified on the other side
238	c.setPacketAcknowledgement(packet.Sequence, types.CommitAcknowledgement(ack))
239	emitWriteAcknowledgement(packet, ack)
240}
241
242func WriteAcknowledgement(cur realm, clientID string, sequence uint64, ack types.Acknowledgement) {
243	c := store.getClient(clientID)
244	if c == nil {
245		panic(ufmt.Sprintf("client %s not found", clientID))
246	}
247
248	pending, found := c.getPendingAsyncAck(sequence)
249	if !found {
250		panic(ufmt.Sprintf("no pending async ack for client=%s sequence=%d", clientID, sequence))
251	}
252
253	callerPath := cur.Previous().PkgPath()
254	if callerPath != pending.appPkgPath {
255		panic(ufmt.Sprintf(
256			"caller %s is not authorized to write ack for sequence %d (expected %s)",
257			callerPath, sequence, pending.appPkgPath,
258		))
259	}
260
261	if c.hasPacketAcknowledgement(sequence) {
262		panic(ufmt.Sprintf("acknowledgement already written for sequence %d", sequence))
263	}
264	if err := ack.Validate(); err != nil {
265		panic(err)
266	}
267
268	c.setPacketAcknowledgement(sequence, types.CommitAcknowledgement(ack))
269	c.deletePendingAsyncAck(sequence)
270	emitWriteAcknowledgement(pending.packet, ack)
271}
272
273func emitWriteAcknowledgement(packet types.Packet, ack types.Acknowledgement) {
274	chain.Emit(types.EventTypeWriteAck,
275		types.AttributeKeySrcClient, packet.SourceClient,
276		types.AttributeKeyDstClient, packet.DestinationClient,
277		types.AttributeKeySequence, ufmt.Sprintf("%d", packet.Sequence),
278		types.AttributeKeyTimeoutTimestamp, ufmt.Sprintf("%d", packet.TimeoutTimestamp),
279		types.AttributeKeyEncodedPacketHex, hex.EncodeToString(packet.ProtoMarshal()),
280		types.AttributeKeyEncodedAckHex, hex.EncodeToString(ack.ProtoMarshal()),
281	)
282}
283
284func Acknowledgement(cur realm, msg types.MsgAcknowledgement) types.ResponseResultType {
285	if err := msg.ValidateBasic(); err != nil {
286		panic(err)
287	}
288
289	clientID := msg.Packet.SourceClient
290	c := store.getClient(clientID)
291	if c == nil {
292		panic(ufmt.Sprintf("client %s not found", clientID))
293	}
294	ensureAuthorizedRelayer()
295	// check client is active
296	if status := c.lightClient.Status(); status != lightclient.Active {
297		panic(ufmt.Sprintf("client (%s) status is %s", clientID, status))
298	}
299	// check counterparty
300	if c.counterpartyClientID != msg.Packet.DestinationClient {
301		panic(ufmt.Sprintf(
302			"counterparty id (%s) does not match packet destination id (%s)",
303			c.counterpartyClientID, msg.Packet.DestinationClient,
304		))
305	}
306
307	commitment := c.getPacketCommitment(msg.Packet.Sequence)
308	if len(commitment) == 0 {
309		// This error indicates that the acknowledgement has already been relayed
310		// or there is a misconfigured relayer attempting to prove an
311		// acknowledgement for a packet never sent. Core IBC will treat this error
312		// as a no-op in order to prevent an entire relay transaction from failing
313		// and consuming unnecessary fees.
314		return types.RESPONSE_NOOP
315	}
316	packetCommitment := types.CommitPacket(msg.Packet)
317	// ensure integrity of commitment
318	if !bytes.Equal(commitment, packetCommitment) {
319		h1, h2 := hex.EncodeToString(packetCommitment), hex.EncodeToString(commitment)
320		panic(ufmt.Sprintf(
321			"commitment bytes are not equal: got (%v), expected (%v)", h1, h2,
322		))
323	}
324
325	// Verify existence of the acknowledgement commitment bytes in the proofs.
326	var (
327		key        = host.PacketAcknowledgementKey(msg.Packet.DestinationClient, msg.Packet.Sequence)
328		merklePath = types.BuildMerklePath(c.counterpartyMerklePrefix, key)
329		value      = types.CommitAcknowledgement(msg.Acknowledgement)
330	)
331	if err := c.lightClient.VerifyMembership(
332		msg.ProofHeight, msg.ProofAcked, merklePath, value,
333	); err != nil {
334		panic(ufmt.Sprintf(
335			"failed packet acknowledgement verification for client (%s): %v",
336			clientID, err,
337		))
338	}
339
340	c.deletePacketCommitment(msg.Packet.Sequence)
341
342	chain.Emit(types.EventTypeAcknowledgePacket,
343		types.AttributeKeySrcClient, msg.Packet.SourceClient,
344		types.AttributeKeyDstClient, msg.Packet.DestinationClient,
345		types.AttributeKeySequence, ufmt.Sprintf("%d", msg.Packet.Sequence),
346		types.AttributeKeyTimeoutTimestamp, ufmt.Sprintf("%d", msg.Packet.TimeoutTimestamp),
347		types.AttributeKeyEncodedPacketHex, hex.EncodeToString(msg.Packet.ProtoMarshal()),
348	)
349
350	// Invoke registed app OnAcknowledgementPacket() for each payload.SourcePort.
351	recvSuccess := !bytes.Equal(msg.Acknowledgement.AppAcknowledgements[0],
352		types.UniversalErrorAcknowledgement())
353	for i, payload := range msg.Packet.Payloads {
354		app := store.route(payload.SourcePort)
355		// if recv was successful, each payload should have its own acknowledgement
356		// so we send each individual acknowledgment to the application otherwise,
357		// the acknowledgement only contains the sentinel error acknowledgement
358		// which we send to the application. The application is responsible for
359		// knowing that this is an error acknowledgement and executing the
360		// appropriate logic.
361		var ack []byte
362		if recvSuccess {
363			ack = msg.Acknowledgement.AppAcknowledgements[i]
364		} else {
365			ack = types.UniversalErrorAcknowledgement()
366		}
367		err := app.OnAcknowledgementPacket(
368			cross(cur), msg.Packet.SourceClient, msg.Packet.DestinationClient,
369			msg.Packet.Sequence, ack, payload,
370		)
371		if err != nil {
372			panic(ufmt.Sprintf(
373				"acknowledgement packet failed for payload #%d app %q: %v",
374				i, payload.SourcePort, err,
375			))
376		}
377	}
378
379	return types.RESPONSE_SUCCESS
380}
381
382func Timeout(cur realm, msg types.MsgTimeout) types.ResponseResultType {
383	if err := msg.ValidateBasic(); err != nil {
384		panic(err)
385	}
386
387	clientID := msg.Packet.SourceClient
388	c := store.getClient(clientID)
389	if c == nil {
390		panic(ufmt.Sprintf("client %s not found", clientID))
391	}
392	ensureAuthorizedRelayer()
393	// check client is active
394	if status := c.lightClient.Status(); status != lightclient.Active {
395		panic(ufmt.Sprintf("client (%s) status is %s", clientID, status))
396	}
397	// check counterparty
398	if c.counterpartyClientID != msg.Packet.DestinationClient {
399		panic(ufmt.Sprintf(
400			"counterparty id (%s) does not match packet destination id (%s)",
401			c.counterpartyClientID, msg.Packet.DestinationClient,
402		))
403	}
404	// check that timeout timestamp has passed on the other end
405	proofTimestamp, err := c.lightClient.TimestampAtHeight(msg.ProofHeight)
406	if err != nil {
407		panic(err)
408	}
409	if proofTimestamp < msg.Packet.TimeoutTimestamp {
410		panic(ufmt.Errorf("proof timestamp: %d, timeout timestamp: %d",
411			proofTimestamp, msg.Packet.TimeoutTimestamp))
412	}
413
414	commitment := c.getPacketCommitment(msg.Packet.Sequence)
415	if len(commitment) == 0 {
416		// This error indicates that the timeout has already been relayed or there
417		// is a misconfigured relayer attempting to prove a timeout for a packet
418		// never sent. Core IBC will treat this error as a no-op in order to
419		// prevent an entire relay transaction from failing and consuming
420		// unnecessary fees.
421		return types.RESPONSE_NOOP
422	}
423	packetCommitment := types.CommitPacket(msg.Packet)
424	// ensure integrity of commitment
425	if !bytes.Equal(commitment, packetCommitment) {
426		h1, h2 := hex.EncodeToString(packetCommitment), hex.EncodeToString(commitment)
427		panic(ufmt.Sprintf(
428			"commitment bytes are not equal: got (%v), expected (%v)", h1, h2,
429		))
430	}
431
432	// Verify packet receipt absence
433	var (
434		key        = host.PacketReceiptKey(msg.Packet.DestinationClient, msg.Packet.Sequence)
435		merklePath = types.BuildMerklePath(c.counterpartyMerklePrefix, key)
436	)
437	if err := c.lightClient.VerifyNonMembership(
438		msg.ProofHeight, msg.ProofUnreceived, merklePath,
439	); err != nil {
440		panic(ufmt.Sprintf(
441			"failed packet receipt absence verification for client (%s): %v",
442			clientID, err,
443		))
444	}
445
446	c.deletePacketCommitment(msg.Packet.Sequence)
447
448	chain.Emit(types.EventTypeTimeoutPacket,
449		types.AttributeKeySrcClient, msg.Packet.SourceClient,
450		types.AttributeKeyDstClient, msg.Packet.DestinationClient,
451		types.AttributeKeySequence, ufmt.Sprintf("%d", msg.Packet.Sequence),
452		types.AttributeKeyTimeoutTimestamp, ufmt.Sprintf("%d", msg.Packet.TimeoutTimestamp),
453		types.AttributeKeyEncodedPacketHex, hex.EncodeToString(msg.Packet.ProtoMarshal()),
454	)
455
456	for i, payload := range msg.Packet.Payloads {
457		app := store.route(payload.SourcePort)
458		err := app.OnTimeoutPacket(
459			cross(cur), msg.Packet.SourceClient, msg.Packet.DestinationClient,
460			msg.Packet.Sequence, payload,
461		)
462		if err != nil {
463			panic(ufmt.Sprintf(
464				"timeout packet failed for payload #%d app %q: %v",
465				i, payload.SourcePort, err,
466			))
467		}
468	}
469
470	return types.RESPONSE_SUCCESS
471}