icon-project/IIPs

[DISCUSSION] ICON Multi protocol Arbitrary Call Service Standard

Opened this issue · 4 comments


iip:
title: ICON xCall Standard
author: Anton Andell (@AntonAndell)
discussions-to:
status: Draft
type: Standards Track
category: IRC
created: 2023-04-24


Simple Summary

A standard interface to make an arbitrary call service (a.k.a. xcall) between different blockchain networks via any bridging protocol.

Abstract

This document describes an extension of xCall as defined in IIP52 that can be used to invoke arbitrary cross-chain contract calls between different blockchains networks using multiple protocols.
The existing interfaces between BMC and BSH (sendMessage and handleBTPMessage)
will be utilized for sending and receiving the requested arbitrary call payloads. Procols other than BTP has to be wrapped to implement the BMC interface.

Motivation

The motivation of IIP52 applies but to allow dapps to use multiple protocols and multi-protocol verification we need to generalize it beyond BTP. This will allow users and dapps to specify their preferred protocols and security models.

Specification

Message centers

xCall will connect to multiple MCs (message centers) implementing the BMC interface.

Encoding of the Calldata Payload

Encoding and decoding of the calldata are up to the DApps implementation.
xcall only passes the raw byte streams with the predefined method calls.
This is simple and easy to implement from the viewpoint of the xcall, and also the result can be sent back to the caller using the same sendCallMessage interface.

Addressing format

Network Address

A Network address is address representing a account on a specific chain and contains the chain native address prefixed with chain id represented as a string:

"<chainId>/<Address>"

Basic Interfaces

sendCallMessage

DApps need to invoke the following method of xcall on the source chain to send a call message to the destination chain.

/**
 * Sends a call message to the contract on the destination chain.
 *
 * @param _to The network address of the callee on the destination chain
 * @param _sources The contracts that will be used to send the message
 * @param _destinations The addresses of the contracts that xcall will expect the message from.
 * @param _data The calldata specific to the target contract
 * @param _rollback (Optional) The data for restoring the caller state when an error occurred
 * @return The serial number of the request
 */
@Payable
@External
BigInteger sendCallMessage(String _to, String[] _sources String[] _destinations byte[] _data, @Optional byte[] _rollback);

The _to parameter is the network address of the callee contract which receives the _data payload on the destination chain. If a Network address is supplied use the default protocol.

The _data parameter is an arbitrary payload defined by the DApp.

The _sources parameter is a list of addresses representing different connections.

The _destinations parameter is a list of addresses representing the connections of the receiving chain.

The _rollback parameter is for handling error cases, see Error Handling section below for details.

This method is payable, and DApps need to call this method with proper fees, see Fees Handling section below for details.

When xcall on the source chain receives the call request message, it sends the _data to _to on the destination chain through all protocols specified in the _to.

CallMessageSent

When xcall invokes sendMessage the following event is emitted

/**
 * Notifies that the requested call message has been sent.
 *
 * @param _from The chain-specific address of the caller
 * @param _to The The network address of the callee on the destination chain
 * @param _sn The serial number of the request
 */
@EventLog(indexed=3)
void CallMessageSent(Address _from, String _to, BigInteger _sn);

CallMessage

When the xcall on the destination chain receives all the call request through the message centers specified in the call request, it emits the following event for notifying the user.

/**
 * Notifies the user that a new call message has arrived.
 *
 * @param _from The network address of the caller on the source chain
 * @param _to A string representation of the callee address
 * @param _sn The serial number of the request from the source
 * @param _reqId The request id of the destination chain
 */
@EventLog(indexed=3)
void CallMessage(String _from, String _to, BigInteger _sn, BigInteger _reqId);

executeCall

The user on the destination chain recognizes the call request and invokes the following method on xcall with the given _reqId.

/**
 * Executes the requested call message.
 *
 * @param _reqId The request Id
 */
@External
void executeCall(BigInteger _reqId);

handleCallMessage

When the user calls executeCall method, the xcall invokes the following predefined method in the target DApp with the calldata associated in _reqId.

/**
 * Handles the call message received from the source chain.
 * Only called from the Call Message Service.
 *
 * @param _from The network address of the caller on the source chain
 * @param _data The calldata delivered from the caller
 * @param _protocols The contract addresses that delivered the data
 */
@External
void handleCallMessage(String _from, byte[] _data, String[] _protocols);

If the call request was a one-way message and DApp on the destination chain needs to send back the result (or error), it may call the same method interface (i.e. sendCallMessage) to send the result message to the caller.
Then the user on the source chain would be notified via CallMessage event, and call executeCall, then DApp on the source chain may process the result in the handleCallMessage method.

CallExecuted

To notify the execution result of DApp's handleCallMessage method, the following event is emitted after its execution.

/**
 * Notifies that the call message has been executed.
 *
 * @param _reqId The request id for the call message
 * @param _code The execution result code
 *              (0: Success, -1: Unknown generic failure, >=1: User defined error code)
 * @param _msg The result message if any
 */
@EventLog(indexed=1)
void CallExecuted(BigInteger _reqId, int _code, String _msg);

Error Handling

In success cases, DApp users only need to invoke two transactions (one for the source chain, and the other for
the destination chain). However, there might be some error situations such as the execution of the call request
has failed on the destination chain. In this case, we need to notify the user on the source chain to rollback to the state
before the call request.

If a DApp needs to handle a rollback operation, it would fill some data in the last _rollback parameter of the sendCallMessage,
otherwise it would have a null value which indicates no rollback handling is required.

ResponseMessage

For all two-way messages (i.e., _rollback is non-null) the same protocols is used in both directions. When the xcall on the source chain receives a response message from the xcall on the destination chain through all of the protocols it emits the following event regardless of its success or not.

/**
 * Notifies that a response message has arrived for the `_sn` if the request was a two-way message.
 *
 * @param _sn The serial number of the previous request
 * @param _code The response code
 *              (0: Success, -1: Unknown generic failure, >=1: User defined error code)
 * @param _msg The result message if any
 */
@EventLog(indexed=1)
void ResponseMessage(BigInteger _sn, int _code, String _msg);

RollbackMessage

When an error occurred on the destination chain and the _rollback is non-null, xcall on the source chain emits the following event for notifying the user that an additional rollback operation is required.

/**
 * Notifies the user that a rollback operation is required for the request '_sn'.
 *
 * @param _sn The serial number of the previous request
 */
@EventLog(indexed=1)
void RollbackMessage(BigInteger _sn);

executeRollback

The user on the source chain recognizes the rollback situation and invokes the following method on xcall with the given _sn.
Note that the executeRollback can be called only when the original call request has responded with a failure.
It should be reverted when there is no failure response with the call request.

/**
 * Rollbacks the caller state of the request '_sn'.
 *
 * @param _sn The serial number of the previous request
 */
@External
void executeRollback(BigInteger _sn);

Then the xcall invokes the handleCallMessage in the source DApp with the given _rollback data.
At this time, the _from would be the network address of xcall and the protocols will be the sources specified when sending the message.

RollbackExecuted

As with the CallExecuted event above, the following event is emitted after the DApp's handleCallMessage execution
to notify its execution result.

/**
 * Notifies that the rollback has been executed.
 *
 * @param _sn The serial number for the rollback
 * @param _code The execution result code
 *              (0: Success, -1: Unknown generic failure, >=1: User defined error code)
 * @param _msg The result message if any
 */
@EventLog(indexed=1)
void RollbackExecuted(BigInteger _sn, int _code, String _msg);

Fees Handling

If a user wants to make a call from ICON to Target Network 1 (T1), he needs to pay X ICX, and for Target Network 2 (T2), he needs to pay Y ICX.
That is, the fees depend on the destination network address.

The fees are divided into two types, one is for relays and the other is for protocol itself.
For example, for a destination network T1, the fees could be relayFee = 0.25 ICX and protocolFee = 0.01 ICX.
And relayFee goes to relays, protocolFee goes to the protocol (eventually, to the Fee Handler).
In this document, we don't address how to deal with these accrued fees for distribution,but just define operational parts like how to get the proper fee amount before sending the call request, etc.

Here are getter and setter methods for the proper fees handling in xcall.
DApps that want to make a call to sendCallMessage, should query the total fee amount for the destination
network via getFee interface, and then enclose the appropriate fees in the method call.
Note that the protocol fee amount can be get/set via xcall, but the relay fee would be obtained from each MC.

/**
 * Gets the fee for delivering a message to the _net.
 * If the sender is going to provide rollback data, the _rollback param should set as true.
 * The returned fee is the sum of the protocol fee and the relay fee.
 *
 * @param _protocol The protocol used
 * @param _net The network id
 * @param _rollback Indicates whether it provides rollback data
 * @return the sum of the protocol fee and the relay fee
 */
@External(readonly=true)
BigInteger getFee(String _protocol, String _net, boolean _rollback);

/**
 * Sets the protocol fee amount.
 *
 * @param _value The protocol fee amount in loop
 * @implNote Only the admin wallet can invoke this.
 */
@External
void setProtocolFee(BigInteger _value);

/**
 * Gets the current protocol fee amount.
 *
 * @return The protocol fee amount in loop
 */
@External(readonly=true)
BigInteger getProtocolFee();

/**
 * Sets the address of Fee Handler.
 * If _addr is null (default), it accrues protocol fees.
 * If _addr is a valid address, it transfers accrued fees to the address and
 * will also transfer the receiving fees hereafter.
 *
 * @param _addr The address of Fee Handler
 * @implNote Only the admin wallet can invoke this.
 */
@External
void setProtocolFeeHandler(@Optional Address _addr);

/**
 * Gets the current protocol fee handler address.
 *
 * @return The protocol fee handler address
 */
@External(readonly=true)
Address getProtocolFeeHandler();

Implementation

References

Copyright

Copyright and related rights waived via CC0.

AntonAndell/btp2-java#1
Did a quick draft of the changes proposed in this doc. However i imagine XCall should move out of the BTP2 -java repo for theses changes. But easier to visualize when compared to the previous xCall implementation.

Some queries i'd like to get discussed are:

  • What are the benefits of using string manipulation for addressing, does this change make it to complicated?
  • Decentralization or trustlessness is hard to achieve with a admin that adds protocols. We could potentially just use abritrary addresses as protocols instead of strings. Since the Dapp need to verify protocol in this implementation anyway, does it make sense to just use addresses, so anyone could add a connection to xCall without any admin.
  • How to achieve constant relay fee? How important are small differences when checking if a message has been received by all protocols, and is there any way we can avoid differences in fees for relays.

To add to my second point about decentralization.
The idea would be to have users add protocols themselves in DApp proxies, but now we are moving further away for the current xCall design. But could be solved by a default DApp proxy.
Here is a quick example on how it could be structured.

/**
 * Sends a call message to the contract on the destination chain.
 *
 * @param _to The network address of the callee on the destination chain
 *  @param _connections List of addresses to send the message
 * @param _destinations List of addresses that are required to relay the message to the destination xCall in order to be valid.
 * @param _data The calldata specific to the target contract
 * @param _rollback (Optional) The data for restoring the caller state when an error occurred
 
 * @return The serial number of the request
 */
@Payable
@External
BigInteger sendCallMessage(String _to, Address[] connections, Byte[][] destinations, byte[] _data, @Optional byte[] _rollback);

Then one could build DApp Proxies on the format.

@Payable
@External
BigInteger sendCallMessage(String _to, byte[] _data, @Optional byte[] _rollback) {
    xCall.sendCallMessage(_to, protocols.get(to.net).connections(), protocols.get(to.net).destinations(), _data, _rollback);
}

This would require no interaction from a admin to add or edit a connection and leaves that completely up to the users.

Why are sendCallMessage sources, destinations, and handleCallMessage protocols arrays? Btw, there is a typo on destinations

Why are sendCallMessage sources, destinations, and handleCallMessage protocols arrays? Btw, there is a typo on destinations

Only the contracts on the chain itself would be relevant to the security. And when sending messages those would be the destinations of the message and when doing rollbacks it would be the sources. But for one chain the sources of a rollback would be the destinations of a message in most cases. So named that differently, however, i can agree the mixing of protocols and sources/destinations can be confusing, and naming can be improved.