feat(cli): Add support to NVRAM - #519
Draft
brunomenezes wants to merge 13 commits into
Draft
Conversation
🦋 Changeset detectedLatest commit: 9a7432d The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
Contributor
Coverage Report
📁 File Coverage (20 files)
|
brunomenezes
force-pushed
the
feat/cli-add-nvram-support
branch
from
August 18, 2026 18:29
391697a to
9a7432d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 canmmap()and persist across advance states.The new cartesi.toml config
[nvrams]is entirely optional — acartesi.tomlwithout it behaves exactly as before, emits no new flags and runs no new build steps.Each table must define
sizeorfilename(or both, in which case they must agree exactly). Sizes must be a multiple of 4Ki, at most 8 nvrams are supported (/dev/uio0to/dev/uio7), and labels cannot collide with drive labels since both share the DTB/aliasesnamespace. These are validated at parse time, so a badcartesi.tomlfails before anything is built.Translation to the emulator, in
cartesi.tomlorder:size = "4Ki"--nvram=label:input,length:4096size,shared,user--nvram=label:output,length:4096,data_filename:output.raw,user:dapp,sharedfilename = "./seed.raw"--nvram=label:seed,data_filename:seed.rawOnly nvrams that need a backing image get a build step: a
sharedone is allocated zero-filled in.cartesi/, and afilenameone 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 forcartesi.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:That is least-privilege hygiene, not an integrity guarantee: booting with
--user=root(orcartesi shell --run-as-root) writes it fine.Behaviour change: cartesi-machine 0.21.0 is now required
--nvramdoes not exist before 0.21.0, sorequiredVersionmoves from^0.20.0to^0.21.0.That constant used to be declarative only — nothing read it at runtime and
doctornever checked the emulator. Since a host-installedcartesi-machinesilently takes precedence over the SDK image (the Docker fallback only triggers onENOENT), a user on 0.20.0 would have got:So the range is now enforced.
buildandshellcheck before booting, anddoctorreports it alongside the Docker checks: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 itsforceDockeroption, silentlyreading the host binary instead of the SDK image. This is why the version assertion in
cartesi-machine.test.tspassed for anyone with a matching local install and failed in CI.test(cli)—CARTESI_TEST_SDKoverrides the image the integration suite runs against, soit can target an SDK image built from source instead of the released tag.
test(cli)—cartesi-machine-stored-hashno longer prefixes the emulator output with0x,which 0.21.0 already includes.
test(cli)— a boot-level nvram integration test, plus an optionalcartesi.tomlargument oncreateTemporaryCartesiApplicationso a test can build an application that declares nvrams.refactor(cli)— argument assembly split out ofbootMachineinto a purebuildMachineArgs,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.
tests/unit/config.test.ts[nvrams]parsing, IEC sizes, every validation errortests/unit/machine.test.ts--nvram=strings and their order relative to--flash-drivetests/unit/exec/cartesi-machine.test.tstests/integration/builder/nvram.test.tstests/integration/machine/nvram.test.tsThe boot test builds a throwaway application whose
cartesi.tomldeclares a pristineinputand ashared
output, then boots it with replacement entrypoints the wayshelldoes, and asserts:output.rawis 4096 zero bytes,input.rawis absent;
/dev/uio*per nvram and no more;nvram input/nvram outputresolve incartesi.tomlorder, which is the ordering the CLI isresponsible for;
echo hello-nvram | writemmap output; readmmap outputround-trips, and the bytes reach thehost's
.cartesi/output.raw— the assertion that actually provessharedworks. Dropshared = trueand only that last assertion fails.It is gated on the emulator supporting
--nvram(describe.skipIfon a version probe) rather thanon
CARTESI_TEST_SDKbeing set, so it skips cleanly today and starts running by itself onceDEFAULT_SDK_VERSIONpoints at a 0.21.0 image. In CI it runs, because the temporary commit belowpoints
CARTESI_TEST_SDKat the PR-tagged SDK image.Merge blocker
DEFAULT_SDK_VERSIONis still0.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.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-nodebuild compiled against emulator 0.21.0, and no release contains one. The publishedv2.0.0-alpha.12carries the same version string but different binaries, so it cannot be used.What it changes:
packages/sdk/temp/fetch.sh(new) — downloads thecartesi-rollups-node.debfiles from anunreleased Actions run (
cartesi/rollups-noderun31189416046, branchfeature/bump-emulator-v0.21.0) via nightly.link, which proxies the artifact withoutauthentication. Checksums are pinned and verified; the download is skipped when the files are
already present and valid.
packages/sdk/Dockerfile— installscartesi-rollups-nodefrom that bind-mounted.debinstead of the release URL, with the corresponding checksums.
packages/sdk/package.json—buildnow runsfetch:tempfirst.packages/sdk/.gitignore— ignores the downloadedtemp/*.debandtemp/artifacts.zip..github/workflows/sdk.yaml— runs./temp/fetch.shbefore the bake..github/workflows/cli.yaml— addspackages/sdk/**to the path filter, setsCARTESI_TEST_SDKto the PR-tagged SDK imageghcr.io/cartesi/sdk:pr-<N>thatsdk.yamlpublishes 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.hcland the foundry checksums in the Dockerfile —base-branch changes:
FOUNDRY_VERSIONback from1.5.1to1.4.3andCARTESI_PRT_VERSIONback from
3.0.0-alpha.4to3.0.0-alpha.3, because the unreleased rollups-node build expectsthem. Dropping the commit restores the base values — take care not to lose them by resolving a
conflict the wrong way.
Removal checklist:
wip(cli): Temporary changes to support local/ci builds and test runsdocker-bake.hclis back toFOUNDRY_VERSION = "1.5.1"andCARTESI_PRT_VERSION = "3.0.0-alpha.4"cartesi-rollups-nodefrom the release URL againpackages/sdk/temp/is gone and both workflows are unmodifiedDEFAULT_SDK_VERSIONto the SDK release shipping emulator 0.21.0