Sending a message with xCall (with rollback)

Prerequisites

For the case of showcasing sending a message without rollback please follow the prerequisites and instructions on the previous tutorial.

Rollback

The rollback functionality of xCall allows the developer to design specific situations in their smart contract DApp on which the process can fail, examples of this can be not having enough balance of a certain token locked in the destination chain to be able to mint some other token in the case of doing a cross chain transaction.

The developer is free to setup their own fail conditions, and once they are trigger is necessary to call the native 'revert' handle of the execution environment to initiate the rollback process.

For the case of the example shown in the jvm-jvm demo (opens in a new tab), we can see that the smart contract calls the Context.require() (opens in a new tab) handler when the data in the message is equal to the string literal "rollback", this is a simple example showcasing a condition to trigger a designed fail condition (if the data in the _rollback parameter of the original transaction is equal to the string "rollback").

    @External
    public void handleCallMessage(String _from, byte[] _data) {
        onlyCallService();
        String rollbackAddress = Context.call(String.class, this.callSvc, "getNetworkAddress");
        Context.println("handleCallMessage: from=" + _from + ", data=" + new String(_data));
        if (rollbackAddress.equals(_from)) {
            return;
        } else {
            Context.require(!new String(_data).equals("rollback"), "failed");
            // normal message delivery
            MessageReceived(_from, _data);
        }
    }

calling the Context.require() handler of the specific execution environment stops the execution and refunds any unused gas (Context.revert() can also be used).

ResponseMessage and RollbackMessage events

For two way communication using xCall (whenever the _rollback param is not null) there are 2 events associated that can be raised.

A ResponseMessage event will be raised in the source chain every time the _rollback param is not null regardless of if an error occurred or not.

/**
 * Event emitted for all two-way communications
 * @param _sn message ID
 * @param _code execution result code (1: Success, 0: Failure)
 */
@EventLog(indexed=1) void ResponseMessage(Integer _sn, Integer _code)

A RollbackMessage event will be raised in the source chain when the _rollback param is not null and an error has occurred (a revert has been called either with Context.revert() on JVM or revert on EVM chains).

/**
 * Event emitted when an error has occurred.
 * @param _sn message ID
 */
@EventLog(indexed=1) void RollbackMessage(Integer _sn)

The demo dapp implements a simple block monitor that you can use to fetch the ResponseMessage and RollbackMessage event (and the rest of the events related to a cross chain message sent with xCall).

Calling ExecuteRollback

Once the RollbackMessage event has been raised, confirming that a rollback operation is required we can execute the rollback by calling the executeRollback method of the xCall contract on the source chain.

/**
 * Rollbacks the caller state of the request '_sn'.
 * @param _sn message ID
 */
external executeRollback(BigInteger _sn);

The following function showcases how this transaction is being signed in our xcall sample dapp.

async function executeRollbackICON(id) {
  try {
    const wallet = ICON_SIGNER;
    const params = {
      _sn: id.toHexString()
    };
 
    const txObj = new CallTransactionBuilder()
      .from(wallet.getAddress())
      .to(ICON_XCALL_ADDRESS)
      .stepLimit(IconConverter.toBigNumber(2000000))
      .nid(IconConverter.toBigNumber(ICON_RPC_NID))
      .nonce(IconConverter.toBigNumber(1))
      .version(IconConverter.toBigNumber(3))
      .timestamp(new Date().getTime() * 1000)
      .method("executeRollback")
      .params(params)
      .build();
 
    const signedTransaction = new SignedTransaction(txObj, wallet);
    console.log("## executeRollback signed transaction");
    console.log(signedTransaction.getRawTransaction());
    return await ICON_SERVICE.sendTransaction(signedTransaction).execute();
  } catch (e) {
    console.log("Error running executeRollback");
    throw new Error(e);
  }
}
CTRL + M