github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/docs/architecture/adr-015-ibc-packet-receiver.md (about) 1 # ADR 015: IBC Packet Receiver 2 3 ## Changelog 4 5 - 2019 Oct 22: Initial Draft 6 7 ## Context 8 9 [ICS 26 - Routing Module](https://github.com/cosmos/ics/tree/master/spec/ics-026-routing-module) defines a function [`handlePacketRecv`](https://github.com/cosmos/ics/tree/master/spec/ics-026-routing-module#packet-relay). 10 11 In ICS 26, the routing module is defined as a layer above each application module 12 which verifies and routes messages to the destination modules. It is possible to 13 implement it as a separate module, however, we already have functionality to route 14 messages upon the destination identifiers in the baseapp. This ADR suggests 15 to utilize existing `baseapp.router` to route packets to application modules. 16 17 Generally, routing module callbacks have two separate steps in them, 18 verification and execution. This corresponds to the `AnteHandler`-`Handler` 19 model inside the SDK. We can do the verification inside the `AnteHandler` 20 in order to increase developer ergonomics by reducing boilerplate 21 verification code. 22 23 For atomic multi-message transaction, we want to keep the IBC related 24 state modification to be preserved even the application side state change 25 reverts. One of the example might be IBC token sending message following with 26 stake delegation which uses the tokens received by the previous packet message. 27 If the token receiving fails for any reason, we might not want to keep 28 executing the transaction, but we also don't want to abort the transaction 29 or the sequence and commitment will be reverted and the channel will be stuck. 30 This ADR suggests new `CodeType`, `CodeTxBreak`, to fix this problem. 31 32 ## Decision 33 34 `PortKeeper` will have the capability key that is able to access only the 35 channels bound to the port. Entities that hold a `PortKeeper` will be 36 able to call the methods on it which are corresponding with the methods with 37 the same names on the `ChannelKeeper`, but only with the 38 allowed port. `ChannelKeeper.Port(string, ChannelChecker)` will be defined to 39 easily construct a capability-safe `PortKeeper`. This will be addressed in 40 another ADR and we will use insecure `ChannelKeeper` for now. 41 42 `baseapp.runMsgs` will break the loop over the messages if one of the handlers 43 returns `!Result.IsOK()`. However, the outer logic will write the cached 44 store if `Result.IsOK() || Result.Code.IsBreak()`. `Result.Code.IsBreak()` if 45 `Result.Code == CodeTxBreak`. 46 47 ```go 48 func (app *BaseApp) runTx(tx Tx) (result Result) { 49 msgs := tx.GetMsgs() 50 51 // AnteHandler 52 if app.anteHandler != nil { 53 anteCtx, msCache := app.cacheTxContext(ctx) 54 newCtx, err := app.anteHandler(anteCtx, tx) 55 if !newCtx.IsZero() { 56 ctx = newCtx.WithMultiStore(ms) 57 } 58 59 if err != nil { 60 // error handling logic 61 return res 62 } 63 64 msCache.Write() 65 } 66 67 // Main Handler 68 runMsgCtx, msCache := app.cacheTxContext(ctx) 69 result = app.runMsgs(runMsgCtx, msgs) 70 // BEGIN modification made in this ADR 71 if result.IsOK() || result.IsBreak() { 72 // END 73 msCache.Write() 74 } 75 76 return result 77 } 78 ``` 79 80 The Cosmos SDK will define an `AnteDecorator` for IBC packet receiving. The 81 `AnteDecorator` will iterate over the messages included in the transaction, type 82 `switch` to check whether the message contains an incoming IBC packet, and if so 83 verify the Merkle proof. 84 85 ```go 86 type ProofVerificationDecorator struct { 87 clientKeeper ClientKeeper 88 channelKeeper ChannelKeeper 89 } 90 91 func (pvr ProofVerificationDecorator) AnteHandle(ctx Context, tx Tx, simulate bool, next AnteHandler) (Context, error) { 92 for _, msg := range tx.GetMsgs() { 93 var err error 94 switch msg := msg.(type) { 95 case client.MsgUpdateClient: 96 err = pvr.clientKeeper.UpdateClient(msg.ClientID, msg.Header) 97 case channel.MsgPacket: 98 err = pvr.channelKeeper.RecvPacket(msg.Packet, msg.Proofs, msg.ProofHeight) 99 case chanel.MsgAcknowledgement: 100 err = pvr.channelKeeper.AcknowledgementPacket(msg.Acknowledgement, msg.Proof, msg.ProofHeight) 101 case channel.MsgTimeoutPacket: 102 err = pvr.channelKeeper.TimeoutPacket(msg.Packet, msg.Proof, msg.ProofHeight, msg.NextSequenceRecv) 103 case channel.MsgChannelOpenInit; 104 err = pvr.channelKeeper.CheckOpen(msg.PortID, msg.ChannelID, msg.Channel) 105 default: 106 continue 107 } 108 109 if err != nil { 110 return ctx, err 111 } 112 } 113 114 return next(ctx, tx, simulate) 115 } 116 ``` 117 118 Where `MsgUpdateClient`, `MsgPacket`, `MsgAcknowledgement`, `MsgTimeoutPacket` 119 are `sdk.Msg` types correspond to `handleUpdateClient`, `handleRecvPacket`, 120 `handleAcknowledgementPacket`, `handleTimeoutPacket` of the routing module, 121 respectively. 122 123 The side effects of `RecvPacket`, `VerifyAcknowledgement`, 124 `VerifyTimeout` will be extracted out into separated functions, 125 `WriteAcknowledgement`, `DeleteCommitment`, `DeleteCommitmentTimeout`, respectively, 126 which will be called by the application handlers after the execution. 127 128 `WriteAcknowledgement` writes the acknowledgement to the state that can be 129 verified by the counter-party chain and increments the sequence to prevent 130 double execution. `DeleteCommitment` will delete the commitment stored, 131 `DeleteCommitmentTimeout` will delete the commitment and close channel in case 132 of ordered channel. 133 134 ```go 135 func (keeper ChannelKeeper) WriteAcknowledgement(ctx Context, packet Packet, ack []byte) { 136 keeper.SetPacketAcknowledgement(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ack) 137 keeper.SetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) 138 } 139 140 func (keeper ChannelKeeper) DeleteCommitment(ctx Context, packet Packet) { 141 keeper.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) 142 } 143 144 func (keeper ChannelKeeper) DeleteCommitmentTimeout(ctx Context, packet Packet) { 145 k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) 146 147 if channel.Ordering == types.ORDERED [ 148 channel.State = types.CLOSED 149 k.SetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), channel) 150 } 151 } 152 ``` 153 154 Each application handler should call respective finalization methods on the `PortKeeper` 155 in order to increase sequence (in case of packet) or remove the commitment 156 (in case of acknowledgement and timeout). 157 Calling those functions implies that the application logic has successfully executed. 158 However, the handlers can return `Result` with `CodeTxBreak` after calling those methods 159 which will persist the state changes that has been already done but prevent any further 160 messages to be executed in case of semantically invalid packet. This will keep the sequence 161 increased in the previous IBC packets(thus preventing double execution) without 162 proceeding to the following messages. 163 In any case the application modules should never return state reverting result, 164 which will make the channel unable to proceed. 165 166 `ChannelKeeper.CheckOpen` method will be introduced. This will replace `onChanOpen*` defined 167 under the routing module specification. Instead of define each channel handshake callback 168 functions, application modules can provide `ChannelChecker` function with the `AppModule` 169 which will be injected to `ChannelKeeper.Port()` at the top level application. 170 `CheckOpen` will find the correct `ChennelChecker` using the 171 `PortID` and call it, which will return an error if it is unacceptable by the application. 172 173 The `ProofVerificationDecorator` will be inserted to the top level application. 174 It is not safe to make each module responsible to call proof verification 175 logic, whereas application can misbehave(in terms of IBC protocol) by 176 mistake. 177 178 The `ProofVerificationDecorator` should come right after the default sybil attack 179 resistent layer from the current `auth.NewAnteHandler`: 180 181 ```go 182 // add IBC ProofVerificationDecorator to the Chain of 183 func NewAnteHandler( 184 ak keeper.AccountKeeper, supplyKeeper types.SupplyKeeper, ibcKeeper ibc.Keeper, 185 sigGasConsumer SignatureVerificationGasConsumer) sdk.AnteHandler { 186 return sdk.ChainAnteDecorators( 187 NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first 188 ... 189 NewIncrementSequenceDecorator(ak), 190 ibcante.ProofVerificationDecorator(ibcKeeper.ClientKeeper, ibcKeeper.ChannelKeeper), // innermost AnteDecorator 191 ) 192 } 193 ``` 194 195 The implementation of this ADR will also change the `Data` field of the `Packet` type from `[]byte` (i.e. arbitrary data) to `PacketDataI`. We want to make application modules be able to register custom packet data type which is automatically unmarshaled at `TxDecoder` time and can be simply type switched inside the application handler. Also, by having `GetCommitment()` method instead of manually generate the commitment inside the IBC keeper, the applications can define their own commitment method, including bare bytes, hashing, etc. 196 197 This also removes the `Timeout` field from the `Packet` struct. This is because the `PacketDataI` interface now contains this information. You can see details about this in [ICS04](https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#definitions). 198 199 The `PacketDataI` is the application specific interface that provides information for the execution of the application packet. In the case of ICS20 this would be `denom`, `amount` and `address` 200 201 ```go 202 // PacketDataI defines the standard interface for IBC packet data 203 type PacketDataI interface { 204 GetCommitment() []byte // Commitment form that will be stored in the state. 205 GetTimeoutHeight() uint64 206 207 ValidateBasic() error 208 Type() string 209 } 210 ``` 211 212 Example application-side usage: 213 214 ```go 215 type AppModule struct {} 216 217 // CheckChannel will be provided to the ChannelKeeper as ChannelKeeper.Port(module.CheckChannel) 218 func (module AppModule) CheckChannel(portID, channelID string, channel Channel) error { 219 if channel.Ordering != UNORDERED { 220 return ErrUncompatibleOrdering() 221 } 222 if channel.CounterpartyPort != "bank" { 223 return ErrUncompatiblePort() 224 } 225 if channel.Version != "" { 226 return ErrUncompatibleVersion() 227 } 228 return nil 229 } 230 231 func NewHandler(k Keeper) Handler { 232 return func(ctx Context, msg Msg) Result { 233 switch msg := msg.(type) { 234 case MsgTransfer: 235 return handleMsgTransfer(ctx, k, msg) 236 case ibc.MsgPacket: 237 switch data := msg.Packet.Data.(type) { 238 case PacketDataTransfer: // i.e fulfills the PacketDataI interface 239 return handlePacketDataTransfer(ctx, k, msg.Packet, data) 240 } 241 case ibc.MsgTimeoutPacket: 242 switch packet := msg.Packet.Data.(type) { 243 case PacketDataTransfer: // i.e fulfills the PacketDataI interface 244 return handleTimeoutPacketDataTransfer(ctx, k, msg.Packet) 245 } 246 // interface { PortID() string; ChannelID() string; Channel() ibc.Channel } 247 // MsgChanInit, MsgChanTry implements ibc.MsgChannelOpen 248 case ibc.MsgChannelOpen: 249 return handleMsgChannelOpen(ctx, k, msg) 250 } 251 } 252 } 253 254 func handleMsgTransfer(ctx Context, k Keeper, msg MsgTransfer) Result { 255 err := k.SendTransfer(ctx,msg.PortID, msg.ChannelID, msg.Amount, msg.Sender, msg.Receiver) 256 if err != nil { 257 return sdk.ResultFromError(err) 258 } 259 260 return sdk.Result{} 261 } 262 263 func handlePacketDataTransfer(ctx Context, k Keeper, packet Packet, data PacketDataTransfer) Result { 264 err := k.ReceiveTransfer(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetDestinationPort(), packet.GetDestinationChannel(), data) 265 if err != nil { 266 // TODO: Source chain sent invalid packet, shutdown channel 267 } 268 k.ChannelKeeper.WriteAcknowledgement([]byte{0x00}) // WriteAcknowledgement increases the sequence, preventing double spending 269 return sdk.Result{} 270 } 271 272 func handleCustomTimeoutPacket(ctx Context, k Keeper, packet CustomPacket) Result { 273 err := k.RecoverTransfer(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetDestinationPort(), packet.GetDestinationChannel(), data) 274 if err != nil { 275 // This chain sent invalid packet or cannot recover the funds 276 panic(err) 277 } 278 k.ChannelKeeper.DeleteCommitmentTimeout(ctx, packet) 279 // packet timeout should not fail 280 return sdk.Result{} 281 } 282 283 func handleMsgChannelOpen(sdk.Context, k Keeper, msg MsgOpenChannel) Result { 284 k.AllocateEscrowAddress(ctx, msg.ChannelID()) 285 return sdk.Result{} 286 } 287 ``` 288 289 ## Status 290 291 Proposed 292 293 ## Consequences 294 295 ### Positive 296 297 - Intuitive interface for developers - IBC handlers do not need to care about IBC authentication 298 - State change commitment logic is embedded into `baseapp.runTx` logic 299 300 ### Negative 301 302 - Cannot support dynamic ports, routing is tied to the baseapp router 303 304 ### Neutral 305 306 - Introduces new `AnteHandler` decorator. 307 - Dynamic ports can be supported using hierarchical port identifier, see #5290 for detail 308 309 ## References 310 311 - Relevant comment: [cosmos/ics#289](https://github.com/cosmos/ics/issues/289#issuecomment-544533583) 312 - [ICS26 - Routing Module](https://github.com/cosmos/ics/blob/master/spec/ics-026-routing-module)