194 lines
9.7 KiB
Markdown
194 lines
9.7 KiB
Markdown
# Native RandomX engine
|
|
|
|
This is the CPU proof engine used by the Wikideck testnet node and explicit opt-in
|
|
native miner. Browser mining has been removed from the active application. The
|
|
Docker image builds this pinned implementation and runs its known-vector test.
|
|
There is no GPU path, simulated hash, or production fallback hash.
|
|
|
|
## Source and build
|
|
|
|
[RandomX v1.2.1](https://github.com/tevador/RandomX/tree/102f8acf90a7649ada410de5499a7ec62e49e1da)
|
|
is fetched by CMake FetchContent from the official HTTPS Git repository, pinned to
|
|
**102f8acf90a7649ada410de5499a7ec62e49e1da** (also the upstream v1.2.1 tag).
|
|
The first configuration requires network access and Git; later builds reuse the
|
|
checkout. Keep upstream copyright/license notices when distributing binaries.
|
|
Do not override FetchContent sources with untrusted local code.
|
|
|
|
Requirements: CMake 3.24+, Git, a C/C++17 toolchain, and a 64-bit host for fast mode.
|
|
MSVC needs its C++ and MASM tools in a developer terminal. MinGW needs its compiler
|
|
and make on PATH; select the `MinGW Makefiles` generator. Linux/macOS need their
|
|
normal C/C++ build tools. No administrator privileges or huge pages are required.
|
|
On MSVC, upstream's pinned build invokes its own PowerShell assembly-generation
|
|
script; review upstream build tooling before running under restrictive policies.
|
|
|
|
From the repository root:
|
|
|
|
```sh
|
|
cmake -S native -B native/build -DCMAKE_BUILD_TYPE=Release -DWIKIDECK_RANDOMX_TESTS=OFF
|
|
cmake --build native/build --config Release --target wikideck-randomx --parallel 2
|
|
```
|
|
|
|
Convenience scripts (two compiler jobs by default):
|
|
|
|
```sh
|
|
sh scripts/build-randomx.sh
|
|
sh scripts/build-randomx.sh --test
|
|
```
|
|
|
|
```powershell
|
|
./scripts/build-randomx.ps1
|
|
./scripts/build-randomx.ps1 -Generator 'MinGW Makefiles' -Test
|
|
```
|
|
|
|
The scripts propagate configure/build/test failures. PowerShell execution policy
|
|
must permit the script; do not bypass a managed policy. POSIX customization:
|
|
`JOBS=2`, `RANDOMX_BUILD_DIR=/absolute/build/path`; PowerShell customization:
|
|
`-Jobs 2`, `-BuildDirectory <path>`. Select a toolchain/generator through the normal
|
|
CMake environment or a manual configuration. Don't switch generators in an
|
|
existing build directory.
|
|
|
|
All generators place the executable directly under [native/build](../native/build)
|
|
(with `.exe` on Windows), including Release builds with multi-config generators.
|
|
Upstream tests/benchmark/codegen are excluded from the default build: v1.2.1 does
|
|
not provide a tests-disable option. No benchmark or dataset-heavy fast-mode test
|
|
runs automatically.
|
|
|
|
## Modes and resource use
|
|
|
|
Run `wikideck-randomx` with no arguments for **light** mode (256 MiB cache plus
|
|
approximately 2 MiB VM scratchpad and runtime overhead). `--fast` adds the real
|
|
approximately 2080 MiB dataset; the 256 MiB cache is retained for key changes, so
|
|
budget over 2.3 GiB per process. Dataset initialization is single-threaded and can
|
|
be expensive. Neither mode allocates RandomX memory until its first valid request.
|
|
Cache, dataset, and VM are reused for the same key. A changed key reinitializes the
|
|
cache and, in fast mode, the entire dataset before creating a fresh VM.
|
|
|
|
Flags start with `randomx_get_flags()` (CPU-supported AES/JIT/Argon2 optimization).
|
|
`RANDOMX_FLAG_SECURE` is explicitly added for VM JIT W^X, and upstream is compiled
|
|
with `RANDOMX_FORCE_SECURE` for cache JIT policy too. Platforms without JIT use the
|
|
reference interpreter; allocation/JIT failures are fatal, never a fake PoW result.
|
|
No large pages are requested. RAII releases VM, dataset, then cache on normal EOF
|
|
and caught errors. Public PoW key/cache data is not secret and is released, not
|
|
claimed to be securely erased from physical memory.
|
|
|
|
## Line protocol
|
|
|
|
One ASCII command per LF-terminated line; CRLF is also accepted. Exactly one ASCII
|
|
response line, flushed immediately, per valid command:
|
|
|
|
```text
|
|
hash <key64hex> <blobhex>
|
|
<hash64hex>
|
|
|
|
mine <key64hex> <blobhex> <target64hex> <nonceOffsetDecimal> <maxAttemptsDecimal>
|
|
found <hash64hex> <nonceDecimal>
|
|
exhausted
|
|
```
|
|
|
|
`found` and `exhausted` are alternatives, not two responses. Input hex may use
|
|
either case; output hashes are lowercase in the RandomX byte order. Keys and
|
|
targets are exactly 32 bytes (64 hex characters); blobs are 1..8192 bytes.
|
|
Tokens use exactly one ASCII space, without leading/trailing whitespace. Commands
|
|
are capped at 16600 bytes before LF. Invalid characters, malformed/overflowing
|
|
unsigned decimal, extra tokens, invalid bounds, or a partial final line cause a
|
|
concise stderr error and nonzero exit, with no fabricated response on stdout.
|
|
Empty stdin EOF exits successfully. There are no stdout banners or diagnostic logs.
|
|
|
|
Mining requires `maxAttempts` in 1..10000 and `nonceOffset + 8 <= blob.size`.
|
|
The initial nonce is read from the blob's existing **eight little-endian bytes**.
|
|
The first attempt uses that nonce, then increments by one and rewrites LE8 for
|
|
each attempt. Only those eight bytes change. At uint64 maximum, search ends
|
|
without wrapping. The native response contains an exact unsigned decimal nonce.
|
|
|
|
**Target convention: interpret the 32 output bytes and target bytes as unsigned
|
|
BIG-ENDIAN integers; accept `hash <= target`. This intentionally differs from
|
|
Monero's convention.** In particular, equal hashes pass. Never reverse the hash
|
|
or target when using this interface.
|
|
|
|
## Node API
|
|
|
|
[RandomX](../server/testnet/randomx.ts) exports:
|
|
|
|
```ts
|
|
interface PowEngine {
|
|
hash(key: string, blob: Uint8Array): Promise<string>;
|
|
close(): void;
|
|
}
|
|
class RandomX implements PowEngine {
|
|
constructor(options?: { binary?: string; fast?: boolean });
|
|
hash(key: string, blob: Uint8Array): Promise<string>;
|
|
mine(key: string, blob: Uint8Array, target: string,
|
|
nonceOffset: number, maxAttempts: number):
|
|
Promise<{ hash: string; nonce: number } | null>;
|
|
close(): void;
|
|
}
|
|
```
|
|
|
|
Binary precedence: `options.binary`, `RANDOMX_BINARY`, then the executable under
|
|
the repository's native build directory (resolved relative to this module,
|
|
independent of working directory). Relative explicit paths are resolved against
|
|
the caller's working directory. Configure only a trusted executable: this is a
|
|
native-code trust boundary, not a sandbox. The adapter uses `spawn` with a fixed
|
|
argument array, `shell: false`, and pipe stdio; user data only enters stdin after
|
|
strict validation. Missing/non-executable files throw in the constructor. Other
|
|
spawn failures are retained and reject current/future requests.
|
|
|
|
Each instance owns one long-lived child, one active command, and at most 64 total
|
|
active/queued requests. Calls are FIFO, including mixed hash/mine calls and key
|
|
changes. Inputs are snapshotted before queuing. The 120-second deadline starts
|
|
when each command is sent (includes cold cache/dataset initialization); queued
|
|
requests wait for earlier work. Keep mining batches small enough to finish on the
|
|
host CPU. The process is killed on timeout, malformed/unsolicited/oversized output,
|
|
stream error, or crash, rejecting all outstanding work and subsequent calls.
|
|
Responses and retained stderr are bounded. No automatic retry or process restart.
|
|
|
|
`mine` returns `null` for exhaustion and validates returned hash syntax, target,
|
|
and nonce range (it does not independently recompute a native result). Because the
|
|
API returns `number`, its entire requested nonce range must fit
|
|
`Number.MAX_SAFE_INTEGER`; higher uint64 nonces are supported only by the direct
|
|
native protocol. Invalid inputs reject without sending native work.
|
|
|
|
Always call `close()` in `finally`. It is idempotent, rejects pending/queued work,
|
|
and sends EOF for graceful RAII cleanup; a busy child is forcibly killed after
|
|
one second. A failed/closed instance cannot be reopened.
|
|
|
|
## Real upstream known-vector check
|
|
|
|
The opt-in `--self-test` executable argument validates upstream's exact
|
|
[Hash test 1a/2a](https://github.com/tevador/RandomX/blob/102f8acf90a7649ada410de5499a7ec62e49e1da/src/tests/tests.cpp):
|
|
|
|
- Key: ASCII `test key 000` (12 bytes, **not NUL-terminated or zero-padded**).
|
|
- Input: ASCII `This is a test` (14 bytes, no terminator).
|
|
- Expected RandomX hash:
|
|
`639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f`.
|
|
|
|
The self-test uses an internal API so the line protocol retains its strict
|
|
32-byte-key requirement. Padding this upstream key to 32 bytes would change the
|
|
hash and is not a valid reproduction of the vector. Self-test success is reported
|
|
on stderr only, with exit zero; mismatch exits nonzero. `--fast --self-test` checks
|
|
the same real vector in dataset mode, but requires the full memory allocation.
|
|
|
|
```sh
|
|
cmake -S native -B native/build -DCMAKE_BUILD_TYPE=Release -DWIKIDECK_RANDOMX_TESTS=ON
|
|
cmake --build native/build --config Release --target wikideck-randomx --parallel 2
|
|
ctest --test-dir native/build -C Release --output-on-failure
|
|
```
|
|
|
|
This test is OFF by default and is separate from the application's tests. Without
|
|
a built native binary, no native engine or native test is run by the application.
|
|
|
|
## Validation record
|
|
|
|
Verified on Windows x64 with CMake 3.26.4, MinGW GCC 13.1.0 and Node 24.22.0:
|
|
|
|
- Release native build with two compiler jobs; opt-in CTest upstream vector passed.
|
|
- Production TypeScript/application build passed.
|
|
- Ad-hoc actual-native light-mode checks passed: FIFO hashes, same-key reuse,
|
|
key changes and restoration, mining success/exhaustion, target equality and
|
|
big-endian comparison, LE8 nonce handling, 8192-byte blob, strict invalid-input
|
|
rejection, native uint64 nonce output, missing binary, queue bounds, crash
|
|
rejection, idempotent close, and stdin EOF.
|
|
- Both build scripts passed syntax checks.
|
|
|
|
Fast-mode allocation/runtime and Linux/macOS/MSVC builds were not run. The memory
|
|
and initialization cost of the full dataset remains an explicit opt-in. |