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}