RPC endpoints of a Lisk node

The open Lisk communication architecture is based on two different RPC (Remote-Procedure-Call), API modes: Inter-Process Communication (IPC) 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 such as other scripts in JS, a tool in Rust, or a Python daemon.

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

The API client

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 nodeAPIURL = 'ws://localhost:8080/ws';

const getClient = async () => {
	if (!clientCache) {
		clientCache = await apiClient.createWSClient(nodeAPIURL);
	}
	return clientCache;
};

const blockId = 123;

getClient().then((client) => {
	client.invoke("app:getBlockByID", {
		id: blockId
	}).then(res => {
		const decodedBlock = client.block.decode(res);
		console.log("Decoded block: ", decodedBlock);
		process.exit(0);
	});
});
const { apiClient } = require('@liskhq/lisk-client');
let clientCache;
const nodeAPIURL = 'ws://localhost:8080/ws';

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

const blockId = 123;

getClient().then((client) => {
	client.invoke("app:getBlockByID", {
		id: blockId
	}).then(res => {
		const decodedBlock = client.block.decode(res);
		console.log("Decoded block: ", decodedBlock);
		process.exit(0);
	});
});

IPC vs WS

There are two methods available whereby a node can communicate via the API:

  • IPC (Inter-Process-Communication)

  • WS (WebSocket)

In general, IPC is the preferred method for local connections for the following reasons:

  • It is slightly faster

  • It supports synchronous data exchange

  • It does not use the system ports, therefore avoiding any risk of collision when the ports are already in use by another application

WS, on the contrary, should be used if the node API communicates with services on remote servers.

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 WebSocket.

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

For each action and event displayed below, the following statements apply:

  • …​ 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 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.

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 data input.
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

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

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:

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