The intent of this article is to describe why and how we have decided to implement communication between the existing Tezos node written in OCaml and our own node that we are building in Rust.
Because the Tezos protocol is implemented in OCaml. which we plan to use for message validation and processing, we had to first solve how OCaml and Rust will communicate between each other.
It would be possible to create some kind of network interface (REST, gRPC) to communicate between OCaml and Rust, but that would require users to deploy, configure, monitor and secure two applications instead of one.
We didn’t want to do that, because our plan is to provide users with the most convenient and secure solution possible.
To meet this goal, we have chosen to use standard C FFI, which is supported by both Rust and OCaml. This allows us to compile OCaml and Rust code into a single executable. This sounds ideal, but there are a few tradeoffs.
When using C FFI to transfer messages between Rust and OCaml, we have to make sure that the memory layout of transferred data is correctly interpreted by both sides. Otherwise we could get segfaults, even if both languages are considered to be safe.
To remove the possibility of memory corruption, we decided to pass only scalar values and byte arrays to our FFI. In case we need to transfer complex data structure, we will first serialize that structure into bytes. To do that, we will be using the Protobuf serialization library which has implementations for both OCaml and Rust.
OCaml code is compiled into a shared library that contains compiled OCaml code and embedded OCaml runtime. The shared library is then linked in a Rust build process to create an executable.
As a result, you won’t have to run the OCaml node alongside the Rust node in order to run Tezos and bake. Since you don’t need to run two nodes at the same time, you spend less time configuring and monitoring another node, in addition to increasing security and performance by avoiding network calls.
For the OCaml part, we utilized the embed protocol.
The code that we created in OCaml will be distributed as a shared library that also contains embedded OCaml runtime, therefore its final size is approximately 35 MB.
The OCaml functions are exported through the OCaml Callback.register function, the exported functions are then called from Rust.
In the Rust part, we’ve created a standalone package lib_ocaml to encapsulate all FFI calls between OCaml and Rust.
Since we wanted to achieve the highest performance and at the same time provide a comfortable experience for developers, we’ve implemented an interface with the help of the new std::future api.
Since we’re using the new std:future API, developers can use the new async/await syntax. Thanks to the new async/await syntax in Rust, asynchronous code is no longer as complicated as it used to be. Currently, most crates are being updated with the new async/await syntax.
We rank as one of the earliest adopters of this syntax because we have been using it since the very beginning of our project in 2018. The entire project has been built with async/await in mind, which should significantly improve the performance of our solution.
We implemented our own OcamlThreadExecutor which performs the execution of Future tasks in the thread dedicated to OCaml runtime. This approach was chosen because it is not possible to access OCaml runtime simultaneously from multiple threads.
The OCaml run-time system is not reentrant: only one thread at a time can be executing OCaml code or C code that uses the OCaml run-time system. Technically, this is enforced by a “master lock” that any thread must hold while executing such code.
When OCaml calls the C code implementing a primitive, the master lock is held, therefore the C code has full access to the facilities of the run-time system. However, no other thread can execute OCaml code concurrently with the C code of the primitive.
The detailed mechanism of multithreaded access is described in the OCaml manual.
At the moment, we are using only a single thread to communicate with OCaml runtime, but thanks to how Rust futures are designed, this approach can be easily scaled to multiple threads, if necessary.
We are really excited about how clean the new Rust futures and async/await syntax feels. Async code looks almost the same as sync code, which is a great thing!
Although the async/await is still not stabilized in Rust, we can expect stabilization in the upcoming stable (most likely 1.39.0) release.
Come see the interoperability code on our GitHub. You are welcome to star it or even try it out!
We are building two OCaml libraries — tzmock and tzreal. The tzreal library is a forked and modified Nomadic Labs Tezos repository while tzmock is used for rapid testing and prototyping purposes. Developers are welcome to use the tzmock library for testing in local prototypes.
This represents an important building block for the Tezos Rust node. As we make progress, we aim to eventually make these functionalities available for wallet producers and other interested parties.
Note that this solution is currently limited to being a proof of concept (PoC). We plan on improving this as we progress in the development of the Tezos Rust node. For instance, the interface will be subject to changes.
If you wish to read more about our project, follow us on Medium or visit our GitHub.