Customizing nodeos for your dApp

or how to get EOS blockchain events in real time

What if your dApp environment needs to know about specific events in your contract and specific changes in its state memory? The usual way would be to write a poller script which would periodically retrieve this data from a nodeos server via its HTTP interface. Slow and inefficient, and you depend on the remote server provider not to block your excessive traffic.

Much more can actually be done at a moderate cost: you can create your own, application-specific plugin for nodeos.

How ZMQ plugin works

Let’s have a short look at the plugin source code. Around the bottom of the page, it adds itself to chain.applied_transaction signal handler. It means, our callback is executed whenever there’s a transaction in a signed block that arrived from the BP. Around Line 115, the plugin dissects the transaction into action traces, and each action trace is also recursively searched for inline actions.

What happens next, the plugin finds all accounts involved in the transaction. There’s also a workaround for eosio system account actions, such as updateauth and buyram, because they don’t leave a trace, and we need to know who was involved .

Then, for each involved account and each involved token cointract, it retrieves the current token balance. Basically it reads from the token contract’s accounts table. This is important to understand how we can customize this.

Once all data is collected, it is transformed into a JSON object and spit out the ZMQ PUSH socket. The difference between PUSH and PUB sockets is that PUSH socket will block if the reader is not ready. The PUB socket will just discard the message if the reader cannot read it.

How your own plugin works

The plugin would ignore all transactions that are happening with other contracts, and only react on transactions that are happening around our own contracts. This filtering is easy to implement with std::set<name> array that will let you easily check if at.act.account (the contract account where the action was executed) or at.receipt.receiver (the receiver contract account which may differ from the former) is your own contract account.

Once we determined that this is an action executed on our contract, we can dig through the action traces and collect data depending on the action name. For example, an action creates an entry in a table. The contract also stores the ID of the new record in its state table. So, the plugin can easily determine the ID of the record, read the record itself, and add it to the resulting JSON that will be pushed toward the ZMQ socket.

If your dApp is tolerant to missing such events (for example, a poller script is executed every few minutes to do the cleanup), you can use a PUB socket in your plugin instead of PUSH socket. In this case, nodeos will not be paused whenever the receiver stops or its input buffer overflows.

Setting up your server infrastructure

You would also need an instance of nodeos which would be shut down periodically, in order to make a snapshot of blockchain state. In case of abrupt crash of the main nodeos process, you would be able to restore the state database quickly, without having to re-play the whole blockchain again. I’m going to experiment with ZFS, as it’s designed for quick and cheap snapshots.

Author’s support

Telegram: cc32d9, EOS account: "cc32dninexxx"

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store