Improving the performance of smart contract execution
By: Robert Sasu
Tags: Scalability, Wasm, Smart Contracts, Vm, Blockchain

The basic architecture of Elrond is a plug-and-play type, where high level components connect/communicate with each other through well defined interfaces. A virtual machine which deploys and executes smart contracts in a specific language is a high level plug-able component in this architecture.

One such VM has to satisfy an interface and gets read-only access to the data existing in the blockchain through a set of hooks. Through this interface the VM gets access to cryptographic and hash functions as well. A smart contract can interact with the blockchain through a well-defined API. All outputs generated by the SmartContract (e.g. balance change), is saved in a so-called VMOutput at the VM’s level. After the complete execution of the smart contract the VM output is returned to the protocol. The protocol then does the basic verification and updates the state trie of the modified accounts.

Furthermore, reading from the blockchain is optimized by saving the data from an accessed account into a cache. At consequential reads through the blockchain hooks, the response comes directly from the cache.

Until now, Elrond had one VM component deployed with the protocol: a formally verified VM for the IELE language, generated by the K Framework with our Go backend. The language is great as it was created for blockchain smart contract purposes, the VM is formally verified meaning it is mathematically demonstrable that all behavior is deterministic.

But IELE is not a well known language for smart contracts, so we asked ourselves, why not support more languages, more VMs, each being optimal for different things. And because VMs in our architecture are already a modular component, we can create more such components, put them into a container and call the one which is needed for the job at any specific time. So having this in mind we decided to go on and search for the next VM to be integrated into Elrond.

We had a few goals in mind when choosing a VM and one or more languages to be supported by the Elrond protocol:

Smart contract code should be easy to read and easy to write.

Catch obvious bugs at compile-time, and it should have a linter for anything that is impossible to catch at compile-time.

Compile to very compact bytecode to ensure that it’s cheap to deploy smart contracts.

It should be easy to discover how to use the API

It should integrate as well with the rest of the ecosystem as possible, so that the tools can be reused and avoid re-implementing code.

Tooling support and determinism for general-purpose languages

And the choice was: WASM

WebAssembly (often shortened to Wasm) is an open standard that defines a portable binary code format for executable programs, and a corresponding textual assembly language, as well as interfaces for facilitating interactions between such programs and their host environment. The main goal of WebAssembly is to enable high performance applications on web pages, but the format is designed to be executed and integrated in other environments as well.[5][6]

WebAssembly is a VM target that (unlike most virtual machines) generally tries to match the semantics of a CPU architecture like ARM or x86 rather than trying to match the semantics of the language that runs on it. As a result, many languages can compile to it unchanged. Since most programming languages are built to expose the semantics of the CPU at some level, having the VM emulate a CPU is a good lowest common denominator. It also allows us to use world-class optimising compilers that were built for ISAs like x86 and ARM — very important when you’re charging by the instruction. solc has an — optimize flag but as far as I can tell all that it does is remove unused jump targets from the list of allowed targets in the assembly’s metadata, which is more of a security feature than an optimisation.

One downside, however, is that these general-purpose languages were not built to write smart contracts in. That, however, can be solved with the right library.

Wasm expands the family of languages available to smart contract developers to include Rust, C/C++, C#, Typescript and much more.This means you can write smart contracts in whichever language you’re familiar with, compile it of WASM and even easily debug its WAT human readable format. Additional benefits to WebAssembly:

Memory-safe, sandboxed, and platform-independent.

64 and 32-bit integer operation support that maps one-to-one with CPU instructions.

Easily made deterministic by removing floating point operations

Supported by the LLVM compiler infrastructure project, meaning that Wasm benefits from over a decade of LLVM’s compiler optimisation.

Continually developed by major companies such as Google, Apple, Microsoft, Mozilla, and Facebook.

As for the implementation part we looked at existing integration and debated whether to use interpreters or compilers. One of the reasons of choosing WASM was speed: we wanted to have the fastest VM / engine out there. These engines are mostly written in C++ and our protocol is in GO, but with cgo those two can work well together.

We wanted to use WASM engines at their best, bare as they are, with as little overhead as possible, preferably none. The eWasm team did a great job of integrating WABT, Bynarien and WAVM through the heraVM into Ethereum and the code looked promising. The WASM engines were left as clean as they are, adding the definition of the blockchain hooks as defined functions at every smart contract run. (BTW this is a place of further improvement — make those calls part of the underlying system, do it only once, not at every run/deploy).

So we took the code and started to create our own adapter to it, one that supports the Elrond VM interface. But while writing the adapter we made several optimizations, in order for the smart contracts to run faster. A smart contract can access several “system” functions to read and write into the state of the blockchain, to manipulate the balance of the accounts. In our integration of hera, we bring these functions and data as close as possible to the running engine — having most of the data in the adapter directly.

So, the smart contracts make use of he blockchain hooks only when the adapter does not have the data the smart contract is asking for. If an account was touched through a smart contract run, all of its data is brought to the adapter. The next reads from that accounts will be resolved directly at the adapter — no more overhead of going all the way to the blockchain.

Furthermore, in Elrond the Virtual Machines only get read access to the blockchain, while any writing they do is made to a local cache, which is written to the state only at the end of the smart contract processing. Writing to these caches is super fast, and in the case of hera this happens directly in the adapter. The final writing into the blockchain state is done only by the protocol, after basic validation — keeping the system safe at all the time.

As hera implemented the Ethereum Environment Interface and we created an adapter over it, this means Elrond directly supports the EEI: smart contracts written for Ethereum can be deployed and ran in Elrond protocol as well (with a small modification: changing the address handling from 20 bytes to 32 bytes).

Our benchmarks for smart contracts running on the protocol using WASM: