Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 12c00ec

Browse files
colin-axnerdamiannolan
andauthoredMar 22, 2023
imp: address ADR 8 review suggestions (#3319)
--------- Co-authored-by: Damian Nolan <damiannolan@gmail.com>
1 parent 7d23e0f commit 12c00ec

File tree

2 files changed

+119
-73
lines changed

2 files changed

+119
-73
lines changed
 

‎docs/architecture/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ To suggest an ADR, please make use of the [ADR template](./adr-template.md) prov
3131
| [002](./adr-002-go-module-versioning.md) | Go module versioning | Accepted |
3232
| [003](./adr-003-ics27-acknowledgement.md) | ICS27 acknowledgement format | Accepted |
3333
| [004](./adr-004-ics29-lock-fee-module.md) | ICS29 module locking upon escrow out of balance | Accepted |
34+
| [008](./adr-008-app-caller-cbs/adr-008-app-caller-cbs.md) | Callback to IBC ACtors | Accepted |
3435
| [015](./adr-015-ibc-packet-receiver.md) | IBC Packet Routing | Accepted |
3536
| [025](./adr-025-ibc-passive-channels.md) | IBC passive channels | Deprecated |
3637
| [026](./adr-026-ibc-client-recovery-mechanisms.md) | IBC client recovery mechansisms | Accepted |

‎docs/architecture/adr-008-app-caller-cbs/adr-008-app-caller-cbs.md

+118-73
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# ADR 008: Callback to IBC Actors
22

33
## Changelog
4+
45
* 2022-08-10: Initial Draft
6+
* 2023-03-22: Merged
57

68
## Status
79

8-
Proposed
10+
Accepted, packet callback interface implemented
911

1012
## Context
1113

@@ -51,40 +53,47 @@ type IBCActor interface {
5153
type PacketActor interface {
5254
// OnRecvPacket will be called on the IBCActor after the IBC Application
5355
// handles the RecvPacket callback if the packet has an IBC Actor as a receiver.
54-
OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer string) error
56+
OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error
5557

5658
// OnAcknowledgementPacket will be called on the IBC Actor
5759
// after the IBC Application handles its own OnAcknowledgementPacket callback
5860
OnAcknowledgmentPacket(
5961
ctx sdk.Context,
6062
packet channeltypes.Packet,
6163
ack exported.Acknowledgement,
62-
relayer string
64+
relayer sdk.AccAddress,
6365
) error
6466

6567
// OnTimeoutPacket will be called on the IBC Actor
6668
// after the IBC Application handles its own OnTimeoutPacket callback
6769
OnTimeoutPacket(
6870
ctx sdk.Context,
6971
packet channeltypes.Packet,
70-
relayer string
72+
relayer sdk.AccAddress,
7173
) error
7274
}
7375
```
7476

75-
The CallbackPacketData interface will get created to add `GetSrcCallbackAddress` and `GetDestCallbackAddress` methods. These may return an address
76-
or they may return the empty string. The address may reference an IBCActor or it may be a regular user address. If the address is not an IBCActor, the actor callback must continue processing (no-op). Any IBC application or middleware that uses these methods must handle these cases. In most cases, the `GetSrcCallbackAddress` will be the sender address and the `GetDestCallbackAddress` will be the receiver address. However, these are named generically so that implementors may choose a different contract address for the callback if they choose.
77+
The `CallbackPacketData` interface will get created to add `GetSourceCallbackAddress` and `GetDestCallbackAddress` methods. These may return an address
78+
or they may return the empty string. The address may reference an PacketActor or it may be a regular user address. If the address is not a PacketActor, the actor callback must continue processing (no-op). Any IBC application or middleware that uses these methods must handle these cases. In most cases, the `GetSourceCallbackAddress` will be the sender address and the `GetDestCallbackAddress` will be the receiver address. However, these are named generically so that implementors may choose a different contract address for the callback if they choose.
79+
80+
The interface also defines a `UserDefinedGasLimit` method. Any middleware targeting this interface for callback handling should cap the gas that a callback is allowed to take (especially on AcknowledgePacket and TimeoutPacket) so that a custom callback does not prevent the packet lifecycle from completing. However, since this is a global cap it is likely to be very large. Thus, users may specify a smaller limit to cap the amount of fees a relayer must pay in order to complete the packet lifecycle on the user's behalf.
7781

78-
The interface also defines a `UserDefinedGasLimit` method. Any middleware targetting this interface for callback handling should cap the gas that a callback is allowed to take (especially on AcknowledgePacket and TimeoutPacket) so that a custom callback does not prevent the packet lifecycle from completing. However, since this is a global cap it is likely to be very large. Thus, users may specify a smaller limit to cap the amount of fees a relayer must pay in order to complete the packet lifecycle on the user's behalf.
82+
IBC applications which provide the base packet data type must implement the `CallbackPacketData` interface to allow `PacketActor` callbacks.
7983

8084
```go
81-
// Implemented by any packet data type that wants to support
82-
// PacketActor callbacks
85+
// Implemented by any packet data type that wants to support PacketActor callbacks
86+
// PacketActor's will be unable to act on any packet data type that does not implement
87+
// this interface.
8388
type CallbackPacketData interface {
84-
// may return the empty string
85-
GetSrcCallbackAddress() string
86-
87-
// may return the empty string
89+
// GetSourceCallbackAddress should return the callback address of a packet data on the source chain.
90+
// This may or may not be the sender of the packet. If no source callback address exists for the packet,
91+
// an empty string may be returned.
92+
GetSourceCallbackAddress() string
93+
94+
// GetDestCallbackAddress should return the callback address of a packet data on the destination chain.
95+
// This may or may not be the receiver of the packet. If no dest callback address exists for the packet,
96+
// an empty string may be returned.
8897
GetDestCallbackAddress() string
8998

9099
// UserDefinedGasLimit allows the sender of the packet to define inside the packet data
@@ -103,7 +112,8 @@ IBC Apps or middleware can then call the IBCActor callbacks like so in their own
103112

104113
### Handshake Callbacks
105114

106-
The handshake init callbacks (`OnChanOpenInit` and `OnChanCloseInit`) will need to include an additional field so that the initiating actor can be tracked and called upon during handshake completion.
115+
The `OnChanOpenInit` handshake callback will need to include an additional field so that the initiating actor can be tracked and called upon during handshake completion.
116+
The actor provided in the `OnChanOpenInit` callback will be the signer of the `MsgChanOpenInit` message.
107117

108118
```go
109119
func OnChanOpenInit(
@@ -141,42 +151,26 @@ func OnChanOpenAck(
141151
ibcActor, _ := acc.(IBCActor)
142152
ibcActor.OnChanOpen(ctx, portID, channelID, version)
143153
}
144-
// cleanup state
145-
k.deleteActor(ctx, portID, channelID)
154+
155+
// the same actor will be used for channel closure
146156
}
147157

148-
func OnChanOpenConfirm(
158+
func OnChanCloseInit(
149159
ctx sdk.Context,
150160
portID,
151-
channelID string,
161+
channelID,
152162
) error {
153163
// run any necesssary logic first
154-
// retrieve final version
155164

156165
actor := k.getActor(ctx, portID, channelID)
157166
if actor != "" {
158167
ibcActor, _ := acc.(IBCActor)
159-
ibcActor.OnChanOpen(ctx, portID, channelID, version)
168+
ibcActor.OnChanClose(ctx, portID, channelID)
160169
}
161170
// cleanup state
162171
k.deleteActor(ctx, portID, channelID)
163172
}
164173

165-
func OnChanCloseInit(
166-
ctx sdk.Context,
167-
portID,
168-
channelID,
169-
actor string,
170-
) error {
171-
acc := k.getAccount(ctx, actor)
172-
ibcActor, ok := acc.(IBCActor)
173-
if ok {
174-
k.setActor(ctx, portID, channelID, actor)
175-
}
176-
177-
// continued logic
178-
}
179-
180174
func OnChanCloseConfirm(
181175
ctx sdk.Context,
182176
portID,
@@ -194,14 +188,16 @@ func OnChanCloseConfirm(
194188
}
195189
```
196190

191+
NOTE: The handshake calls `OnChanOpenTry` and `OnChanOpenConfirm` are explicitly left out as it is still to be determined how the actor of the `OnChanOpenTry` step should be provided. Initially only the initiating side of the channel handshake may support setting a channel actor, future improvements should allow both sides of the channel handshake to set channel actors.
192+
197193
### PacketCallbacks
198194

199195
No packet callback API will need to change.
200196

201197
```go
202198
// Call the IBCActor recvPacket callback after processing the packet
203-
// if the recvPacket callback exists and returns an error
204-
// then return an error ack to revert all packet data processing
199+
// if the recvPacket callback exists. If the callback returns an error
200+
// then return an error ack to revert all packet data processing.
205201
func OnRecvPacket(
206202
ctx sdk.Context,
207203
packet channeltypes.Packet,
@@ -210,10 +206,16 @@ func OnRecvPacket(
210206
// run any necesssary logic first
211207
// IBCActor logic will postprocess
212208

209+
// postprocessing should only if the underlying application
210+
// returns a successful ack
211+
213212
// unmarshal packet data into expected interface
214213
var cbPacketData callbackPacketData
215214
unmarshalInterface(packet.GetData(), cbPacketData)
215+
216216
if cbPacketData == nil {
217+
// the packet data does not implement the CallbackPacketData interface
218+
// continue processing (no-op)
217219
return
218220
}
219221

@@ -229,6 +231,21 @@ func OnRecvPacket(
229231
// deduct consumed gas from original context
230232
ctx = ctx.WithGasLimit(ctx.GasMeter().RemainingGas() - cbCtx.GasMeter().GasConsumed())
231233
if err != nil {
234+
// NOTE: by returning an error acknowledgement, it is assumed that the
235+
// base IBC application on the counterparty callback stack will be able
236+
// to properly unmarshal the error acknowledgement. It should not expect
237+
// some custom error acknowledgement. If it does, failed acknowledgements
238+
// will be unsuccessfully processed which can be catastrophic in processing
239+
// refund logic.
240+
//
241+
// If this issue is a serious concern, an ADR 8 implementation can construct its own
242+
// acknowledgement type which wraps the underlying application acknowledgement. This
243+
// would require deployment on both sides of the packet flow, in addition to version
244+
// negotiation to enable the custom acknowledgement type usage.
245+
//
246+
// Future improvmenets should allow for each IBC application in a stack of
247+
// callbacks to provide their own acknowledgement without disrupting the unmarshaling
248+
// of an application above or below it in the stack.
232249
return AcknowledgementError(err)
233250
}
234251
}
@@ -245,14 +262,17 @@ func (im IBCModule) OnAcknowledgementPacket(
245262
ctx sdk.Context,
246263
packet channeltypes.Packet,
247264
acknowledgement []byte,
248-
relayer string,
265+
relayer sdk.AccAddress,
249266
) error {
250267
// application-specific onAcknowledgmentPacket logic
251268

252269
// unmarshal packet data into expected interface
253270
var cbPacketData callbackPacketData
254271
unmarshalInterface(packet.GetData(), cbPacketData)
272+
255273
if cbPacketData == nil {
274+
// the packet data does not implement the CallbackPacketData interface
275+
// continue processing (no-op)
256276
return
257277
}
258278

@@ -261,32 +281,42 @@ func (im IBCModule) OnAcknowledgementPacket(
261281
unmarshal(acknowledgement, ack)
262282

263283
// send acknowledgement to original actor
264-
acc := k.getAccount(ctx, cbPacketData.GetSrcCallbackAddress())
284+
acc := k.getAccount(ctx, cbPacketData.GetSourceCallbackAddress())
265285
ibcActor, ok := acc.(IBCActor)
266286
if ok {
267287
gasLimit := getGasLimit(ctx, cbPacketData)
268288

269-
// create cached context with gas limit
270-
cacheCtx, writeFn := ctx.CacheContext()
271-
cacheCtx = cacheCtx.WithGasLimit(gasLimit)
289+
290+
handleCallback := func() error {
291+
// create cached context with gas limit
292+
cacheCtx, writeFn := ctx.CacheContext()
293+
cacheCtx = cacheCtx.WithGasLimit(gasLimit)
272294

273-
defer func() {
274-
if e := recover(); e != nil {
275-
log("ran out of gas in callback. reverting callback state")
276-
} else {
277-
// only write callback state if we did not panic during execution
278-
writeFn()
295+
defer func() {
296+
if e := recover(); e != nil {
297+
log("ran out of gas in callback. reverting callback state")
298+
} else if err == nil {
299+
// only write callback state if we did not panic during execution
300+
// and the error returned is nil
301+
writeFn()
302+
}
279303
}
280-
}
281304

282-
err := ibcActor.OnAcknowledgementPacket(cacheCtx, packet, ack, relayer)
305+
err := ibcActor.OnAcknowledgementPacket(cacheCtx, packet, ack, relayer)
283306

284-
// deduct consumed gas from original context
285-
ctx = ctx.WithGasLimit(ctx.GasMeter().RemainingGas() - cbCtx.GasMeter().GasConsumed())
307+
// deduct consumed gas from original context
308+
ctx = ctx.WithGasLimit(ctx.GasMeter().RemainingGas() - cbCtx.GasMeter().GasConsumed())
309+
310+
return err
311+
}
286312

287-
setAckCallbackError(ctx, packet, err)
288-
emitAckCallbackErrorEvents(err)
313+
if err := handleCallback(); err != nil {
314+
setAckCallbackError(ctx, packet, err) // optional
315+
emitAckCallbackErrorEvents(err)
316+
}
289317
}
318+
319+
return nil
290320
}
291321

292322
// Call the IBCActor timeoutPacket callback after processing the packet
@@ -298,44 +328,56 @@ func (im IBCModule) OnAcknowledgementPacket(
298328
func (im IBCModule) OnTimeoutPacket(
299329
ctx sdk.Context,
300330
packet channeltypes.Packet,
301-
relayer string,
331+
relayer sdk.AccAddress,
302332
) error {
303333
// application-specific onTimeoutPacket logic
304334

305335
// unmarshal packet data into expected interface
306336
var cbPacketData callbackPacketData
307337
unmarshalInterface(packet.GetData(), cbPacketData)
338+
308339
if cbPacketData == nil {
340+
// the packet data does not implement the CallbackPacketData interface
341+
// continue processing (no-op)
309342
return
310343
}
311344

312345
// call timeout callback on original actor
313-
acc := k.getAccount(ctx, cbPacketData.GetSrcCallbackAddress())
346+
acc := k.getAccount(ctx, cbPacketData.GetSourceCallbackAddress())
314347
ibcActor, ok := acc.(IBCActor)
315348
if ok {
316349
gasLimit := getGasLimit(ctx, cbPacketData)
317350

318-
// create cached context with gas limit
319-
cacheCtx, writeFn := ctx.CacheContext()
320-
cacheCtx = cacheCtx.WithGasLimit(gasLimit)
351+
handleCallback := func() error {
352+
// create cached context with gas limit
353+
cacheCtx, writeFn := ctx.CacheContext()
354+
cacheCtx = cacheCtx.WithGasLimit(gasLimit)
321355

322-
defer func() {
323-
if e := recover(); e != nil {
324-
log("ran out of gas in callback. reverting callback state")
325-
} else {
326-
// only write callback state if we did not panic during execution
327-
writeFn()
356+
defer func() {
357+
if e := recover(); e != nil {
358+
log("ran out of gas in callback. reverting callback state")
359+
} else if err == nil {
360+
// only write callback state if we did not panic during execution
361+
// and the error returned is nil
362+
writeFn()
363+
}
328364
}
329-
}
330365

331-
err := ibcActor.OnTimeoutPacket(ctx, packet, relayer)
366+
err := ibcActor.OnTimeoutPacket(ctx, packet, relayer)
332367

333-
// deduct consumed gas from original context
334-
ctx = ctx.WithGasLimit(ctx.GasMeter().RemainingGas() - cbCtx.GasMeter().GasConsumed())
368+
// deduct consumed gas from original context
369+
ctx = ctx.WithGasLimit(ctx.GasMeter().RemainingGas() - cbCtx.GasMeter().GasConsumed())
335370

336-
setTimeoutCallbackError(ctx, packet, err)
337-
emitTimeoutCallbackErrorEvents(err)
371+
return err
372+
}
373+
374+
if err := handleCallback(); err != nil {
375+
setTimeoutCallbackError(ctx, packet, err) // optional
376+
emitTimeoutCallbackErrorEvents(err)
377+
}
338378
}
379+
380+
return nil
339381
}
340382

341383
func getGasLimit(ctx sdk.Context, cbPacketData CallbackPacketData) uint64 {
@@ -367,10 +409,13 @@ Chains are expected to specify a `chainDefinedActorCallbackLimit` to ensure that
367409
### Negative
368410

369411
- Callbacks may now have unbounded gas consumption since the actor may execute arbitrary logic. Chains implementing this feature should take care to place limitations on how much gas an actor callback can consume.
370-
- Application packets that want to support ADR-8 must additionally have their packet data implement the `CallbackPacketData` interface and register their implementation on the chain codec
371412

372413
### Neutral
373414

415+
- Application packets that want to support ADR-8 must additionally have their packet data implement the `CallbackPacketData` interface and register their implementation on the chain codec
416+
374417
## References
375418

376-
- https://github.com/cosmos/ibc-go/issues/1660
419+
- [Original issue](https://github.com/cosmos/ibc-go/issues/1660)
420+
- [CallbackPacketData interface implementation](https://github.com/cosmos/ibc-go/pull/3287)
421+
- [ICS 20, ICS 27 implementations of the CallbackPacketData interface](https://github.com/cosmos/ibc-go/pull/3287)

0 commit comments

Comments
 (0)
Please sign in to comment.