From 8176bc37a250a31da255f6f209a2131984e1f011 Mon Sep 17 00:00:00 2001 From: Vijit Singh Date: Sun, 2 Aug 2026 11:12:03 -0500 Subject: [PATCH 1/2] feat(doctor): surface missing AES-NI / AVX2 instead of mining silently slow (#338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RandomX without AES-NI falls back to XMRig's soft-AES path, roughly 4x slower, and nothing anywhere said why — the last undelivered acceptance criterion from #1 ("unsupported hardware is surfaced rather than silently failing"). setup/apply now warn at configure time when the CPU flags lack aes (and, advisory, avx2 — that one only slows dataset init), and doctor counts a missing AES-NI as an issue. Judged only when an x86-style flags line exists in /proc/cpuinfo (same CPUINFO override util/proposed-grub.sh already uses); macOS, ARM and stubbed sandboxes read as unknown, and unknown never manufactures an issue — the #333 lockdown stance. Never aborts: a knowingly-old rig is a valid choice. Closes #338 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 10 ++++++++ rigforge.sh | 52 ++++++++++++++++++++++++++++++++++++++ tests/run.sh | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9fe290..0c78524 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ All notable changes to RigForge are documented here. The format is based on ## [Unreleased] +### Added + +- **Missing AES-NI / AVX2 is surfaced instead of mining silently slow (#338).** RandomX without + AES-NI falls back to XMRig's soft-AES path, roughly 4x slower, and nothing anywhere said why — the + last undelivered acceptance criterion from #1. `setup`/`apply` now warn at configure time when the + CPU flags lack `aes` (and, advisory, `avx2` — that one only slows dataset init), and `doctor` + counts a missing AES-NI as an issue. Judged only when an x86-style `flags` line exists in + `/proc/cpuinfo`; macOS, ARM and stubbed sandboxes read as unknown, and unknown never manufactures + an issue (the #333 lockdown stance). Never aborts: a knowingly-old rig is a valid choice. + ### Fixed - **Debian: dependency install no longer fails on `linux-tools-common` (#327).** The apt dependency diff --git a/rigforge.sh b/rigforge.sh index 2fefea9..7ad42c8 100755 --- a/rigforge.sh +++ b/rigforge.sh @@ -132,6 +132,9 @@ BIN_DIR="${BIN_DIR:-/usr/local/bin}" # Read-only system paths the `doctor` health check inspects (overridable for tests). MEMINFO="${MEMINFO:-/proc/meminfo}" +# CPU flags source for the ISA preflight (#338). Same override name util/proposed-grub.sh already +# uses for its pdpe1gb probe — one knob, and the test harness already isolates it from the host. +CPUINFO="${CPUINFO:-/proc/cpuinfo}" MSR_MODULE_DIR="${MSR_MODULE_DIR:-/sys/module/msr}" GOVERNOR_FILE="${GOVERNOR_FILE:-/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor}" HUGEPAGES_1G_NR="${HUGEPAGES_1G_NR:-/sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages}" @@ -1001,6 +1004,19 @@ generate_xmrig_config() { # where only one CCD has the V-cache). See issue #44. if [ "$OS_TYPE" != "Darwin" ]; then log "Detected CPU: ${CPU_MODEL:-unknown} — using XMRig auto-tuning (threads, asm, MSR, NUMA auto-detected)." + # ISA preflight (#338): surface missing AES-NI/AVX2 here, at the moment the rig is being + # configured, instead of letting unsupported hardware mine silently slow. Warn, never abort: + # XMRig still runs (soft AES / non-AVX2 init) and a knowingly-old rig is a valid choice. + local _missing_isa + _missing_isa=$(_cpu_missing_isa) + case " $_missing_isa " in *" aes "*) + warn "This CPU has no AES-NI: RandomX falls back to soft AES, roughly 4x slower. Mining will work, but expect a fraction of a modern CPU's rate." + ;; + esac + case " $_missing_isa " in *" avx2 "*) + warn "This CPU has no AVX2: dataset init will be slower (steady-state hashrate is unaffected)." + ;; + esac fi # Rig label for the pool `user` field (#22): any pool entry that didn't set its own `user` gets the @@ -4398,6 +4414,22 @@ _lockdown_state() { # -> none|integrity|confidentiality, or empty when unknown return 0 } +# RandomX ISA preflight (#338, the last acceptance criterion from #1): echo which of aes / avx2 the +# CPU lacks, from the kernel's flags line. RandomX without AES-NI silently falls back to XMRig's +# soft-AES path (~4x slower) and without AVX2 dataset init slows — neither aborts anything, so a rig +# on unsupported hardware "works" at a mysteriously bad rate unless somebody says why. Judged ONLY +# when an x86-style "flags" line exists: no flags line (macOS has no /proc, ARM cpuinfo says +# "Features", sandboxes stub the file) means unknown, and unknown never manufactures an issue — the +# same stance as #333's lockdown probe. `-w` so vaes/avx2_vnni style neighbors can't false-match. +_cpu_missing_isa() { # -> "aes", "avx2", "aes avx2", or empty when all present / undeterminable + grep -q '^flags' "$CPUINFO" 2>/dev/null || return 0 + local missing="" + grep -qw aes "$CPUINFO" || missing="aes" + grep -qw avx2 "$CPUINFO" || missing="$missing${missing:+ }avx2" + printf '%s' "$missing" + return 0 +} + # True when lockdown is at a level that blocks MSR writes (#333). LOCKDOWN_MSR sits below # LOCKDOWN_INTEGRITY_MAX in enum lockdown_reason (include/linux/security.h), so both `integrity` and # `confidentiality` deny the write; only `none` permits it. @@ -4759,6 +4791,26 @@ doctor() { _ck_warn "1GB HugePages not reserved (optional; needs a pdpe1gb CPU + reboot)" fi + # CPU ISA support (#338): a rig without AES-NI mines at soft-AES speed (~4x slower) with no + # error anywhere — exactly the "silently failing" #1's acceptance criterion forbids, so it's a + # counted issue. Missing AVX2 only slows dataset init: advisory. Quiet when there's no x86 + # flags line to judge (unknown, not unsupported). + local miss_isa + miss_isa=$(_cpu_missing_isa) + case " $miss_isa " in *" aes "*) + _ck_warn "CPU has no AES-NI — RandomX runs soft AES, roughly 4x slower; this hardware cannot mine at a competitive rate" + issues=$((issues + 1)) + ;; + *) + # The ok line only when there IS a flags line to have judged; no flags line = unknown, say nothing. + grep -q '^flags' "$CPUINFO" 2>/dev/null && _ck_ok "CPU supports AES-NI (hardware RandomX path)" || true + ;; + esac + case " $miss_isa " in *" avx2 "*) + _ck_info "CPU has no AVX2 — dataset init is slower (steady-state hashrate unaffected)" + ;; + esac + # Resolve the worker's xmrig.log once — the MSR-applied (#66) and HUGE PAGES checks both read it. local wr="" log_file="" if [ -f "$CONFIG_JSON" ]; then diff --git a/tests/run.sh b/tests/run.sh index b01cec6..4b6599f 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -3468,6 +3468,77 @@ assert_contains "bios menu: Gigabyte Secure Boot path (#333)" "$(bm_sb "Gigabyte assert_contains "bios menu: MSI Secure Boot path (#333)" "$(bm_sb "Micro-Star International")" "Windows OS Configuration" assert_contains "bios menu: unknown vendor falls back generically (#333)" "$(bm_sb "Some OEM")" "usually under Boot or Security" +# #338 (the last #1 acceptance criterion): missing AES-NI/AVX2 must be SURFACED — soft-AES mining is +# ~4x slower with no error anywhere. AES-NI missing = counted doctor issue + setup warn; AVX2 missing = +# advisory only; no x86 "flags" line (macOS, ARM's "Features", stubs) = unknown = silence, never an issue. +echo "== unit: CPU ISA preflight — AES-NI / AVX2 surfaced (#338) ==" +printf 'processor : 0\nflags : fpu vme aes avx avx2 vaes\n' >"$DOC/cpuinfo_full" +# vaes but NOT the standalone aes word: proves the -w match can't be satisfied by a neighbor flag. +printf 'processor : 0\nflags : fpu vme avx avx2 vaes\n' >"$DOC/cpuinfo_noaes" +printf 'processor : 0\nflags : fpu vme aes avx\n' >"$DOC/cpuinfo_noavx2" +printf 'processor : 0\nflags : fpu vme avx\n' >"$DOC/cpuinfo_neither" +printf 'processor : 0\nFeatures : fp asimd aes\n' >"$DOC/cpuinfo_arm" # ARM shape: no "flags" line + +# --- the pure helper, exercised directly --- +isa_miss() { (source "$SCRIPT" && CPUINFO="$1" _cpu_missing_isa); } +assert_eq "isa: full flags -> nothing missing (#338)" "$(isa_miss "$DOC/cpuinfo_full")" "" +assert_eq "isa: vaes does not satisfy the aes word-match (#338)" "$(isa_miss "$DOC/cpuinfo_noaes")" "aes" +assert_eq "isa: missing avx2 reported alone (#338)" "$(isa_miss "$DOC/cpuinfo_noavx2")" "avx2" +assert_eq "isa: both missing, space-separated (#338)" "$(isa_miss "$DOC/cpuinfo_neither")" "aes avx2" +assert_eq "isa: ARM Features line -> unknown, not unsupported (#338)" "$(isa_miss "$DOC/cpuinfo_arm")" "" +assert_eq "isa: absent cpuinfo -> unknown (#338)" "$(isa_miss "/nonexistent-cpuinfo")" "" + +# --- doctor: counted for aes, advisory for avx2, silent on unknown --- +out="$(CPUINFO="$DOC/cpuinfo_noaes" run_doctor "$DOC/meminfo_ok" "$DOC/msrmod" "$DOC/gov_perf" "$DOC/nr1g")" +assert_contains "doctor: missing AES-NI named (#338)" "$out" "CPU has no AES-NI" +assert_contains "doctor: missing AES-NI is a counted issue (#338)" "$out" "issue(s) found" +out="$(CPUINFO="$DOC/cpuinfo_noavx2" run_doctor "$DOC/meminfo_ok" "$DOC/msrmod" "$DOC/gov_perf" "$DOC/nr1g")" +assert_contains "doctor: missing AVX2 is advisory (#338)" "$out" "CPU has no AVX2" +assert_contains "doctor: missing AVX2 alone still passes (#338)" "$out" "all critical checks passed" +out="$(CPUINFO="$DOC/cpuinfo_full" run_doctor "$DOC/meminfo_ok" "$DOC/msrmod" "$DOC/gov_perf" "$DOC/nr1g")" +assert_contains "doctor: AES-NI present reported ok (#338)" "$out" "CPU supports AES-NI" +out="$(CPUINFO="$DOC/cpuinfo_arm" run_doctor "$DOC/meminfo_ok" "$DOC/msrmod" "$DOC/gov_perf" "$DOC/nr1g")" +assert_absent "doctor: unknown ISA raises no alarm (#338)" "$out" "AES-NI" + +# --- setup path: generate_xmrig_config warns at configure time, and never aborts --- +export STUB_CPU_MODEL="Old Xeon E5405" STUB_NPROC=4 STUB_HOSTNAME=rigbox +ISA338="$(mktemp -d "$SANDBOX/isa338.XXXXXX")" +gen338_out="$( + cd "$ISA338" || exit 1 + source "$SCRIPT" + OS_TYPE=Linux + WORKER_ROOT="$ISA338" + POOL_ADDRESS=myrig.local + POOLS_JSON='[{"url":"myrig.local:3333","user":"","pass":"x","keepalive":true,"tls":false,"enabled":true}]' + ACCESS_TOKEN=tok123 + DONATION=1 + LOGROTATE_DIR="$ISA338" + CPUINFO="$DOC/cpuinfo_neither" + set +e + PATH="$STUBS:$PATH" generate_xmrig_config 2>&1 +)" +assert_rc "config-gen still succeeds on unsupported hardware (#338)" "$?" "0" +assert_contains "config-gen warns about missing AES-NI (#338)" "$gen338_out" "no AES-NI" +assert_contains "config-gen warns about missing AVX2 (#338)" "$gen338_out" "no AVX2" +assert_contains "config-gen: the config was still generated (#338)" "$(J "$ISA338/config.json" '.pools[0].url')" "myrig.local:3333" +# A fully-capable CPU stays quiet — the warn must not become noise on normal rigs. +QUIET338="$(mktemp -d "$SANDBOX/isaq338.XXXXXX")" +genq_out="$( + cd "$QUIET338" || exit 1 + source "$SCRIPT" + OS_TYPE=Linux + WORKER_ROOT="$QUIET338" + POOL_ADDRESS=myrig.local + POOLS_JSON='[{"url":"myrig.local:3333","user":"","pass":"x","keepalive":true,"tls":false,"enabled":true}]' + ACCESS_TOKEN=tok123 + DONATION=1 + LOGROTATE_DIR="$QUIET338" + CPUINFO="$DOC/cpuinfo_full" + set +e + PATH="$STUBS:$PATH" generate_xmrig_config 2>&1 +)" +assert_absent "config-gen: no ISA warning on a capable CPU (#338)" "$genq_out" "AES-NI" + # #278: doctor reports control receiver health when `control` is enabled. Active + responding (200 or # 503, per util/control-server.py) is ok; enabled-but-down (service inactive, or active but not # answering) warns with a hint and counts as an issue; disabled prints no control-receiver lines at all. From 56ef0cb0a3b507cee1bc8233ed4c5ba495fb57cb Mon Sep 17 00:00:00 2001 From: Vijit Singh Date: Sun, 2 Aug 2026 11:18:19 -0500 Subject: [PATCH 2/2] refactor: collapse one-arm case blocks to [[ ]] guards (ponytail-review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Fable 5 --- rigforge.sh | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/rigforge.sh b/rigforge.sh index 7ad42c8..322fa62 100755 --- a/rigforge.sh +++ b/rigforge.sh @@ -1009,14 +1009,8 @@ generate_xmrig_config() { # XMRig still runs (soft AES / non-AVX2 init) and a knowingly-old rig is a valid choice. local _missing_isa _missing_isa=$(_cpu_missing_isa) - case " $_missing_isa " in *" aes "*) - warn "This CPU has no AES-NI: RandomX falls back to soft AES, roughly 4x slower. Mining will work, but expect a fraction of a modern CPU's rate." - ;; - esac - case " $_missing_isa " in *" avx2 "*) - warn "This CPU has no AVX2: dataset init will be slower (steady-state hashrate is unaffected)." - ;; - esac + [[ " $_missing_isa " == *" aes "* ]] && warn "This CPU has no AES-NI: RandomX falls back to soft AES, roughly 4x slower. Mining will work, but expect a fraction of a modern CPU's rate." + [[ " $_missing_isa " == *" avx2 "* ]] && warn "This CPU has no AVX2: dataset init will be slower (steady-state hashrate is unaffected)." fi # Rig label for the pool `user` field (#22): any pool entry that didn't set its own `user` gets the @@ -4797,19 +4791,14 @@ doctor() { # flags line to judge (unknown, not unsupported). local miss_isa miss_isa=$(_cpu_missing_isa) - case " $miss_isa " in *" aes "*) + if [[ " $miss_isa " == *" aes "* ]]; then _ck_warn "CPU has no AES-NI — RandomX runs soft AES, roughly 4x slower; this hardware cannot mine at a competitive rate" issues=$((issues + 1)) - ;; - *) + else # The ok line only when there IS a flags line to have judged; no flags line = unknown, say nothing. grep -q '^flags' "$CPUINFO" 2>/dev/null && _ck_ok "CPU supports AES-NI (hardware RandomX path)" || true - ;; - esac - case " $miss_isa " in *" avx2 "*) - _ck_info "CPU has no AVX2 — dataset init is slower (steady-state hashrate unaffected)" - ;; - esac + fi + [[ " $miss_isa " == *" avx2 "* ]] && _ck_info "CPU has no AVX2 — dataset init is slower (steady-state hashrate unaffected)" # Resolve the worker's xmrig.log once — the MSR-applied (#66) and HUGE PAGES checks both read it. local wr="" log_file=""