Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADR: 015] IBC Packet Receiver #5230

Merged
merged 37 commits into from
Dec 11, 2019
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7ace5b2
WIP: ADR IBC Packet Receiver
mossid Oct 22, 2019
9b1b06e
Update adr-015-ibc-packet-receiver.md
mossid Oct 22, 2019
d275aae
Remove FoldHandler, add consequences
mossid Nov 7, 2019
6bacbcf
Apply suggestions from code review @fedekunze @cwgoes
mossid Nov 7, 2019
465d1d8
Apply suggestions from code review
mossid Nov 8, 2019
291452c
Add wrapper function for acknowledgement
mossid Nov 8, 2019
4ae5461
fix typo
mossid Nov 8, 2019
5735e2b
Merge branch 'master' into joon/adr-ibc-packet-receiver
cwgoes Nov 8, 2019
35ec132
Apply suggestions from code review
mossid Nov 8, 2019
43fe290
Add other handler types requires verification
mossid Nov 13, 2019
330b043
Add PacketHandler type
mossid Nov 13, 2019
7ab51de
Use AnteDecorator, rename the helper to WriteAck
mossid Nov 14, 2019
fe499a6
fix typo
mossid Nov 14, 2019
c6f9154
Merge branch 'master' into joon/adr-ibc-packet-receiver
cwgoes Nov 14, 2019
e130c07
minor cleanup
fedekunze Nov 14, 2019
ab1dcd2
Update adr-015-ibc-packet-receiver.md
mossid Nov 15, 2019
66547af
Add vulnerability scenario(failing message injection)
mossid Nov 15, 2019
9cd9bc3
Apply suggestions from code review
fedekunze Nov 19, 2019
b168e3f
Add PacketDataI to this ADR
jackzampolin Nov 22, 2019
c3a812d
add port/chan checking, add ics20 example
mossid Nov 22, 2019
7f1b0d6
add foldhandler, remove safety assumptions
mossid Dec 3, 2019
961ea03
Apply suggestions from code review
mossid Dec 3, 2019
c60d4c6
Merge branch 'master' into joon/adr-ibc-packet-receiver
fedekunze Dec 4, 2019
4106830
address comments in progress
mossid Dec 4, 2019
4569be4
address missing pieces
mossid Dec 7, 2019
07a8987
fix missed line
mossid Dec 7, 2019
26db0bc
Apply suggestions from code review
cwgoes Dec 8, 2019
ed131a3
Merge branch 'master' into joon/adr-ibc-packet-receiver
cwgoes Dec 8, 2019
e6692f7
Apply suggestions from code review
mossid Dec 9, 2019
46e018c
address comments
mossid Dec 9, 2019
4ce753c
Merge branch 'master' into joon/adr-ibc-packet-receiver
fedekunze Dec 9, 2019
6e4c052
address comments in progress
mossid Dec 10, 2019
0d236ff
Merge branch 'master' into joon/adr-ibc-packet-receiver
alexanderbez Dec 11, 2019
7ebb6ad
Add newline
alexanderbez Dec 11, 2019
d62de0c
Fix linting and typos
alexanderbez Dec 11, 2019
a22aeee
Merge branch 'master' into joon/adr-ibc-packet-receiver
cwgoes Dec 11, 2019
37342cf
Merge branch 'master' into joon/adr-ibc-packet-receiver
cwgoes Dec 11, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/architecture/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ Please add a entry below in your Pull Request for an ADR.
- [ADR 010: Modular AnteHandler](./adr-010-modular-antehandler.md)
- [ADR 011: Generalize Genesis Accounts](./adr-011-generalize-genesis-accounts.md)
- [ADR 012: State Accessors](./adr-012-state-accessors.md)
- [ADR 015: IBC Packet Receiver](./adr-015-ibc-packet-receiver.md)
289 changes: 289 additions & 0 deletions docs/architecture/adr-015-ibc-packet-receiver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
# ADR 015: IBC Packet Receiver

## Changelog

- 2019 Oct 22: Initial Draft

## Context

[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).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line doesn't have any context. Is it incomplete?


In ICS 26, the routing module is defined as a layer above each application module
which verifies and routes messages to the destination modules. It is possible to
implement it as a separate module, however, we already have functionality to route
messages upon the destination identifiers in the baseapp. This ADR suggests
to utilize existing `baseapp.router` tp route packets to application modules.
cwgoes marked this conversation as resolved.
Show resolved Hide resolved

Generally, each routing module callbacks have two separate steps in them,
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
verificaton and execution. This corresponds to the `AnteHandler`-`Handler`
moodel inside the SDK. We can do the verificaton inside the `AnteHandler`
mossid marked this conversation as resolved.
Show resolved Hide resolved
in order to increase developer ergonomics by reducing boilerplate
verification code.

## Decision

`PortKeeper` will have the capability key that is able to access only on the
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
channels binded to the port. Entities those holding a `PortKeeper` will be
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
able to call the corresponding `ChannelKeeper`s method, but only with the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call the corresponding ChannelKeepers method

Which corresponding method?

allowed port. `ChannelKeeper.Port(string, ChannelChecker)` will be defined to
easily construct a capability-safe `PortKeeper`. This will be address in
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
another ADR and we will use unsecure `ChannelKeeper` for now.

`baseapp.runMsgs` will break the loop over the messages if one of the handlers
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this paragraph has no context

returns `!Result.IsOK()`. However, the outer logic will write the cached
store if `Result.IsOK() || Result.Code.IsBreak()`. `Result.Code.IsBreak()` if
`Result.Code == CodeTxBreak`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain why we might want to write the cached logic sometimes even if one of the messages fails?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we want to persist sequence number increments & commitments even if IBC packet execution fails. It's basically analogous to nonces on accounts incrementing even if the tx fails in DeliverTx.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we want to persist sequence number increments & commitments even if IBC packet execution fails. It's basically analogous to nonces on accounts incrementing even if the tx fails in DeliverTx.

this needs to be written down


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context doesn't really define any problem...at least I can't see one immediately. I would state the problem clearly (purpose of the ADR) and how it relates to the ante-handler.

```go
// Pseudocode
func (app *BaseApp) runTx(tx sdk.Tx) (result sdk.Result) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (app *BaseApp) runTx(tx sdk.Tx) (result sdk.Result) {
func (app *BaseApp) runTx(tx Tx) (result Result) {

msgs := tx.GetMsgs()

// AnteHandler
if app.anteHandler != nil {
anteCtx, msCache := app.cacheTxContext(ctx)
newCtx, err := app.anteHandler(anteCtx, tx)
if !newCtx.IsZero() {
ctx = newCtx.WithMultiStore(ms)
}

if err != nil {
// error handling logic
return res
}

msCache.Write()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

// Main Handler
runMsgCtx, msCache := app.cacheTxContext(ctx)
result = app.runMsgs(runMsgCtx, msgs)
// BEGIN modification made in this ADR
if result.IsOK() || result.IsBreak() {
// END
msCache.Write()
}

return result
}
```

The Cosmos SDK will define an `AnteDecorator` for IBC packet receiving. The
`AnteDecorator` will iterate over the messages included in the transaction, type
`switch` to check whether the message contains an incoming IBC packet, and if so
verify the Merkle proof.

```go
type ProofVerificationDecorator struct {
clientKeeper ClientKeeper
channelKeeper ChannelKeeper
}

func (pvr ProofVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I've been avoiding package prefixes in ADRs (pseudo-code in general). e.g. sdk.Context => Context. It shortens the code and makes it easier to read.

mossid marked this conversation as resolved.
Show resolved Hide resolved
for _, msg := range tx.GetMsgs() {
var err error
switch msg := msg.(type) {
case client.MsgUpdateClient:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: In the ADR this is OK, but I recommend in implementation that the case handlers be broken out into individual auxiliary methods/functions for improved readability and testing.

Copy link
Contributor

@alexanderbez alexanderbez Dec 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: All errors here should return typed errors. This will help devs. e.g. return ErrInvalidTxMultiPort

err = pvr.clientKeeper.UpdateClient(msg.ClientID, msg.Header)
case channel.MsgPacket:
err = pvr.channelKeeper.RecvPacket(msg.Packet, msg.Proofs, msg.ProofHeight)
case chanel.MsgAcknowledgement:
err = pvr.channelKeeper.AcknowledgementPacket(msg.Acknowledgement, msg.Proof, msg.ProofHeight)
case channel.MsgTimeoutPacket:
err = pvr.channelKeeper.TimeoutPacket(msg.Packet, msg.Proof, msg.ProofHeight, msg.NextSequenceRecv)
case channel.MsgChannelOpenInit;
err = pvr.channelKeeper.CheckOpen(msg.PortID, msg.ChannelID, msg.Channel)
default:
continue
}

if err != nil {
return ctx, err
}
}

return next(ctx, tx, simulate)
}
```

Where `MsgUpdateClient`, `MsgPacket`, `MsgAcknowledgement`, `MsgTimeoutPacket`
are `sdk.Msg` types correspond to `handleUpdateClient`, `handleRecvPacket`,
`handleAcknowledgementPacket`, `handleTimeoutPacket` of the routing module,
respectively.

The side effects of `RecvPacket`, `VerifyAcknowledgement`,
`VerifyTimeout` will be extracted out into separated functions, which will
be called by the application handlers after the execution.

```go
func (keeper ChannelKeeper) WriteAcknowledgement(ctx sdk.Context, packet Packet, ack []byte) {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
keeper.SetPacketAcknowledgement(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ack)
keeper.SetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
}

func (keeper ChannelKeeper) DeleteCommitment(ctx sdk.Context, packet Packet) {
mossid marked this conversation as resolved.
Show resolved Hide resolved
keeper.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
}

func (keeper ChannelKeeper) DeleteCommitmentTimeout(ctx sdk.Context, packet Packet) {
mossid marked this conversation as resolved.
Show resolved Hide resolved
k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())

if channel.Ordering == types.ORDERED [
channel.State = types.CLOSED
k.SetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), channel)
}
}
```

Each application handler should call respective finalization methods on the `PortKeeper`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the finalization methods have to be called by each module handler or do we want to handle it automatically?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finalization methods have to be called by each module handler.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finalization methods have to be called by each module handler.

please write that down in the ADR

in order to increase sequence(in case of packet) or remove the commitment
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
(in case of acknowledgement and timeout).
Calling those functions implies that the application logic has successfully executed.
However, the handlers can return `Result` with `CodeTxBreak` after calling those methods
which will persist the state changes that has been already done but prevent any further
messages to be executed in case of semantically invalid packet. In any case the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why might this be useful in the IBC case?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above response (but this should be added to the ADR).

application modules should never return state reverting result, which will make the
channel unable to proceed.

`ChannelKeeper.CheckOpen` method will be introduced. `ChannelChecker` function will be
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChannelKeeper.CheckOpen method will be introduced.

why?

provided by each of the `AppModule` and injected to `ChannelKeeper.Port()` at the
top level application. `CheckOpen` will find the correct `ChennelChecker` using the
`PortID` and call it, which will return an error if it is unacceptible by the application.

The `ProofVerificationDecorator` will be inserted to the top level application.
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
It is not safe to make each application responsible to call proof verification
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
logic, whereas application can misbehave(in terms of IBC protocol) by
mistake.

The `ProofVerificationDecorator` should come right after the default sybil attack
resistent layer from the current `auth.NewAnteHandler`:

```go
// add IBC ProofVerificationDecorator to the Chain of
func NewAnteHandler(
ak keeper.AccountKeeper, supplyKeeper types.SupplyKeeper, ibcKeeper ibc.Keeper,
sigGasConsumer SignatureVerificationGasConsumer) sdk.AnteHandler {
return sdk.ChainAnteDecorators(
NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
...
NewIncrementSequenceDecorator(ak),
ibcante.ProofVerificationDecorator(ibcKeeper.ClientKeeper, ibcKeeper.ChannelKeeper), // innermost AnteDecorator
)
}
```

The implementation of this ADR will also change the `Data` field of the `Packet` type from `[]byte` (i.e. arbitrary data) to `PacketDataI`. 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).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this relevant to this ADR?


The `PacketDataI` is the application specific interface that provides information for the execuition of the application packet. In the case of ICS20 this would be `denom`, `amount` and `address`

```go
// PacketDataI defines the standard interface for IBC packet data
type PacketDataI interface {
GetCommitment() []byte // Commitment form that will be stored in the state.
GetTimeoutHeight() uint64

ValidateBasic() sdk.Error
Type() string
}
```

Example application-side usage:

```go
type AppModule struct {}

func (module AppModule) CheckChannel(portID, channelID string, channel Channel) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a top-level function instead of a message handler?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckChannel will be provided to ChannelKeeper.Port() at the top level application. Will add to the doc.

if channel.Ordering != UNORDERED {
return ErrUncompatibleOrdering()
}
if channel.CounterpartyPort != "bank" {
return ErrUncompatiblePort()
}
if channel.Version != "" {
return ErrUncompatibleVersion()
}
return nil
}

func NewHandler(k Keeper) sdk.Handler {
mossid marked this conversation as resolved.
Show resolved Hide resolved
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
mossid marked this conversation as resolved.
Show resolved Hide resolved
switch msg := msg.(type) {
case MsgTransfer:
return handleMsgTransfer(ctx, k, msg)
case ibc.MsgPacket:
switch data := msg.Packet.Data.(type) {
case PacketDataTransfer: // i.e fulfills the PacketDataI interface
return handlePacketDataTransfer(ctx, k, msg.Packet, data)
}
case ibc.MsgTimeoutPacket:
switch packet := msg.Packet.Data.(type) {
case PacketDataTransfer: // i.e fulfills the PacketDataI interface
return handleTimeoutPacketDataTransfer(ctx, k, msg.Packet)
}
// interface { PortID() string; ChannelID() string; Channel() ibc.Channel }
// MsgChanInit, MsgChanTry
case ibc.MsgChannelOpen:
return handleMsgChannelOpen(ctx, k, msg)
}
}
}

func handleMsgTransfer(ctx sdk.Context, k Keeper, msg MsgTransfer) sdk.Result {
mossid marked this conversation as resolved.
Show resolved Hide resolved
err := k.SendTransfer(ctx,msg.PortID, msg.ChannelID, msg.Amount, msg.Sender, msg.Receiver)
if err != nil {
return sdk.ResultFromError(err)
}

return sdk.Result{}
}

func handlePacketDataTransfer(ctx sdk.Context, k Keeper, packet ibc.Packet, data PacketDataTransfer) sdk.Result {
mossid marked this conversation as resolved.
Show resolved Hide resolved
err := k.ReceiveTransfer(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetDestinationPort(), packet.GetDestinationChannel(), data)
if err != nil {
// TODO: Source chain sent invalid packet, shutdown channel
}
k.ChannelKeeper.WriteAcknowledgement([]byte{0x00}) // WriteAcknowledgement increases the sequence, preventing double spending
return sdk.Result{}
}

func handleCustomTimeoutPacket(ctx sdk.Context, k Keeper, packet CustomPacket) sdk.Result {
mossid marked this conversation as resolved.
Show resolved Hide resolved
err := k.RecoverTransfer(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetDestinationPort(), packet.GetDestinationChannel(), data)
if err != nil {
// This chain sent invalid packet or cannot recover the funds
panic(err)
}
k.ChannelKeeper.DeleteCommitmentTimeout(ctx, packet)
// packet timeout should not fail
return sdk.Result{}
}

func handleMsgChannelOpen(ctx sdk.Context, k Keeper, msg ibc.MsgOpenChannel) sdk.Result {
mossid marked this conversation as resolved.
Show resolved Hide resolved
k.AllocateEscrowAddress(ctx, msg.ChannelID())
return sdk.Result{}
}
```

## Status

Proposed

## Consequences

### Positive

- Intuitive interface for developers - IBC handlers do not need to care about IBC authentication
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
- State change commitment logic is embedded into `baseapp.runTx` logic

### Negative

- Cannot support dynamic ports, routing is tied to the baseapp router
jackzampolin marked this conversation as resolved.
Show resolved Hide resolved
Dynamic ports can be supported using hierarchical port identifier, see #5290 for detail
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a negative.


### Neutral

- Introduces new `AnteHandler` decorator.

## References

- Relevant comment: [cosmos/ics#289](https://github.com/cosmos/ics/issues/289#issuecomment-544533583)
- [ICS26 - Routing Module](https://github.com/cosmos/ics/blob/master/spec/ics-026-routing-module)