Communication

The open Lisk communication architecture is based on IPC Unix Sockets and WebSocket (WS). The Application can be configured to either expose an IPC or a WS API that can be used by internal components such as modules and plugins, as well as by any external service like other scripts in JS, a tool in Rust, or a Python daemon. The Lisk SDK offers two different RPC API modes: ipc (Inter-process communication) and ws (WebSocket). If the node and the frontend are running on the same machine, it is recommended to use the ipc mode. To enable the API to be reachable remotely, use the ws option.

For more information about the configuration of the RPC endpoints, check out the configuration guide.
communication architecture

The API client (external communication)

The API client simplifies sending API requests to a blockchain application via IPC or WS.

It can be imported in any JS client application.

It provides an interface to subscribe to all events and to invoke actions of the blockchain application and its' modules & plugins.

To conveniently communicate with a blockchain application, use the apiClient which is included in the @liskhq/lisk-client and the lisk-sdk packages.
  • WS API client example

  • IPC API client example

const { apiClient } = require('@liskhq/lisk-client');
let clientCache;

const getClient = async () => {
  if (!clientCache) {
    clientCache = await apiClient.createWSClient('ws://localhost:8080/ws');
  }
  return clientCache;
};

const apiRequest = async () => {
  const client = await getClient();
  const blockAtHeight123 = await client.block.getByHeight(123);
  client.subscribe('app:block:new', ( data ) => {
    console.log('new block:',data);
  });
  return blockAtHeight123;
};

apiRequest().then((val) => {
  console.log('val:',val);
});
const { apiClient } = require('lisk-sdk');
let clientCache;

const getClient = async () => {
    if (!clientCache) {
        clientCache = await apiClient.createIPCClient('~/.lisk/my-app');
    }
    return clientCache;
};

const apiRequest = async () => {
  const client = await getClient();
  const blockAtHeight123 = await client.block.getByHeight(123);
  client.subscribe('app:block:new', ( data ) => {
    console.log("new block:",data);
  });
  return blockAtHeight123;
};

apiRequest().then((val) => {
  console.log("val:",val);
});

Channels

All modules and plugins have access to a channel to communicate with the application via actions and events.

Channel for modules

The channel in modules has only one purpose: it allows a module to publish events to the application which were defined in the Events property of the module.

The channel is accessible inside of a module under this._channel. It is used especially in the lifecycle-hooks, to publish the events of the module.

The following function is available for a channel inside a module:

  • publish(eventName: string, data?: object): Publishes an event.

An example how to use the channel to publish an event is shown below:

this._channel.publish('hello:newHello', {
  sender: transaction._senderAddress.toString('hex'),
  hello: helloAsset.helloString
});

The above code example will publish the event hello:newHello to the application, and attach an object which is containing the sender address and the hello message of the last sent hello transaction.

Channel for plugins

The channel is used inside of the load() function of a plugin.

The following functions are available for a channel inside a plugin:

  • publish(eventName: string, data?: object): Publishes an event.

  • subscribe(eventName: string, cb: EventCallback): Subscribes to an event.

  • once(actionName: string, cb: EventCallback): Executes the callback only once, when receiving the event for the first time.

  • invoke(actionName: string, params?: object): Invokes an action.

channel.subscribe('app:block:new', ({ data }) => {
    const decodedBlock = this.codec.decodeBlock(data.block);
    this._knownTimestamps.push(decodedBlock.header.timestamp);
    channel.publish('myPlugin:timestamp', { timestamp: decodedBlock.header.timestamp });
});

Aliases

Events and Actions are identified by their alias.

Example alias:

"monitor:getTransactionStats"

The alias always consists of the following parts:

  1. Prefix: Consists of the module or plugin name that provides the respective action or event. Equals app if it’s an application event or action. The prefix monitor in this example is referring the the Monitor plugin.

  2. Separator: Prefix and suffix are always separated by a colon :.

  3. Suffix: The respective name of the event or action.

Interfaces

A blockchain application communicates via Actions and Events which can be invoked (actions) or subscribed to (events) via Remote-Procedure-Calls (RPCs).

The different components of the application each have access to different parts of these interfaces. This is summarized in the following table.

A green..

  • …​ reply means, the component can reply to this kind of RPC request.

  • …​ invoke means, the component can invoke this kind of RPC request.

  • …​ publish means, the component can publish events.

  • …​ subscribe means, the component can subscribe to events.

sdk interfaces

Actions

Actions are invoked to receive specific data from the blockchain application. Actions are part of the request / response API, and are invoked via Remote-Procedure-Calls (RPCs).

The following components can expose actions:

The following components can invoke actions:

  • Plugins

  • External services/applications

How to invoke actions

The first argument is always the alias. If input data is required, it is provided as a second argument.

  • API client

  • Channel

Actions can be invoked by The API client (external communication).

How to invoke different kind of actions with the API client
const data = await client.invoke('app:getSchema'); (1)
const data = await client.invoke('app:actionName', input); (2)
client.invoke('monitor:getTransactionStats').then((val) => { (3)
    console.log(val);
});
1 How to invoke an action.
2 How to invoke an action that needs some input data.
3 Example of how to invoke an action of the monitor plugin.

Actions can be invoked by plugins with the Channel for plugins.

How to invoke an action inside a plugin
this._nodeInfo = await this.channel.invoke("app:getNodeInfo");

Events

If an event is published it is immediately received by all of the subscribers of the event. Events are part of the public publish / subscribe API of a blockchain application.

The following components can publish events:

The following components can subscribe to events:

  • Plugins

  • External services / applications

How to publish and subscribe to events

Events are published inside lifecycle hooks of the module. The channel is available inside the lifecycle hooks, which offers the possibility to subscribe and publish to events, as well as invoking actions in the network.

Publishing an event
channel.publish('pluginAlias:timestamp', { info: 'sample' });

Example for subscribing to an event by utilizing The API client (external communication):

Subscribing to an event
client.subscribe('pluginAlias:timestamp', ( data ) => {
  console.log(data);
});