Skip to content
Logo

Running in the Cartesi Machine

Inside the Cartesi Machine — the deterministic riscv64 Linux VM your application ships in — the binding stops simulating: it statically links the real libcmt and talks to the machine's rollup device through the kernel driver. Your application code does not change. The right flavor is selected by CPU architecture at install time.

What your machine image needs

  1. Node.js for riscv64 — use the cartesi/node image (see below).
  2. Your application and its dependencies — installed with npm as usual. The package's linux-riscv64 prebuild is picked up automatically; no compiler or libcmt development files are needed inside the image.

Node.js for riscv64

Node.js publishes no official riscv64 builds, so Cartesi builds them and publishes them to Docker Hub as cartesi/node (built from cartesi/docker-node). Prefer it over the distribution's own nodejs package, which lags well behind: Debian trixie ships Node 20, Ubuntu noble Node 18.

Tags follow <version>-<ubuntu codename>[-slim], and the images are riscv64-only:

TagNode
cartesi/node:22-noble-slim22.x on Ubuntu 24.04
cartesi/node:24-noble-slim24.x on Ubuntu 24.04

Minor and patch versions (22.23, 22.23.0), the non-slim variants, and older jammy (Ubuntu 22.04) builds are published too. There is no latest tag — pick an explicit one.

Any of these work with this package: the addon is a Node-API addon built against API version 8, so a single riscv64 prebuild is ABI-stable across Node major versions.

A minimal Dockerfile for a machine rootfs

FROM --platform=linux/riscv64 cartesi/node:22-noble-slim
ARG GUEST_TOOLS_VERSION=v0.18.0
ADD https://github.com/cartesi/machine-guest-tools/releases/download/${GUEST_TOOLS_VERSION}/machine-guest-tools_riscv64.deb /tmp/
RUN apt-get update \
    && apt-get install -y --no-install-recommends /tmp/machine-guest-tools_riscv64.deb \
    && rm -rf /var/lib/apt/lists/* /tmp/machine-guest-tools_riscv64.deb
COPY app /opt/app
ENTRYPOINT ["node", "/opt/app/index.mjs"]

(machine-guest-tools provides the machine's init system; tools like cartesi-cli automate this whole image-building step.)

Why the API is synchronous

When the application calls finish and no input is available, the machine yields: the emulator stops executing it entirely until the next input arrives. From the guest's perspective time freezes — the Node event loop, timers, everything. An async API would suggest concurrency that cannot exist; the synchronous one tells the truth.

This has a practical consequence: don't schedule background work (intervals, keep-alives, watchers) and expect it to run "while waiting" — it will only run while a request is being handled.

Determinism rules

Every node validating your application re-executes the same machine and must reach bit-identical results. The machine guarantees this for you — there is no wall-clock, no real randomness, no network inside. Use the request's own context when you need these:

import {  } from "@cartesi/rollup";
 
const  = new ();
 
await .({
    (, ) {
        // ✓ deterministic time: the L1 block timestamp of the input
        const  = .;
        // ✓ deterministic randomness beacon: prevRandao
        const  = .;
        .(.(`t=${} seed=${}`));
        return true;
    },
});

Trying it locally

The cartesi-machine emulator can run your image on your development host and feed it inputs from files:

cartesi-machine \
  --flash-drive=label:root,data_filename:rootfs.ext2 \
  --cmio-advance-state=input:input-%i.bin,input_index_begin:0,input_index_end:1 \
  --no-revert \
  -- node /opt/app/index.mjs

Each input file uses the same EvmAdvance encoding as the host mock; outputs and reports are stored to files as the machine emits them.

For a complete, working example — building the rootfs, feeding inputs, verifying outputs byte-for-byte — see the package's own end-to-end test harness: packages/rollup/test/machine/.

Limits to keep in mind

  • One Rollup instance at a time. The device is exclusive; a second new Rollup() throws -EBUSY until the first is closed.
  • Output sizes are bounded by the machine's cmio buffers (2 MiB by default) — a too-large voucher/notice/report throws with error.errno.
  • RAM is configured per machine (--ram-length); Node.js is comfortable from 512 MiB up.