This document provides technical details of the Interledger component:
- Detailed description of the Interledger Component itself
- Description of `Ledger Interfaces`_ that must be implemented in the application smart contracts utilising the Interledger
- Instructions about Extending Interledger to Support Additional Ledgers
The Interledger component consists of the following parts as shown in Figure 1:
- One or more Initiator adapters, which are responsible for communication with the Initiator ledger
- One or more Responder adapters, which are responsible for communication with the Responder ledger
- The Interledger core, which is responsible for relaying data between the Initiator and Responder
Figure 1: Internal structure of the Interledger module
Each Interledger instance forms a unidirection connection from one Initiator ledger to one or more Responder ledger(s). In order to support bidirectional communication between two ledgers, two Interledger instances in opposite directions must be started as described below.
Interledger supports both one-on-one transactions that connect an Initiator ledger with a single Responder ledger as shown in Figure 2, and multi-ledger transactions, where an Initiator ledger connects to multiple Responder ledgers as shown in Figure 3. Multi-ledger transactions require that either all-N Responder ledgers succeed with the transaction or that only k-out-of-N Responder ledgers succeed depending on the configuration chosen.
Figure 2: One-on-one transactions connect an Initiator with one Responder.
Figure 3: Multi-ledger transactions connect an Initiator with multiple Responders
The Initiator adapter interface is responsible for catching the InterledgerEventSending() events from an Initiator ledger and invoking the interledgerCommit() and interledgerAbort() ledger functions depending on the result of the Responder ledger.
The functions provided by Initiator adapter are:
listen_for_events: waits for events coming from the connected ledger;commit_sending: calls theinterledgerCommit()ledger function to finalise a transfer;abort_sending: calls theinterledgerAbort()ledger function to terminate a transfer;
The Responder adapter interface receives transfer requests from the core and is responsible for relaying the transfer to the Responder ledger by executing the interledgerReceive() ledger function in a one-on-one transaction, or first executing the interledgerInquire() function to check that sufficiently many Responders will accept the transaction before executing the interledgerReceive() function to commit the multi-ledger transaction.
The functions provided by Responder is:
send_data_inquire: inquires the Responder ledger's ability to succesfully complete asend_data(used as the first phase of a two phase commit with multi-ledger transactions); listens for theInterledgerInquiryAccepted()andInterledgerInquiryRejected()events as possible replies.send_data: receives a transfer request and calls theinterledgerReceive()function of the Responder ledger (used in one-on-one transactions and as the second phase of a two phase commit with multi-ledger transactions); listens for theInterledgerEventAccepted()andInterledgerEventRejected()events as possible replies.abort_send_data: rolls back a transaction after a failed round ofsend_data_inquires(used as the second phase of a failed two phase commit with multi-ledger transactions); listens for theInterledgerEventRejected()event as the only possible reply.
The functions commit_sending, abort_sending and send_data have the following dictionary as return value:
{
'status': bool,
'tx_hash': str,
'exception': object,# only with errors
'error_code': Enum, # only with errors
'message': str # only with errors
}During the development of the Decentralized Interledger (DIL), the concept of Interledger state management has been introduced: it enables a shared state for coordinating the multiple nodes of DIL, but it can also be used for faster crash recovery in single-node installations. Interledger state manager is the adapter that interacts with the different types of state management layers for the creation and update of transaction entries that are accessed by participating nodes.
The functions provided by a state adapter are:
create_entry: create entry of transaction in the state layer;signal_send_acceptance: update the state layer a transfer corresponding to a given id will be carried out by this IL node;update_entry: update the transfer entry status, and optionally its content in state layer;receive_entry_events: Check emmited events from state layer about transfers that become ready or responded.
The Transfer object is a data structure that contains the data necessary to perform the data transfer. When an event is caught, the Initiator creates a Transfer object and this objects will be modified and processed until the data transfer it handles will be finalized or aborted. Figure 2 shows the flow of the Transfer object between the Initiator and the Responder.
A Transfer object includes a python future object which stores the asynchrounous call to the Responder.send_data(), which triggers the interledgerReceive() function. As soon this call terminates and the future object has a result:
- if it is positive, i.e. the
interledgerReceive()transaction was successful andInterledgerEventAccepted()was received, the Interledger will call theInitiator.commit_sending()of the Initator; - otherwise, Interledger will call the
Initiator.abort_sending()of the Initator.
The Interledger module functions as the core of the Interledger component.
The Interledger module creates a bridge from a ledger A to a ledger B by instantiating a Initiator listening for events coming from ledger A and executing transactions to ledger B by instantiating a Responder. To handle transfers from ledger B to ledger A, simply instantiate a second Interledger class with Initiator connected to ledger B and Responder connected to ledger A.
The functions provided by Interledger are:
receive_transfer: callsInitiator.get_trasfers()to catch events, and generate a random nonce for them. This function is blocking;send_inquiry: calls theResponder.send_data_inquire()to check whether the Responder ledger smart contract will accept the incoming data;transfer_inquiry: gets the inquiry answer back to the Interledger internal state;send_transfer: if there are available events, calls asynchronouslyResponder.receive_transfer();transfer_result: waits for completed results fromResponder.receive_transfer(). This function is blocking;process_result: if there are available results,commit_transfer()orabort_transfer()the transfer according to the result.
The interledger exposes the run() operation to start the loop flow.
Example of loop step:
while True:
receive = self.receive_transfer()
result = self.transfer_result()
await receive or result # wait for an event or process pending ones
send = self.send_transfer()
process = self.process_result()
await send # send events to Responder, if any
await process # process accepted eventsThe figure 4 below shows a visual representation of a transfer between ledgers:
- The Initiator starts listening for
InterledgerEventSendingoperations from LedgerA; - When the Initiator catches one, it builds a
transferto send to the Interledger; - For each incoming transfer Interledger generates a random nonce and forwards the transfer to the Responder;
- The Responder calls the
interledgerReceive()function to send the data to the Ledger B; - If the Responder receives
InterledgerEventAccepted()event from Ledger B as a result of function call in step 4, the Responder sets thattransfer.result["status"]status as "True";
- The Interledger loops over the pending transfers and, if a transfer result has its status set to "True", calls the
commit_sending()function of the Responder;- The Responder will finalise the transfer by calling the
interledgerCommit()function in Ledger A;
- If on the other hand the function call is step 4 fails (transaction fails, transaction results in
InterledgerEventRejected(), or noInterledgerEventAccepted()is received), the Responder the transfer's status to "False";
- The Interledger loops over the pending transfers and, if a transfer result has its status set to "False", calls the
abort_sending()function of the Responder;- The Responder will abort the transfer by calling the
interledgerAbort()function in Ledger A;
The red and blue colors identify the caller of the transaction to a specific ledger, the caller is responsible for paying the transaction fee.
Figure 4: Interledger protocol
In order to utilise the Interledger component, InterledgerSenderInterface and InterledgerReceiverInterface must be implemented by the application on the respective ledgers by e.g. using smart contracts for Ethereum or chaincode for Hyperledger Fabric. Ethereum example for sender and receiver interfaces are provided in the contracts directory.
The example implementations of ledger interfaces include DataTransceiver, GameToken, and HTLCEth. The detailed description about how the Interledger component is using ledger interfaces follows in Details of Interledger Protocol section.
The example implementations of ledger interfaces for Hyperledger Fabric are found under chaincode folder.
The sender interface contains the following events and functions:
event InterledgerEventSending(uint256 id, bytes data); function interledgerCommit(uint256 id) public; function interledgerCommit(uint256 id, bytes memory data) public; function interledgerAbort(uint256 id, uint256 reason) public;
event InterledgerEventSending(uint256 id, bytes data) initiates the Interledger protocol. The id is the apllication smart contract's internal identifier for the event that the application can choose at will. The id does not have to be unique, so the same id can e.g. be used for similar transaction or each transaction can have a unique id. The data is the data to be sent to another ledger. Interledger does not process the data in any way, so it's up to the application to encode the data and e.g. Solidity's abi.encode() function can be used to encode any data structure inside the data parameter.
function interledgerCommit(uint256 id) is be called by Interledger to conclude a successful Interledger transaction. The id parameter is the same as in InterledgerEventSending event. In some special cases, the overloaded function interledgerCommit(uint256 id, bytes memory data) function is called instead: when storing hashes to KSI ledger, a new KSI signature id is generated, and this id will be relayed to the Initator ledger using the data parameter.
function interledgerAbort(uint256 id, uint256 reason) will be called by Interledger to conclude a failed Interledger transaction. The id parameter is the same as above, while the reason is ErrorCode defined in interfaces.
The receiver interface contains the following functions and events:
function interledgerReceive(uint256 nonce, bytes memory data) public; event InterledgerEventAccepted(uint256 nonce); event InterledgerEventRejected(uint256 nonce);
function interledgerReceive(uint256 nonce, bytes memory data) is called by the Interledger component to relay data to the destination ledger. The nonce is a nonce chosen by the Interledger component (it is used to internally keep track of each transaction even if the application chosen ids are not unique), while data is the data received from the sender.
The application smart contract should then emit either the event InterledgerEventAccepted(uint256 nonce) or event InterledgerEventRejected(uint256 nonce) depending on whether it wants to accept the incoming data or not. The nonce parameter in the emitted event must match the received nonce.
In order to utilise the Interledger component, InterledgerSenderInterface and InterledgerReceiverInterface must be implemented by the application on the respective ledgers by e.g. using smart contracts for Ethereum or chaincode for Hyperledger Fabric. Ethereum example for sender and receiver interfaces are provided in the contracts directory.
The example implementations of ledger interfaces include DataTransceiver, GameToken, and HTLCEth. The detailed description about how the Interledger component is using ledger interfaces follows in `Detailed Overview of Interledger Protocol`_ section.
The example implementations of ledger interfaces for Hyperledger Fabric are found under chaincode folder.
The sender interface contains the following events and functions:
event InterledgerEventSending(uint256 id, bytes data); function interledgerCommit(uint256 id) public; function interledgerCommit(uint256 id, bytes memory data) public; function interledgerAbort(uint256 id, uint256 reason) public;
event InterledgerEventSending(uint256 id, bytes data) initiates the Interledger protocol. The id is the apllication smart contract's internal identifier for the event that the application can choose at will. The id does not have to be unique, so the same id can e.g. be used for similar transaction or each transaction can have a unique id. The data is the data to be sent to another ledger. Interledger does not process the data in any way, so it's up to the application to encode the data and e.g. Solidity's abi.encode() function can be used to encode any data structure inside the data parameter.
function interledgerCommit(uint256 id) is be called by Interledger to conclude a successful Interledger transaction. The id parameter is the same as in InterledgerEventSending event. In some special cases, the overloaded function interledgerCommit(uint256 id, bytes memory data) function is called instead: when storing hashes to KSI ledger, a new KSI signature id is generated, and this id will be relayed to the Initator ledger using the data parameter.
function interledgerAbort(uint256 id, uint256 reason) will be called by Interledger to conclude a failed Interledger transaction. The id parameter is the same as above, while the reason is ErrorCode defined in interfaces.
The receiver interface contains the following functions and events:
function interledgerReceive(uint256 nonce, bytes memory data) public; event InterledgerEventAccepted(uint256 nonce); event InterledgerEventRejected(uint256 nonce);
function interledgerReceive(uint256 nonce, bytes memory data) is called by the Interledger component to relay data to the destination ledger. The nonce is a nonce chosen by the Interledger component (it is used to internally keep track of each transaction even if the application chosen ids are not unique), while data is the data received from the sender.
The application smart contract should then emit either the event InterledgerEventAccepted(uint256 nonce) or event InterledgerEventRejected(uint256 nonce) depending on whether it wants to accept the incoming data or not. The nonce parameter in the emitted event must match the received nonce.
Extending the Interledger component to support additional ledgers consists of two steps:
- create new classes implementing the
Initiatorand/orResponderinterfaces defined in the interfaces file, and - add a new ledger type to the
LedgerTypeclass defined in the same file.
As mentioned above, Initiator and Responder classes handle the communication with the ledgers/application smart contracts. the Initiator class must implement the listen_for_events, commit_sending, and abort_sending functions, while the Responder class must implement the send_data_inquire send_data functions.
Due to their design, it may not be possible to use all ledgers as both Responder and Initiator. For example, since KSI lacks ability to emit events, it cannot be used as the Initiator, only as the Responder. In such cases, start_interledger.py script must be modified to enforce such limitations.



