DApp interface protocol

HotPocket defines a DApp as a regular POSIX application that can receive Inputs, produce Outputs and also persist State to permanent storage. You can use any POSIX-compliant programming platform to create HotPocket DApps. Furthermore, you can also develop a library that helps to execute the HotPocket features efficiently.

Currently, there are two such libraries available for NodeJS and C.

The HotPocket DApp is spawned as a child process by HotPocket for each consensus round. The communication between the DApp and the HotPocket consensus engine happens as an inter-process communication using file descriptors. As per the IBM definition, the file descriptor is an unsigned integer used by a process to identify an open file. In HotPocket, there is a set of application-specific file descriptors, which are used for different channels. The file descriptors that are currently defined are given below (each and every message protocol will be described later):

  • User input/output file descriptor

  • NPL file descriptor

  • Control file descriptor

When implementing a DApp on the HotPocket consensus engine, you need to consider the following basic components of the HotPocket DApp:

Let’s get a basic understanding of these components.

Contract execution context

The HotPocket consensus engine passes context parameters in JSON format as command line arguments to the contract when it is being executed (the engine actually writes the contract arguments JSON into the stdin of the contract process).

{
  "hp_version": "<HotPocket_version>",
  "contract_id": "<contract_id>",
  "public_key": "<public_key>",
  "private_key": "<private_key>",
  "timestamp": "<timestamp>",
  "readonly": "<readonly>",
  "lcl_seq_no": "<lcl_seq_no>",
  "lcl_hash": "<lcl_hash>",
  "npl_fd": "<npl_fd>",
  "control_fd": "<control_fd>",
  "user_in_fd": "<user_in_fd>",
  "users": "<users>",
  "unl": "<unl>"
}
  • contract_id - GUID string of the contract which is specified at the time of deploying the contract. This is used as a validation mechanism by all the nodes of a cluster and the users to ensure they are talking with instances of the correct contract.

  • public_key - ed22519 public key of the node in hexadecimal format prefixed with ‘ed’. HotPocket uses this key pair to sign its messages to other nodes and users.

  • private_key - ed22519 private key of the node in hexadecimal format prefixed with ‘ed’. HotPocket uses this key pair to sign its messages to other nodes and users.

  • readonly - A boolean indicating whether the contract is being invoked due to a consensus execution or a read request. In readonly mode, the filesystem is readonly and represents the last consensus state.

  • timestamp - Consensus timestamp of the last closed ledger in UNIX epoch milliseconds.

  • lcl_seq_no - Last closed ledger sequence number. (Not available in read-only mode.)

  • lcl_hash - Last closed ledger hash in hexadecimal. (Not available in read-only mode.)

  • npl_fd - NPL file descriptor for the current contract invocation. Provides a channel for sending/receiving messages to other contracts during consensus execution. (Not available in read-only mode.)

  • control_fd - File descriptor for the contract to communicate with HotPocket.

  • user_in_fd - File descriptor containing all consensed user inputs.

  • users - List of connected users public keys, file descriptors for writing user outputs, and their corresponding user input offsets from user_in_fd.

  • unl - Information about the list of nodes that participated in consensus (Unique Node List).

Control channel

Control messages are passed between the DApp and HotPocket via the control file descriptor. This file descriptor can be found in the contract execution context as shown earlier. The DApp can send predefined instructions to HotPocket using control messages as follows.

Changing peers

DApps can add or remove peers from the HotPocket node by sending the ‘peer_changeset’ control message. After receiving this message, HotPocket will add the peers in the add section to the peer list, and remove the peers in the remove section from the peer list.

{
    'type': 'peer_changeset',
    'add': ['<ip1>:<port1>', '<ip2>:<port2>', ...],
    'remove': ['<ip1>:<port1>', '<ip2>:<port2>', ...]
}

NOTE: Control messages should not exceed 128KB.

Users/Clients channel

The HotPocket DApp maintains a single file descriptor for user inputs and separate file descriptors for user outputs. Currently, user messages can be sent and received in JSON or BSON formats.

Read users’ inputs

When a contract is executing, the collected user inputs by HotPocket are passed to the DApp via the user input file descriptor.

The users object that is passed inside the stdin arguments from the HotPocket consensus engine will be in the following format:

{
    ...
    "<public key user i>" : ["<output file descriptor for user i>", [<offset>, <length>]],
    "<public key user i+1>" : ["<output file descriptor for user i+1>", [<offset>, <length>]],
    ...
}

The key will be the user public key hex value, while the value is an array where the first element carries the output file descriptor and the second element carries the array which contains the offset and length of each user input. The relevant user input file descriptor is also passed within the stdin arguments.

In this way, when reading user inputs from the user input file descriptor (user_in_fd in stdin arguments), you have to use the message offset and length to separate each message.

Send outputs

For the user output file descriptors, the DApp receives a map of users with their file descriptors. The contract outputs are passed via each user’s output file descriptor from the DApp. The positioning of the output file descriptors inside the users object can be observed in the previous section which is about reading users' inputs.

When sending messages to the users, the HotPocket DApp should serialize the message accordingly based on the type of the message and the protocol that is used (BSON or JSON). This serialized message is written into the user output file descriptor as an array of two buffers. The first element of the buffer array contains the byte length of the serialized content (message length is in Big Endian format). The other element will be the serialized content.

[
    <4 byte Header Buffer - contains the length (Big Endian format)>,
    <Message Buffer - serialized message>
]

Please refer the user protocol described in the User Protocol section to get further details of the user messaging feature.

NPL (Node Party Line) channel

The NPL channel is used to interchange messages between DApps running on each and every node in the cluster, where HotPocket acts as an intermediary. The DApp sends NPL messages to HotPocket via the NPL file descriptor, and HotPocket broadcasts the message to all the connected UNL nodes in the cluster. Furthermore, the NPL messages sent by other peers are also sent to the DApp from HotPocket using this file descriptor.

The NPL message can be in any format that needs to be transferred between peers. This channel can be used if we cannot assure that all the DApps in the cluster nodes are generating the same particular result (e.g.- random number). Hence, using this channel we can share the result between all the nodes and elect one result upon agreement.

Read NPL messages

The HotPocket consensus engine reads the corresponding file descriptor, obtains the message, and broadcasts it to the UNL. However, the node is an OBSERVER or a non-UNL node, which means it does not broadcast the NPL messages. This role is defined in the HotPocket configuration file hp.cfg.

Once a node receives NPL messages, the NPL inputs are fed into the contract as sequence packets. It first sends the HEX public key buffer which has a length of 33 bytes (66 chars. = 2 chars. for key type prefix + 64 chars. for key) and then the data buffer (<pub_key><data>).

Send NPL messages

When sending a message, the content is written into the NPL file descriptor. This message length should also not exceed 128KB. You can write inputs such as text inputs and JSON objects (after converting them into strings) to this file descriptor as buffers.

Note :

The patch.cfg file - In each consensus round, the contract block of hp.cfg is synced with the peers in the cluster. The separated block is maintained inside the state directory of the node and is called the patch.cfg file. In some instances, we might need to update the patch.cfg and retrieve relevant configurations from that configuration file within the DApp.