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}