Open Vishwas1 opened 2 years ago
IBCModule Interface
and callbackThe Cosmos SDK expects all IBC modules to implement the IBCModule interface. This interface contains all of the callbacks IBC expects modules to implement.
Here are the channel open handshake callbacks that modules are expected to implement:
// Called by IBC Handler on MsgOpenInit
func (k Keeper) OnChanOpenInit(ctx sdk.Context,
order channeltypes.Order,
connectionHops []string,
portID string,
channelID string,
channelCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version string,
) error {
// OpenInit must claim the channelCapability that IBC passes into the callback
if err := k.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil {
return err
}
// ... do custom initialization logic
// Use above arguments to determine if we want to abort handshake
// Examples: Abort if order == UNORDERED,
// Abort if version is unsupported
err := checkArguments(args)
return err
}
// Called by IBC Handler on MsgOpenTry
OnChanOpenTry(
ctx sdk.Context,
order channeltypes.Order,
connectionHops []string,
portID,
channelID string,
channelCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version,
counterpartyVersion string,
) error {
// Module may have already claimed capability in OnChanOpenInit in the case of crossing hellos
// (ie chainA and chainB both call ChanOpenInit before one of them calls ChanOpenTry)
// If the module can already authenticate the capability then the module already owns it so we don't need to claim
// Otherwise, module does not have channel capability and we must claim it from IBC
if !k.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
// Only claim channel capability passed back by IBC module if we do not already own it
if err := k.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil {
return err
}
}
// ... do custom initialization logic
// Use above arguments to determine if we want to abort handshake
err := checkArguments(args)
return err
}
// Called by IBC Handler on MsgOpenAck
OnChanOpenAck(
ctx sdk.Context,
portID,
channelID string,
counterpartyVersion string,
) error {
// ... do custom initialization logic
// Use above arguments to determine if we want to abort handshake
err := checkArguments(args)
return err
}
// Called by IBC Handler on MsgOpenConfirm
OnChanOpenConfirm(
ctx sdk.Context,
portID,
channelID string,
) error {
// ... do custom initialization logic
// Use above arguments to determine if we want to abort handshake
err := checkArguments(args)
return err
}
Channel Closing Handshake
The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls ChanCloseInit
and the finalizing chain calls ChanCloseConfirm
.
// Called by IBC Handler on MsgCloseInit
OnChanCloseInit(
ctx sdk.Context,
portID,
channelID string,
) error {
// ... do custom finalization logic
// Use above arguments to determine if we want to abort handshake
err := checkArguments(args)
return err
}
// Called by IBC Handler on MsgCloseConfirm
OnChanCloseConfirm(
ctx sdk.Context,
portID,
channelID string,
) error {
// ... do custom finalization logic
// Use above arguments to determine if we want to abort handshake
err := checkArguments(args)
return err
}
// Custom packet data defined in application module
type CustomPacketData struct {
// Custom fields ...
}
EncodePacketData(packetData CustomPacketData) []byte {
// encode packetData to bytes
}
DecodePacketData(encoded []byte) (CustomPacketData) {
// decode from bytes to packet data
}
// Sending custom application packet data
data := EncodePacketData(customPacketData)
packet.Data = data
IBCChannelKeeper.SendPacket(ctx, packet)
// Receiving custom application packet data (in OnRecvPacket)
packetData := DecodePacketData(packet.Data)
// handle received custom packet data
Just as IBC expected modules to implement callbacks for channel handshakes, IBC also expects modules to implement callbacks for handling the packet flow through a channel.
Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel.
Briefly, a successful packet flow works as follows:
Sending packet
to send a packet a module simply needs to call SendPacket
on the IBCChannelKeeper
.
// retrieve the dynamic capability for this channel
channelCap := scopedKeeper.GetCapability(ctx, channelCapName)
// Sending custom application packet data
data := EncodePacketData(customPacketData)
packet.Data = data
// Send packet to IBC, authenticating with channelCap
IBCChannelKeeper.SendPacket(ctx, channelCap, packet)
Receiving Packets
OnRecvPacket
callback. OnRecvPacket(
ctx sdk.Context,
packet channeltypes.Packet,
) (res *sdk.Result, ack []byte, abort error) {
// Decode the packet data
packetData := DecodePacketData(packet.Data)
// do application state changes based on packet data
// and return result, acknowledgement and abortErr
// Note: abortErr is only not nil if we need to abort the entire receive packet, and allow a replay of the receive.
// If the application state change failed but we do not want to replay the packet,
// simply encode this failure with relevant information in ack and return nil error
res, ack, abortErr := processPacket(ctx, packet, packetData)
// if we need to abort the entire receive packet, return error
if abortErr != nil {
return nil, nil, abortErr
}
// Encode the ack since IBC expects acknowledgement bytes
ackBytes := EncodeAcknowledgement(ack)
return res, ackBytes, nil
}
Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement will be written once the packet has been processed by the application which may be well after the packet receipt.
// Acknowledgement is the recommended acknowledgement format to be used by
// app-specific protocols.
// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental
// conflicts with other protobuf message formats used for acknowledgements.
// The first byte of any message with this format will be the non-ASCII values
// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS:
// https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#acknowledgement-envelope
message Acknowledgement {
// response contains either a result or an error and must be non-empty
oneof response {
bytes result = 21;
string error = 22;
}
}
After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can then process the acknowledgement using the OnAcknowledgementPacket
callback.
OnAcknowledgementPacket(
ctx sdk.Context,
packet channeltypes.Packet,
acknowledgement []byte,
) (*sdk.Result, error) {
// Decode acknowledgement
ack := DecodeAcknowledgement(acknowledgement)
// process ack
res, err := processAck(ack)
return res, err
}
When we enable IBC modules in our blockchain, it exposes websocket API (https://docs.tendermint.com/master/rpc/#/Websocket). The Relayer opens socket connections with IBC modules (both chains ). It keep listening to the events thrown by one IBC modules and then relays to the other
In the Cosmos SDK, it can be assumed that for every message there is an event emitted with the type
message
, attribute keyaction
, and an attribute value representing the type of message sent (channel_open_init
would be the attribute value forMsgChannelOpenInit
)
message.action
key to extract the number of messages in the transaction and the type of IBC transactions sent
Configure your application to use IBC and send data packets to other chains.