Sharing Information Between Antelope.IO Blockchains
By: Phil Mesnier, Partner, Blockchain Practice Lead
With editorial contributions from Brian Johnson and Jason Schindler
May 2023
Inter-Blockchain Communication using Antelope.IO
This is a survey of the new Inter-Blockchain Communications (IBC) feature of the Antelope/leap blockchain platform. In the following paragraphs we will describe IBC and Antelope in general, cover details of this protocol and the provided token transfer utility, and we will work through an example token transfer that illustrates how to use IBC.
This IBC protocol implementation was created by Guillaume “Gnome” Babin-Tremblay of the UX Network blockchain and documented by Fabiana Cecin, also part of the UX Network team. I would like to express deep gratitude to Gnome and Fabiana for their patience and thoughtful replies to my questions.
About Antelope/leap
Antelope.IO is a blockchain platform which is the descendant of the EOSIO platform. The EOS Network Foundation (ENF) took responsibility for the code from its founder, Block.one. Leap is the name given to the C++ version of the core libraries and applications such as nodeos, keosd, cleos, etc. The open source nature of the EOSIO and later Antelope.IO/leap codebase has led to the creation of several alternative chains.
To ensure the ENF continues with the community’s best interests in mind when making implementation plans, the Antelope.IO package is managed by a coalition of blockchains including EOS, Telos, UX, and Wax. This coalition meets regularly and uses voice voting to approve changes to the foundation’s code base.
Each of these blockchains, and indeed many others, have various features that differentiate them even though they share the same core.
Motivation for IBC
Now that there are several distinct chains that are otherwise very similar, there are many cases where users of any one of these chains may wish to conduct business with a user of a different chain. This might include passing value between users by way of token transfer, or Dapp implementation using smart contracts on various chains.
The primary requirement to address is that of verification that actions related to a chain-jumping transaction are complete, valid, and irreversible. The secondary requirement is for a means to communicate the action details to the companion chain in order to complete the required transaction.
Mechanics of Antelope IBC
Antelope IBC was developed by Gnome at UX Network, who is a voting member of the Antelope coalition. The key component of his solution is a smart contract referred to as the “bridge.”
You can find the official documentation of the IBC protocol by visiting the documentation page. A reference implementation of a chain-spanning token delivery mechanism is also provided as is a simple server that is able to assist with the forwarding of the proof objects needed to perform the necessary validations. Links to these various software packages are listed at the end of this document.
The bridge contract maintains consensus models for all associated peer chains, so only one bridge contract needs to be active on a given native chain. The model of the peer chains includes the details necessary to compute block digests as part of the process to verify actions. The bridge provides a series of “check proof” actions that vary in the arguments but perform similar tasks. These actions take block proof objects and action proof objects.
The Antelope IBC package includes a “proof server” which serves as a helper to a node on the peer blockchain you are interested in communicating with. The included proof server is able to provide json formatted proof objects which your application can use to enable verification on peer chains. The proof server is connected to the blockchain node via two services, the first is a “lightproof\_db” server, and the second a high performance block data streamer such as firehose, SHiP, or Greymass.
So far the environment consists of a pair of blockchains, let’s call them native and foreign. Each chain is running the bridge contract and there is at least one proof server ingesting block data for each chain. These components are necessary for IBC but not sufficient. There needs to be contracts deployed on both chains that include IBC-aware actions. Finally the client application needing to invoke a cross chain transaction has to coordinate the exchange through a series of steps.
- The client application initiates the process by invoking an IBC aware action on the native chain. This invocation is recorded by the blockchain in a transaction in a block. If the information to be forwarded to the foreign chain is the result of some transformation, a subsequent inline action must be invoked with a parameter list containing the same parameters as the foreign chain’s action. This inline action doesn’t need to do anything, its name and parameter list are recorded in the transaction. That will make them available to the contract on the foreign chain as part of the action proof supplied by the client application.
- The client application next interrogates the proof server on the native blockchain. When a transaction ID or block number is supplied, the proof server will provide the action and block proof objects in JSON format.
- Next the client application invokes the appropriate “check proof” action on the foreign chain’s bridge, providing the proof objects retrieved from the native blockchain. Assuming the proof objects are valid, the foreign bridge’s model of the native blockchain will be updated to include awareness of the native chain’s execution of the action.
- To complete the transferal of state from the native chain, an action on the appropriate IBC-aware foreign chain contract must be invoked. If the use case allows it, this contract may be constructed to take the proof objects as arguments and can invoke the bridge’s check proof directly, combining step 3 and 4 in a single step.
The foreign chain’s contract can signal completion of the transaction by triggering an inline “completion” action inside step 1. Then steps 2 through 4 are processed with the chain roles reversed, retrieving the proof objects from the foreign chain and supplying them to the native chain’s bridge.
An Example Usage
The creators of Antelope IBC have provided the means of transferring arbitrary tokens from the native chain to the foreign chain. The “wraplock” and “wraptoken” contracts work together to transport eosio.token compatible tokens from a provider account on the native chain to a receiver account on the foreign chain. This is accomplished by sequestering the specified quantity of tokens on the native chain, hence the name “wraplock.” Then on the foreign chain the wraptoken contract mints the specified quantity of the native tokens which it holds until they are withdrawn.
The wraptoken contract has the means to return any quantity of those tokens to the native chain where the wraplock contract will release the returned quantity back into circulation. This is a directional relationship. If a contract on the foreign chain needs to initiate token transfers, it can do so by using a wraplock contract on the foreign chain and a wraptoken instance on the native chain.
- Trigger a transfer of some “nativetokens” from source to “xfertopaired” with “tokentaker” as the memo.
- Wraplock contract notified of transfer, locks the quantity of “nativetoken” tokens, invokes inline action “emitxfer.”
- Client app queries native proof server for block and action proof related to the wraplock emitxfer action.
- Client app supplies block and action proofs to “xferreceiver” on the paired blockchain.
- Wraptoken contract invokes checkproof action on bridge.
- Wraptoken mints the appropriate quantity of wrapped “nativetokens” and initiates a transfer to “tokentaker” contract
A More Detailed Look
Now that we have presented a high-level view of the participants of IBC, we can now take a deeper look into these elements. This section will cover the bridge contract actions, the proof server API, and the various proof objects involved.
The Bridge Contract
Before it can be used to track the state of another blockchain, the bridge must be initialized with details from that other chain. This list shows the information required to initialize the bridge for a new chain.
- Chain name. The name is a descriptive identifier.
- Chain ID. The checksum256 value that uniquely identifies a given instance of the blockchain.
- Return_value_enabled. The antelope coalition defined as a feature the ability to return values from contract actions. This modified the way block and action digests are computed. For this reason, the bridge needs to know at what block number was the first one when this feature was activated.
- Producer schedule. The current producer schedule includes the signing keys for the active producers, which the bridge needs to validate the block data. There are two versions of the producer schedule.
Since the EOS smart contract ABI format does not support overloading of action names, there are two initializer actions, inita() takes the first version of the schedule, and initb() which takes the second.
Once the bridge is initialized for a given peer chain, there may be times when the bridge to that peer must be suspended. This can be accomplished by invoking the disable() action, supplying the chain name. Once it is time to allow data to flow, the enable() action, which takes the chain name, resumes access.
Before we examine the various “check proof” actions, let’s look at the proof objects used by the bridge to track the peer chain’s state.
- Heavy block proof. This is used to update the bridge’s model of the peer chain, containing the necessary info for computing merkel tree values. Heavy proofs are retrieved from the peer chain’s proof server.
- Light block proof. Unlike the heavy proof, the light proof is used to validate actions based on older blocks in the chain.
- Action proof. This proof object carries the state information for the action that is involved with the cross chain transaction.
The bridge defines six variances of the “check proof” action, three to be called by external applications and three that are for inline invocations. Within these groups, there are variants that take a heavy or light block proof along with an action proof and one that takes only a heavy proof object. These first three variants are for inline use from contracts that internally cache block proof objects so the bridge only needs the calling contract name with which it is able to obtain the desired block proof object.
- checkproofa(name contract) Retrieves a heavy block proof from the named contract and uses that to update the state model only.
- checkproofb(name contract, actionproof action) Retrieves a heavy block proof from the named contract and uses that to update the state model and also to validate the action.
- checkproofc(name contract, actionproof action) Retrieves a light block proof from the named contract and uses that to validate the action.
The remaining variants are intended to be invoked by external entities that provide the appropriate block proof directly.
- checkproofd(heavyproof blockproof) Uses the supplied block proof to update the state model of the peer chain.
- checkproofe(heavyproof blockproof, actionproof action) Updates the state model and validates the action.
- checkprooff(lightproof blockproof, actionproof action) Validates the action.
The proof server
As the process described above, this proof server is a demonstrator only, not a formal part of the package. That is because it is anticipated that applications will use customized implementations that may be more efficient or optimized to a particular use case, or whatever. That said, the proof server used here provides an API for application use that uses web sockets as a communication protocol and provides the following calls. The source can be found here.
- getHeavyProof with argument block_height. Returns a heavy block proof object.
- getLightProof with argument block_height. Returns a light block proof object.
- getActionProof with arguments block_height and action_receipt_digest. Returns an action proof. The block_hight is the number of the block containing your action details and the action_receipt_digest is the “act_digest” value found in the action receipt provided by the api node when your action is processed.
The resulting proof objects are suitable for use as input to the bridge on the paired chain.
The Example WrapLock Contract
This contract is provided as an example source for information to transfer via IBC to a foreign chain. This is also a useful tool if you have a need to share eosio.token compatible tokens with a recipient account existing on a different blockchain. As described above, the wrap lock contract works with the wrap token contract on the foreign chain. The wraplock contract has several actions involved with transferring tokens.
- init(chain_id, bridge, paired_chain), Initializes the contract’s global attributes. The chain_id is the checksum256 chain_id for the native chain. The bridge is the account name for the bridge contract managing state for the desired paired_chain. The paired\chain value is the checksum256 chain_id for the foreign chain this wraplock will work with.
- deposit(to, from, asset, memo), invoked by the infrastructure whenever any token’s transfer(to, from, asset, memo) is triggered. The “to” argument is the name of the wraplock contract account and the memo contains the account name of the recipient on the foreign chain.
- emitxfer(xfer), an otherwise empty action that takes a structure as an argument. This action and its arguments are recorded in the transaction data which is propagated in the bridge contracts actionproof object.
The wraplock contract is bound to a particular foreign blockchain when it is initialized. This means that a separate wraplock account is needed for each target chain. Once the wraplock instance is initialized, the eosio::on_notify attribute is used to trigger its own deposit() action whenever a transfer action is invoked on any other contract with the wrap lock contract account as the “to” account, allowing it to process the transfer request. The deposit action validates the request, then sequesters the amount of tokens to be transferred.
Within the deposit action, an internal action, emitxfer(), is triggered with arguments appropriate for the corresponding wraptoken account. At this point the native chain has done its work. At this point the client application that triggered the transfer retrieves the associated block proof and action proof from the native chain and passes it to the foreign chain bridge or wraptoken contract.
The WrapToken contract
This is the contract on the foreign chain which is responsible for minting a quantity of tokens corresponding to the amount sequestered by the wraplock contract. Like the wraplock contract, wraptoken must be initialized before it can be used. Unlike wraplock, an instance of wraptoken supports only a single token type. Therefore you need to deploy one wrap token instance per token type per paired chain you expect it to receive token distribution orders.
The wraptoken API is intended to be invoked by external entities, and supplies two versions of the “issue” action.
- issuea() takes a heavyblockproof and an actionproof. These are the proof objects received from the native chain’s proof server after the wraplock contract handled a deposit action. These proof objects are provided to the local bridge object by the wraptoken.
- issueb() takes a lightblockproof and an actionproof. Other than the nature of the block proof object this issue action behaves just like the other.
Internally, the wraptoken::issue[ab]() actions will first validate some context, then will engage the bridge to update its state using the supplied proof objects. Assuming that is all good, the wraptoken contract will “mint” new wrapped tokens by adding the quantity of wrapped native tokens to a table entry for the benefactor’s account. The quantity is part of the data found inside the action proof.
The wraplock and wraptoken contracts also support returning tokens from the recipient to the original sender up to the amount originally issued. If there is to be an arbitrary transfer of tokens from an account on the foreign chain to any account on the native chain, there needs to be a separate pair of wraplock and wraptoken contracts deployed from the foreign chain to the native.
Conclusion
This document was intended to highlight the new inter-blockchain communication feature available on AntelopeIO based blockchains. In it we covered the new contracts and external tools that interact to facilitate these communications. Hopefully you now have an understanding of the nature of IBC, that it is mainly concerned with sharing blockchain consensus between paired chains by way of the bridge objects. The example used was provided by the implementers of the IBC tools and are intended to demonstrate the capabilities of this tech.
References
The following links were an invaluable source of information used while developing this document.
- https://t.me/antelopeIBC Telegram group, Antelope IBC Protocol
- https://ibc-docs.uxnetwork.io/ The Antelope IBC documentation
- https://github.com/AntelopeIO/cdt.git The Antelope Contract Development Toolkit documentation from github repository.
- https://github.com/AntelopeIO/reference-contracts.git The Antelope reference contracts github repository, for eosio.token source.
The remaining links on this list are the github repositories of the various components of the IBC service and the example contracts.
- https://github.com/CryptoMechanics/eosio-ibc-bridge-contract.git
- https://github.com/CryptoMechanics/ibc-proof-server.git
- https://github.com/CryptoMechanics/ibc-reference-ui-html.git
- https://github.com/CryptoMechanics/lightproof-db.git
- https://github.com/CryptoMechanics/wraplock.git
- https://github.com/CryptoMechanics/wraptoken.git
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.