hypersign-protocol / whitepaper

2 stars 0 forks source link

Research on sending Custom packets on IBC #32

Open Vishwas1 opened 2 years ago

Vishwas1 commented 2 years ago

Configure your application to use IBC and send data packets to other chains.

Modules must implement the IBC module interface (which contains both channel handshake callbacks and packet handling callbacks).

Vishwas1 commented 2 years ago

Implement IBCModule Interface and callback

The Cosmos SDK expects all IBC modules to implement the IBCModule interface. This interface contains all of the callbacks IBC expects modules to implement.

Channel Handshake

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
}
Vishwas1 commented 2 years ago

Custom Packets

  1. Define custom packet
// 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
}
  1. Then a module must encode its packet data before sending it through IBC.
// Sending custom application packet data
data := EncodePacketData(customPacketData)
packet.Data = data
IBCChannelKeeper.SendPacket(ctx, packet)
  1. A module receiving a packet must decode the PacketData into a structure it expects so that it can act on it.
// Receiving custom application packet data (in OnRecvPacket)
packetData := DecodePacketData(packet.Data)
// handle received custom packet data
Vishwas1 commented 2 years ago

Packet Flow Handling

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:

  1. module A sends a packet through the IBC module
  2. the packet is received by module B
  3. if module B writes an acknowledgement of the packet then module A will process the acknowledgement
  4. if the packet is not successfully received before the timeout, then module A processes the packet's timeout.

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(
    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
}
Vishwas1 commented 2 years ago

Acknowledgment

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
}
Vishwas1 commented 2 years ago

Relayers

image

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 key action, and an attribute value representing the type of message sent (channel_open_init would be the attribute value for MsgChannelOpenInit)

Subscribing with Tendermint

Example implementation of relayers