Skip to content

Add reproducible-build feature; fix DARCH→ARCH; std::fesetround compat#1

Closed
kim0 wants to merge 1 commit into
developmentfrom
feat/reproducible-build-flag
Closed

Add reproducible-build feature; fix DARCH→ARCH; std::fesetround compat#1
kim0 wants to merge 1 commit into
developmentfrom
feat/reproducible-build-flag

Conversation

@kim0

@kim0 kim0 commented May 17, 2026

Copy link
Copy Markdown
Owner

Motivation

Let downstreams that need reproducible, byte-identical release binaries
(notably the cuprate Monero node) opt out of the -march=native CPU-
specific codegen that miners want, without forking the crate.

Changes

  1. New reproducible-build Cargo feature (off by default). When on,
    the bundled RandomX is built with -DARCH=default, producing the
    same object code on every host. Miners leave it off and keep current
    behaviour.

  2. Fix DARCHARCH in .define(...). The previous call set a
    CMake variable literally named DARCH, which RandomX's CMakeLists
    doesn't read; only ARCH is consulted. Every existing build of this
    crate has been getting whatever RandomX's CMake ARCH default is,
    not the native mode the build script thinks it's selecting. This
    is a strict bug fix.

  3. std::fesetround compat patch applied at build time to
    RandomX/src/instructions_portable.cpp. Under libstdc++
    configurations where the global-namespace fesetround isn't reliably
    exposed via <cfenv> (e.g. Guix's gcc-15.2 with
    _GLIBCXX_HAVE_FENV_H undefined in c++config.h), the unqualified
    call fails to compile. Qualifying it as std::fesetround works
    everywhere <cfenv> works and is a no-op on toolchains that already
    exposed the symbol globally. Applied as a build-script patch so the
    RandomX submodule pointer stays at the tevador upstream commit;
    becomes a no-op once tevador/RandomX picks up the same fix.

Verification

Built with the reproducible-build feature in the cuprate Guix
reproducible-build pipeline; 5 independent builds across separate pods
produced byte-identical cuprated artifacts (SHA256
cc1f523a68bf61304c0c4b8fba4352aaea4626491460e95795b3e5b0f35c15d0).

Upstreaming

This branch lives in kim0/randomx-rs. Intent is to keep it open here
while the matching cuprate PR is reviewed, then upstream to
Cuprate/randomx-rs:development once both are agreed upon.

Two related improvements that let downstreams ship deterministic, byte-
reproducible binaries linked against randomx-rs without forcing every
miner to give up native CPU codegen:

1. New `reproducible-build` Cargo feature (off by default). When on,
   the bundled RandomX is built with `-DARCH=default`, omitting the
   `-march=native` / CPU-specific instructions and producing the same
   object code on every host. Hash-rate-sensitive miners simply leave
   the feature off and get current upstream behaviour.

2. The cmake invocation previously passed `.define("DARCH", "native")`,
   which sets a CMake variable literally named `DARCH`. RandomX's own
   CMakeLists.txt only consults `ARCH`. Result: every existing build of
   randomx-rs has been shipping whatever RandomX's CMake default
   happens to be, not the `native` mode that build.rs intends. Fixed
   to pass `.define("ARCH", ...)`.

3. Apply a build-time patch to RandomX/src/instructions_portable.cpp
   qualifying the single `fesetround(mode)` call as `std::fesetround`.
   Under libstdc++ configurations where the global namespace symbol
   isn't reliably exposed (e.g. Guix's gcc-15.2 with _GLIBCXX_HAVE_FENV_H
   undef'd in c++config.h), the unqualified call fails to compile;
   qualifying it is a no-op everywhere it already worked.
kim0 pushed a commit to kim0/cuprate that referenced this pull request May 17, 2026
cuprated's reproducible-build pipeline (contrib/guix) needs RandomX
built without -march=native so the produced binary is identical
across CPUs. randomx-rs upstream defaults to native (correct for
miners, hostile to reproducible release artifacts).

Point at kim0/randomx-rs branch feat/reproducible-build-flag, which
adds an opt-in \`reproducible-build\` Cargo feature that flips
RandomX's CMake ARCH to "default", plus two bug fixes that travel
with it:

  - DARCH -> ARCH: the previous .define(\"DARCH\", \"native\") in
    randomx-rs build.rs set a CMake variable RandomX never reads.
    Every existing build of the crate has been shipping whatever
    RandomX's CMake default happened to be. Strict bug fix.
  - std::fesetround compat patch applied at build time to
    RandomX/src/instructions_portable.cpp. Lets the crate compile
    under libstdc++ configurations where <cfenv> doesn't expose
    fesetround in the global namespace.

Both PRs (kim0/randomx-rs#1 here, and the
cuprate PR using it) are intended for eventual upstream into
Cuprate/randomx-rs and cuprate-crypto/cuprate respectively.
@kim0

kim0 commented May 17, 2026

Copy link
Copy Markdown
Owner Author

Closing this PR — after digging deeper into RandomX's CMake, the changes here turn out to be unnecessary.

The root finding: The DARCH -> ARCH line is a typo, but a fortunate one. cmake-rs's .define("DARCH", "native") emits -DDARCH=native, which sets a CMake variable named DARCH. Upstream tevador/RandomX's CMakeLists.txt has zero references to DARCH — it reads ARCH, which defaults to "default" when unset:

if(NOT ARCH)
  set(ARCH "default")
endif()
...
if(ARCH STREQUAL "native")
  add_flag("-march=native")

So -march=native has never actually been emitted by this build. The non-native branch only adds -maes -mssse3 -mavx2, all compiler-capability-gated (not host-CPU-gated). The build has always been compiler-deterministic and host-CPU-independent.

Implications for the consumer side (cuprate): the build is already reproducible across CPUs given a pinned toolchain. The Guix pipeline I'm proposing on the cuprate side handles the only real issue (libstdc++'s <cfenv> not exposing fesetround) via a global -D_GLIBCXX_HAVE_FENV_H=1 -D_GLIBCXX_USE_C99_FENV=1 in CXXFLAGS, so the std::fesetround source patch here is redundant belt-and-suspenders.

What I'm doing instead: the cuprate PR (kim0/cuprate#2) is being restructured to depend on upstream Cuprate/randomx-rs unchanged, drop the kim0/randomx-rs fork dependency, and document the DARCH typo finding in the threat-model README so future reviewers don't go down the same path I did.

The branch feat/reproducible-build-flag stays on this fork as a record. If at some point the DARCH typo is fixed in upstream Cuprate/randomx-rs (it's still a legitimate small bug, even if currently benign), it can be filed separately as a one-line PR.

Thanks to two GPT-pro reviews for catching this — both flagged the inconsistency that the comment in the new build.rs claimed RandomX reads ARCH but the old code (now removed) was setting DARCH, which implies the old default was already "default". Verifying that against upstream's CMake closed it out.

@kim0 kim0 closed this May 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant