Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions rigforge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down Expand Up @@ -1001,6 +1004,13 @@ 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)
[[ " $_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
Expand Down Expand Up @@ -4398,6 +4408,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.
Expand Down Expand Up @@ -4759,6 +4785,21 @@ 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)
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
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=""
if [ -f "$CONFIG_JSON" ]; then
Expand Down
71 changes: 71 additions & 0 deletions tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down