RETHV - Building Reth for the RISC-V platform
Tags: reth, ethereum, riscv
Published: 2024-06-28 16:41:18

We describe how we managed to run [Reth](https://github.com/paradigmxyz/reth), the Rust Ethereum execution client maintained by Paradigm and the open-source community, on a RISC-V environnement.

As we faced issues and learned many things, we wanted not only to share the finished PoC but also the journey itself. If you'd like to chat about running Reth on RISC-V in more details, feel free to [contact us](mailto:contact@quartz.technology).

Last but not least, we are not experts on the topics described in the document - we might have missed easy fixes, misunderstood concepts or over-engineered some solutions, we encourage you to let us know.

Happy reading!

## Table Of Contents
  * [Introduction](#introduction)
  * [The RISC-V environnement](#the-risc-v-environnement)
    * [Bare Metal](#bare-metal)
    * [QEMU to the rescue](#qemu-to-the-rescue)
  * [Cross-Compiling Reth](#cross-compiling-reth)
  * [Cross-Compilation Target](#cross-compilation-target)
  * [MDBX Bindgen Configuration](mdbx-bindgen-configuration)
  * [Building Reth](#building-reth)
  * [Wrapping Up](#wrapping-up)
    * [Custom Docker image for RISC-V toolchain](#custom-docker-image-for-risc-v-toolchain)
    * [Using the non-trivial approach](#using-the-non-trivial-approach)
    * [Using a simpler approach](#using-a-simpler-approach)
  * [Running Reth](#running-reth)
  * [Conclusion](#conclusion)
  * [Acknowledgments](#acknowledgments)
  * [Resources](#resources)
  * [Authors](#authors)

## Introduction <a name="introduction"></a>
Our goal was to be able to run Reth on a RISC-V GNU Linux. Why? Because:
1. We believe it enhances client diversity. It's not only about what is the most used client implementation, but also what hardware it runs on.
2. It's an amazing challenge. We learned many things about cross-compilation, QEMU and the tools used in Reth.
3. Extending the previous reason, we just wanted to share our journey with the community. Maybe it'll help some of you get started on RISC-V, exploring QEMU and more.

We'll explain how we managed to configure a RISC-V environnement, how we cross-compiled Reth for the RISC-V target and present the tools used to achieve it. We'll also recap the limitations we currently know about.

## The RISC-V environnement <a name="the-risc-v-environnement"></a>
In this first section, we describe how we failed to run Reth on a non-compatible bare-metal environnement, which led us to use a VM with the help of QEMU. 

### Bare Metal <a name="bare-metal"></a>
At the beginning, we really wanted to run Reth on a bare-metal server with a RISC-V processor.

We used the [Elastic Metal RV1 instances from Scaleway](https://labs.scaleway.com/en/em-rv1/) but faced a first error we previously encountered when trying to run Reth on ARM64 machines: the virtual memory layout on Linux.
In a nutshell, the Linux kernel can be compiled to use a specific virtual memory layout, but the hardware on the machine might not support all of them.
Scaleway's instances only support SV39 mode at best, while we need SV48 at least. Not using a proper virtual memory layout leads to an error which was [documented in the Reth issue here](https://github.com/paradigmxyz/reth/issues/2211) .
On RISC-V Linux, the SV39 mode allows up to 256 GB of user-space virtual memory while the SV48 allows up to 128 TB - as specified in the [kernel documentation](https://www.kernel.org/doc/html/v6.4-rc7/riscv/vm-layout.html).
Check-out the issue linked above to learn more about the underlying reasons of why this is an issue for Reth, onbjerg did an amazing job explaining it in simple terms.

When we started the project, Scaleway was the only cloud provider with RISC-V instances and we did not have a dev board at home, which led us to look for another setup: using QEMU to create a RISC-V VM.

### QEMU to the rescue <a name="qemu-to-the-rescue"></a>
We tried to follow the [official documentation for running Linux on QEMU for RISC-V](https://risc-v-getting-started-guide.readthedocs.io/en/latest/linux-qemu.html) but faced too many compilation errors on macOS with Apple Silicon.

As we had a Ubuntu machine with an x86 CPU on Hetzner for development purposes, we decided to immediately switch.
After following the instructions to compile QEMU with RISC-V compatibility and running a compatible Debian image (amazing blog post with the [instructions here](https://colatkinson.site/linux/riscv/2021/01/27/riscv-qemu/)), it worked!
You can connect to the VM using:
```bash
ssh root@localhost -p 2222 # Passord: root
```

```bash
root@debian:~# uname -r
6.8.12-riscv64
root@debian:~# cat /proc/cpuinfo
...
mmu             : sv57 # Sweet, this virtual memory layout allows up to 64 PB user-space virtual memory. Enough to store da blobs.
...
```

Note that the provided image only has a 10 GB-size disk. If you'd like to extend it consider taking a look at this [SO answer](https://serverfault.com/questions/324281/how-do-you-increase-a-kvm-guests-disk-space) and then resizing the FS using `fdisk` (be careful, when expanding the root partition, the first sector might not be the default one) and `resize2fs`.

The development environnement is ready. Now is the time to build and run Reth.

## Cross-Compiling Reth <a name="cross-compiling-reth"></a>
To compile Reth for the RISC-V 64 target, we have different options:
- Compile natively inside the VM. Slow but less prone to errors and requires less configuration tweaks.
- Compile using the cross-compiler. Requires to setup the tools first but leads to way faster compilation time.

The first option is actually really easy: just follow the official documentation to [build Reth from source](https://reth.rs/installation/source.html) from within the VM.
The second option is a bit more complex: we can either use install the cross-compiler from a package manager or build it from source. 

As your distribution may very from our own, we chose to build it from the [official toolchain sources](https://github.com/riscv-collab/riscv-gnu-toolchain). In fact, we built a [Docker image](https://hub.docker.com/layers/quartztech/riscv-gnu-toolchain/latest/images/sha256-5e97020d214ba5abc16709db4f1e3c39c2c0cc5417fb901506a3297284af62b7?context=repo) which contains the RISC-V toolchain built from source for both the `amd64` and `arm64` platforms.


From there, it's as easy as just running `cargo build` right? Well, there are two details.
The cross-compilation target must be specified and requires some extra parameters, and the MDBX auto-generated bindings requires extra-configuration.

### Cross-Compilation Target <a name="cross-compilation-target"></a>
When cross-compiling in Rust using cargo, the target can be specified using the `--target` flag.
Here, we will inform cargo that the target is `riscv64gc-unknown-linux-gnu`.
One thing we discovered was that another target we aimed for, `riscv64gc-unknown-none-elf`, is not yet fully supported by Rust. Namely, it does not support std right now and therefore Reth cannot be cross-compiled for this target.

During the cross-compilation, the linker also must be specified manually, and we chose the easiest way (an environnement variable) to do so:
```bash
export CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=riscv64-unknown-linux-gnu-gcc
```

But if we try to build Reth, the compilation will fail due to the custom `mdbx-sys` crate build process.
```bash
...
33.85 error: failed to run custom build command for `reth-mdbx-sys v1.0.0 (/build/crates/storage/libmdbx-rs/mdbx-sys)`
33.85
33.85 Caused by:
33.85   process didn't exit successfully: `/build/target/release/build/reth-mdbx-sys-c89ac4f3243e36e2/build-script-build` (exit status: 101)
33.85   --- stdout
33.85   cargo:rerun-if-changed=/build/crates/storage/libmdbx-rs/mdbx-sys/libmdbx
33.85
33.85   --- stderr
33.85   /build/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.h:80:2: warning: "The RISC-V architecture is intentionally insecure by design.   Please delete this admonition at your own risk,   if you make such decision informed and consciously.   Refer to https://clck.ru/32d9xH for more information." [-W#warnings]
33.85   /usr/include/stdint.h:26:10: fatal error: 'bits/libc-header-start.h' file not found
...
```
Let's fix it by tweaking the bindgen configuration.

### MDBX Bindgen Configuration <a name="mdbx-bindgen-configuration"></a>
To build the `mdbx-sys` crate bindings, the bindgen tool is used. As it uses clang to generate the C/C++/Rust bindings, it does not know about the RISC-V toolchain and we must specify it manually.
Hopefully, as stated in the bindgen's README, we can pass extra flags to clang using the `BINDGEN_EXTRA_CLANG_ARGS` environnement variables (we could also tweak the `build.rs` file in the crate, but we did not wanted to temper with the sources).
```bash
# The sysroot for the quartztech/riscv-gnu-toolchain image is in /usr/local/
export BINDGEN_EXTRA_CLANG_ARGS="--sysroot=/usr/local/sysroot"
```

Finally, we can build Reth.

## Building Reth <a name="building-reth"></a>
Here's the command to build reth using our RISC-V cross-compiler:
```bash
cargo build --release --target riscv64gc-unknown-linux-gnu
```

### Wrapping Up <a name="wrapping-up"></a>

#### Custom Docker image for RISC-V toolchain <a name="custom-docker-image-for-risc-v-toolchain"></a>
Here's our Dockerfile used as the based image in the next sub-section.
It contains the RISC-V toolchain built from source.
```dockerfile
FROM ubuntu:22.04 AS builder

WORKDIR /build

RUN apt-get update && apt-get install -y \
    autoconf \
    automake \
    autotools-dev \
    curl \
    python3 \
    python3-pip \
    libmpc-dev \
    libmpfr-dev \
    libgmp-dev \
    gawk \
    build-essential \
    bison \
    flex \
    texinfo \
    gperf \
    libtool \
    patchutils \
    bc \
    zlib1g-dev \
    libexpat-dev \
    ninja-build \
    git \
    cmake \
    libglib2.0-dev \
    libslirp-dev

RUN git clone https://github.com/riscv/riscv-gnu-toolchain .

# Newlib installation.
RUN ./configure --enable-multilib
RUN make

# Linux installation.
RUN ./configure --enable-multilib
RUN make linux

FROM ubuntu:22.04 AS runtime

WORKDIR /

COPY --from=builder /usr/local /usr/local

ENTRYPOINT [ "/bin/bash" ]
```

#### Using the non-trivial approach <a name="using-the-non-trivial-approach"></a>
For the braves, here's a Dockerfile which uses the Quartz base image to cross-compile Reth:
```dockerfile
FROM quartztech/riscv-gnu-toolchain:latest

WORKDIR /build

ENV PATH=/opt/riscv/bin:$PATH

RUN apt-get update && apt-get install -y \
    curl \
    build-essential \
    git \
    pkg-config \
    libssl-dev \
    libclang-dev

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH=/root/.cargo/bin:$PATH

RUN rustup target add riscv64gc-unknown-linux-gnu

RUN git clone https://github.com/paradigmxyz/reth .

ENV CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=riscv64-unknown-linux-gnu-gcc
ENV BINDGEN_EXTRA_CLANG_ARGS="--sysroot=/usr/local/sysroot"

RUN cargo build --release --target riscv64gc-unknown-linux-gnu

ENTRYPOINT [ "/bin/bash" ]
```

#### Using a simpler approach <a name="using-a-simpler-approach"></a>
Now if you use a simpler method and just install the toolchain from a package manager, here's what the Dockerfile would look like (with Ubuntu as the base image at least):
```dockerfile
FROM ubuntu:22.04

WORKDIR /build

RUN apt-get update && apt-get install -y \
    curl \
    build-essential \
    git \
    pkg-config \
    libssl-dev \
    libclang-dev \
    # All of this article for this change ^^, I love reinventing the wheel.
    gcc-riscv64-linux-gnu

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH=/root/.cargo/bin:$PATH

RUN rustup target add riscv64gc-unknown-linux-gnu

RUN git clone https://github.com/paradigmxyz/reth .

ENV CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc

RUN cargo build --release --target riscv64gc-unknown-linux-gnu

ENTRYPOINT [ "/bin/bash" ]
```

Way simpler than everything else, sometimes it's all about installing one package that does the job.

## Running Reth <a name="running-reth"></a>
The last step is to copy the binary built inside the Docker image to the RISC-V VM:
```bash
docker cp <containerId>:/build/target/riscv64gc-unknown-linux-gnu/release/reth .
scp -P 2222 reth root@localhost:/tmp
ssh root@localhost -p 2222
# ...
# ...
# ...
root@debian:~# cd /tmp
root@debian:/tmp# ls
reth
root@debian:/tmp# ./reth node --full
2024-06-24T20:00:40.554366Z  INFO Initialized tracing, debug log directory: /root/.cache/reth/logs/mainnet
2024-06-24T20:00:40.588379Z  INFO Starting reth version="1.0.0-dev (81b5fbf57)"
2024-06-24T20:00:40.592833Z  INFO Opening database path="/root/.local/share/reth/mainnet/db"
2024-06-24T20:00:40.824511Z  INFO Configuration loaded path="/root/.local/share/reth/mainnet/reth.toml"
2024-06-24T20:00:40.908347Z  INFO Verifying storage consistency.
2024-06-24T20:00:41.025506Z  INFO Database opened
2024-06-24T20:00:41.029405Z  INFO
# ...
```

While it is definitely NOT an environnement suited for production, it works!

## Conclusion <a name="conclusion"></a>
We managed to overcome some challenges to build and run Reth on RISC-V, which contributes to maximizing the diversity of the Ethereum infrastructure!

Now let's not celebrate too much: RISC-V is VERY early, slow and insecure (even MDBX reminds us of this, look back at the logs ^^). It's just a proof of concept and also a good excuse for us to test new things.
We also did not (but it's in the TODO list) synchronized a full-node entirely, and therefore can not attest that it works completely.

I really encourage you to try this on your own, hopefully you'll learn a few things in the process. I spent some time reading documentation on RISC-V, the Rust bindgen, cross-compilation and many more. As everyone has it's own way of approaching this deep-focus phase, I'll let you invest your time as you wish.

## Acknowledgments <a name="acknowledgments"></a>
- All the Reth contributors for their amazing work.
- [Onbjerg](https://github.com/onbjerg) for his help on [debugging compilation errors on ARM64 due to memory layout configuration](https://github.com/paradigmxyz/reth/issues/2211#issuecomment-1613745079).
- [Colin Atkinson](https://colatkinson.site/) for his post on [running RISC-V in QEMU](https://colatkinson.site/linux/riscv/2021/01/27/riscv-qemu/).

## Resources <a name="resources"></a>
- [Quartz Docker image for RISC-V Toolchain](https://github.com/quartz-technology/riscv-gnu-toolchain-docker).
- [Rust Platform Support](https://doc.rust-lang.org/nightly/rustc/platform-support.html).
- [RISC-V Linux using QEMU](https://risc-v-getting-started-guide.readthedocs.io/en/latest/linux-qemu.html).
- [Virtual Memory Layout on RISC-V Linux](https://www.kernel.org/doc/html/v6.4-rc7/riscv/vm-layout.html).
- [Reth](https://github.com/paradigmxyz/reth).
- [Rust bindgen](https://github.com/rust-lang/rust-bindgen?tab=readme-ov-file).

## Authors <a name="authors"></a>
This project was made by the 🦀 at [Quartz Technology](https://quartz.technology).