Skip to content

feat(cli): Add support to NVRAM - #519

Draft
brunomenezes wants to merge 13 commits into
refactor/sdk-update-anvil-state-sourcefrom
feat/cli-add-nvram-support
Draft

feat(cli): Add support to NVRAM#519
brunomenezes wants to merge 13 commits into
refactor/sdk-update-anvil-state-sourcefrom
feat/cli-add-nvram-support

Conversation

@brunomenezes

Copy link
Copy Markdown
Contributor

Summary

Code changes to add NVRAM support to the CLI, on top of the emulator 0.21.0 bump from refactor/sdk-update-anvil-state-source (this PR is stacked on that branch).

An NVRAM is a raw range of bytes exposed to the guest as a /dev/uio* device through the generic-uio driver. Unlike a flash drive it has no filesystem, no mount point and no page cache between the guest and the memory range, so writes are immediately visible to the emulator with no flush before snapshotting. That makes it the right primitive for an application that wants a fixed region of bytes it can mmap() and persist across advance states.

The new cartesi.toml config

[nvrams] is entirely optional — a cartesi.toml without it behaves exactly as before, emits no new flags and runs no new build steps.

[nvrams.input]
size = "4Ki"              # pristine, filled with zeros by cartesi-machine

[nvrams.output]
size = "4Ki"
shared = true             # guest writes are persisted to .cartesi/output.raw
user = "dapp"             # let the unprivileged entrypoint user write to it

[nvrams.seed]
filename = "./seed.raw"   # existing raw image; size defaults to the file size

Each table must define size or filename (or both, in which case they must agree exactly). Sizes must be a multiple of 4Ki, at most 8 nvrams are supported (/dev/uio0 to /dev/uio7), and labels cannot collide with drive labels since both share the DTB /aliases namespace. These are validated at parse time, so a bad cartesi.toml fails before anything is built.

Translation to the emulator, in cartesi.toml order:

configuration flag
size = "4Ki" --nvram=label:input,length:4096
size, shared, user --nvram=label:output,length:4096,data_filename:output.raw,user:dapp,shared
filename = "./seed.raw" --nvram=label:seed,data_filename:seed.raw

Only nvrams that need a backing image get a build step: a shared one is allocated zero-filled in .cartesi/, and a filename one is copied there (never written back to the source). A pristine nvram produces no artifact at all, since the emulator zero-fills the range itself.

Documented in apps/cli/tests/unit/config/fixtures/full.toml, the de-facto reference for cartesi.toml.

Permissions

user = "dapp" is what makes an nvram writable by the application, since entrypoints run unprivileged. Leaving it off gives a root-owned device node — readable by the app, not writable:

crw-rw-r-- 1 root root  /dev/uio0    # [nvrams.input]  — no user
crw-rw-r-- 1 dapp dapp  /dev/uio1    # [nvrams.output] — user = "dapp"

That is least-privilege hygiene, not an integrity guarantee: booting with --user=root (or cartesi shell --run-as-root) writes it fine.

Behaviour change: cartesi-machine 0.21.0 is now required

--nvram does not exist before 0.21.0, so requiredVersion moves from ^0.20.0 to ^0.21.0.

That constant used to be declarative only — nothing read it at runtime and doctor never checked the emulator. Since a host-installed cartesi-machine silently takes precedence over the SDK image (the Docker fallback only triggers on ENOENT), a user on 0.20.0 would have got:

lua5.4: /usr/share/lua/5.4/cartesi-machine.lua:1956: unrecognized option --nvram=label:input,length:4096
stack traceback: ...

So the range is now enforced. build and shell check before booting, and doctor reports it alongside the Docker checks:

✖ Unsupported Cartesi Machine version. Required version is ^0.21.0. Installed version is 0.20.0.
✔ Cartesi Machine 0.21.0

The check is deliberately non-blocking when the version cannot be determined at all — that also happens when Docker is down or the binary is missing, and the boot that follows reports those on its own.

Also included

  • fix(cli) — the cartesi-machine version check ignored its forceDocker option, silently
    reading the host binary instead of the SDK image. This is why the version assertion in
    cartesi-machine.test.ts passed for anyone with a matching local install and failed in CI.
  • test(cli)CARTESI_TEST_SDK overrides the image the integration suite runs against, so
    it can target an SDK image built from source instead of the released tag.
  • test(cli)cartesi-machine-stored-hash no longer prefixes the emulator output with 0x,
    which 0.21.0 already includes.
  • test(cli) — a boot-level nvram integration test, plus an optional cartesi.toml argument on
    createTemporaryCartesiApplication so a test can build an application that declares nvrams.
  • refactor(cli) — argument assembly split out of bootMachine into a pure buildMachineArgs,
    so the generated flags can be unit tested without spawning a machine.

Testing

Coverage is layered so that each level tests something the CLI actually owns.

level file covers
unit tests/unit/config.test.ts [nvrams] parsing, IEC sizes, every validation error
unit tests/unit/machine.test.ts the exact --nvram= strings and their order relative to --flash-drive
unit tests/unit/exec/cartesi-machine.test.ts the version range and its error message
integration tests/integration/builder/nvram.test.ts the backing images on disk
integration tests/integration/machine/nvram.test.ts a real machine booting with nvrams

The boot test builds a throwaway application whose cartesi.toml declares a pristine input and a
shared output, then boots it with replacement entrypoints the way shell does, and asserts:

  • only the nvrams that need a backing image get one — output.raw is 4096 zero bytes, input.raw
    is absent;
  • the guest exposes one /dev/uio* per nvram and no more;
  • nvram input / nvram output resolve in cartesi.toml order, which is the ordering the CLI is
    responsible for;
  • echo hello-nvram | writemmap output; readmmap output round-trips, and the bytes reach the
    host's .cartesi/output.raw — the assertion that actually proves shared works. Drop
    shared = true and only that last assertion fails.

It is gated on the emulator supporting --nvram (describe.skipIf on a version probe) rather than
on CARTESI_TEST_SDK being set, so it skips cleanly today and starts running by itself once
DEFAULT_SDK_VERSION points at a 0.21.0 image. In CI it runs, because the temporary commit below
points CARTESI_TEST_SDK at the PR-tagged SDK image.

Merge blocker

DEFAULT_SDK_VERSION is still 0.12.0-alpha.41, and that published image ships emulator 0.20.0. Until an SDK release carrying 0.21.0 exists and the default is bumped to it, anyone on the default image gets the new version error. This PR should not merge before that bump, either as a final commit here or as an immediately following PR.

⚠️ Temporary support test / ci-builds — (Must be removed before merging)

Commit details

The last commit, wip(cli): Temporary changes to support local/ci builds and test runs, exists only so this branch can be built and tested before its dependencies are released. It must be dropped before merge. It is deliberately isolated in a single commit so it can be removed with a single revert or drop.

It exists because the SDK image needs a cartesi-rollups-node build compiled against emulator 0.21.0, and no release contains one. The published v2.0.0-alpha.12 carries the same version string but different binaries, so it cannot be used.

What it changes:

  • packages/sdk/temp/fetch.sh (new) — downloads the cartesi-rollups-node .deb files from an
    unreleased Actions run (cartesi/rollups-node run 31189416046, branch
    feature/bump-emulator-v0.21.0) via nightly.link, which proxies the artifact without
    authentication. Checksums are pinned and verified; the download is skipped when the files are
    already present and valid.
  • packages/sdk/Dockerfile — installs cartesi-rollups-node from that bind-mounted .deb
    instead of the release URL, with the corresponding checksums.
  • packages/sdk/package.jsonbuild now runs fetch:temp first.
  • packages/sdk/.gitignore — ignores the downloaded temp/*.deb and temp/artifacts.zip.
  • .github/workflows/sdk.yaml — runs ./temp/fetch.sh before the bake.
  • .github/workflows/cli.yaml — adds packages/sdk/** to the path filter, sets
    CARTESI_TEST_SDK to the PR-tagged SDK image ghcr.io/cartesi/sdk:pr-<N> that sdk.yaml
    publishes for this PR, and logs in to ghcr.io to pull it. This is what makes CI exercise the
    integration suite against an emulator that actually supports --nvram.
  • packages/sdk/docker-bake.hcl and the foundry checksums in the Dockerfile⚠️ this reverts
    base-branch changes
    : FOUNDRY_VERSION back from 1.5.1 to 1.4.3 and CARTESI_PRT_VERSION
    back from 3.0.0-alpha.4 to 3.0.0-alpha.3, because the unreleased rollups-node build expects
    them. Dropping the commit restores the base values — take care not to lose them by resolving a
    conflict the wrong way.

Two deadlines worth knowing: the Actions artifact expires 2026-11-05, after which the SDK image can no longer be built from this branch, and until then bun run build in packages/sdk requires network access to nightly.link.

Removal checklist:

  • Drop commit wip(cli): Temporary changes to support local/ci builds and test runs
  • Confirm docker-bake.hcl is back to FOUNDRY_VERSION = "1.5.1" and CARTESI_PRT_VERSION = "3.0.0-alpha.4"
  • Confirm the Dockerfile installs cartesi-rollups-node from the release URL again
  • Confirm packages/sdk/temp/ is gone and both workflows are unmodified
  • Bump DEFAULT_SDK_VERSION to the SDK release shipping emulator 0.21.0
  • Re-check that the nvram boot test runs (not skips) once the default SDK carries 0.21.0

@changeset-bot

changeset-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9a7432d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cartesi/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@brunomenezes brunomenezes moved this to 🧑‍💻 In Progress in Rollups Tooling Aug 18, 2026
@brunomenezes brunomenezes self-assigned this Aug 18, 2026
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 98.24% (🎯 0%) 5137 / 5229
🔵 Statements 98.24% 5137 / 5229
🔵 Functions 94.44% 153 / 162
🔵 Branches 0% 0 / 0
📁 File Coverage (20 files)
File Lines Statements Functions Branches Uncovered Lines
apps/cli/src/builder/directory.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/builder/docker.ts 🟢 86.72% 🟢 86.72% 🟡 66.67% 🔴 0% 75-77, 79, 109-111, 169-178
apps/cli/src/builder/empty.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/builder/none.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/builder/nvram.ts 🟢 96.88% 🟢 96.88% 🟢 100% 🔴 0% 27
apps/cli/src/builder/tar.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/compose/builder.ts 🟢 99.79% 🟢 99.79% 🟢 100% 🔴 0% 228
apps/cli/src/compose/common.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/compose/node.ts 🟢 99.24% 🟢 99.24% 🟢 100% 🔴 0% 106
apps/cli/src/config.ts 🟢 95.12% 🟢 95.12% 🟢 96.15% 🔴 0% 78-79, 298, 307, 316, 410, ...
apps/cli/src/contracts.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
...rc/errors/ForkChainValidationError.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
...c/errors/UnsupportedForkChainError.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
...c/exec/cartesi-machine-stored-hash.ts 🟢 92.86% 🟢 92.86% 🟢 100% 🔴 0% 36-37
apps/cli/src/exec/cartesi-machine.ts 🟢 89.19% 🟢 89.19% 🟢 100% 🔴 0% 27-29, 53
apps/cli/src/exec/genext2fs.ts 🟢 96.92% 🟢 96.92% 🟢 100% 🔴 0% 87-88
apps/cli/src/exec/index.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/exec/mksquashfs.ts 🟢 91.53% 🟢 91.53% 🟢 100% 🔴 0% 70-74
apps/cli/src/exec/util.ts 🟢 85.11% 🟢 85.11% 🟡 66.67% 🔴 0% 24-28, 68-69
apps/cli/src/machine.ts 🟢 82.48% 🟢 82.48% 🟡 70% 🔴 0% 19, 22, 25, 81-82, 88, 101,...

@brunomenezes brunomenezes changed the title feat(cli): Support nvrams in cartesi.toml feat(cli): Add support to NVRAM Aug 18, 2026
@brunomenezes
brunomenezes force-pushed the feat/cli-add-nvram-support branch from 391697a to 9a7432d Compare August 18, 2026 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Todo
Status: 🧑‍💻 In Progress

Development

Successfully merging this pull request may close these issues.

1 participant