-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
Changes from 26 commits
7ace5b2
9b1b06e
d275aae
6bacbcf
465d1d8
291452c
4ae5461
5735e2b
35ec132
43fe290
330b043
7ab51de
fe499a6
c6f9154
e130c07
ab1dcd2
66547af
9cd9bc3
b168e3f
c3a812d
7f1b0d6
961ea03
c60d4c6
4106830
4569be4
07a8987
26db0bc
ed131a3
e6692f7
46e018c
4ce753c
6e4c052
0d236ff
7ebb6ad
d62de0c
a22aeee
37342cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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). | ||||||
|
||||||
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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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`. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
this needs to be written down |
||||||
|
||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where is this defined? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
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: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||
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` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Finalization methods have to be called by each module handler. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why might this be useful in the IBC case? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this a top-level function instead of a message handler? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) |
There was a problem hiding this comment.
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?