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)