diff --git a/rigforge.sh b/rigforge.sh index 174bc7f..73ef954 100755 --- a/rigforge.sh +++ b/rigforge.sh @@ -359,6 +359,12 @@ parse_config() { # attribute in-string program lines, and the patch-coverage gate needs every new line hittable. POOLS_JSON=$(jq -c --argjson base "$POOLS_JSON" '[$base, [.pools[] | ."tls-fingerprint"]] | transpose | map(.[0] + (if (.[1] // null) != null then {"tls-fingerprint": .[1]} else {} end))' "$CONFIG_JSON") || error "Could not parse 'pools' in $CONFIG_JSON." + # #265: jq's `//` treats an explicit false like null/missing, so the map above rewrites an + # operator's "keepalive": false / "enabled": false to the true default. Restore explicit falses + # from the raw config in another single-line pass (same kcov rationale as #115 above). Only a + # boolean false matches `== false`; non-boolean junk flows through `//` untouched to validation. + POOLS_JSON=$(jq -c --argjson base "$POOLS_JSON" '[$base, [.pools[] | [(.keepalive == false), (.enabled == false)]]] | transpose | map(.[0] + (if .[1][0] then {"keepalive": false} else {} end) + (if .[1][1] then {"enabled": false} else {} end))' "$CONFIG_JSON") || error "Could not parse 'pools' in $CONFIG_JSON." + # Validate every pool field — fail fast with a clear message rather than writing a config XMRig # would choke on. url must be host:port: a valid hostname / IPv4 / bracketed-IPv6 host and a port # in 1-65535; user/pass reject whitespace and shell/control characters; keepalive/tls/enabled @@ -2436,10 +2442,10 @@ _gridsearch() { # from what the generated config actually uses (both default false). Shared by the seeds. _seed_hj() { jq -r '.cpu."huge-pages-jit" // false' "$TUNE_BASE"; } _seed_cq() { jq -r '.randomx.cache_qos // false' "$TUNE_BASE"; } -_seed_g() { jq -r '.randomx."1gb-pages" // true' "$TUNE_BASE"; } # base 1gb-pages value (default true) +_seed_g() { jq -r '.randomx | if has("1gb-pages") then ."1gb-pages" else true end' "$TUNE_BASE"; } # base 1gb-pages value (default true) # wrmsr seed: the base config's value as a single token (true/false/number). An array (advanced custom # MSRs) isn't a sweepable scalar, so seed from the safe default 'true' and let the operator set TUNE_WRMSR. -_seed_wr() { jq -r '(.randomx.wrmsr // true) | if (type=="boolean" or type=="number") then tostring else "true" end' "$TUNE_BASE"; } +_seed_wr() { jq -r '(.randomx | if has("wrmsr") then .wrmsr else true end) | if (type=="boolean" or type=="number") then tostring else "true" end' "$TUNE_BASE"; } # Seed the state with XMRig's auto baseline (the generated config's own values; threads left to auto). _seed_auto() { diff --git a/tests/run.sh b/tests/run.sh index 24b9e9a..d95d553 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -269,6 +269,10 @@ c="$(mkconf p_full "{ \"pools\": [{\"url\":\"pool.example:443\",\"tls\":true,\"p assert_eq "explicit url kept" "$(PJ "$c" | jq -r '.[0].url')" "pool.example:443" assert_eq "explicit tls kept" "$(PJ "$c" | jq -c '.[0].tls')" "true" assert_eq "explicit pass kept" "$(PJ "$c" | jq -r '.[0].pass')" "w" +# #265: jq `//` treats explicit false the same as null/missing — an operator's false must survive. +c="$(mkconf p_falsebool "{ \"pools\": [{\"url\":\"h:3333\",\"enabled\":false,\"keepalive\":false}] }")" +assert_eq "explicit enabled:false kept (#265)" "$(PJ "$c" | jq -c '.[0].enabled')" "false" +assert_eq "explicit keepalive:false kept (#265)" "$(PJ "$c" | jq -c '.[0].keepalive')" "false" # A Pithead stratum password (p2pool.stratum_password) flows through verbatim as the pool pass — the # cross-repo contract for an authenticated stack. Covers an auto-generated hex secret and a literal # with the punctuation Pithead allows (. _ : @ -); both are valid XMRig passes. @@ -378,6 +382,16 @@ assert_rc "non-boolean keepalive rejected" "$?" "1" c="$(mkconf p_baden "{ \"pools\": [{\"url\":\"h:3333\",\"enabled\":1}] }")" parse_rc "$c" "$ROOT" assert_rc "non-boolean enabled rejected" "$?" "1" +# #265: the has() guard must still let bogus values through to validation, not swallow them. +c="$(mkconf p_enstr "{ \"pools\": [{\"url\":\"h:3333\",\"enabled\":\"yes\"}] }")" +out="$( ( + source "$SCRIPT" + CONFIG_JSON="$c" + SCRIPT_DIR="$ROOT" + set +e + parse_config 2>&1 +))" +assert_contains "non-boolean string enabled still rejected by validation (#265)" "$out" "Pool enabled must be true or false" # HOME_DIR must be DYNAMIC_HOME or a clean absolute path. c="$(mkconf hd_rel "{ \"HOME_DIR\": \"relative/path\", $POOL }")" parse_rc "$c" "$ROOT" @@ -4292,6 +4306,23 @@ assert_eq "throttled faster candidate not adopted (#62)" "$(J "$OVR" '.randomx.s out="$(throttle_run 0)" assert_eq "TUNE_MIN_FREQ_MHZ=0 disables the throttle skip (#62 control)" "$(J "$OVR" '.randomx.scratchpad_prefetch_mode')" "2" +# #265: _seed_wr / _seed_g must keep an explicit base-config false instead of jq `//` flipping it to +# the true default; with the key absent, the true default still applies. +echo "== unit: tune seeds keep explicit false (#265) ==" +seed_base() { # + local f="$SANDBOX/seed_base.json" + printf '%s' "$2" >"$f" + ( + source "$SCRIPT" + TUNE_BASE="$f" + "$1" + ) +} +assert_eq "_seed_wr keeps explicit wrmsr:false (#265)" "$(seed_base _seed_wr '{"randomx":{"wrmsr":false}}')" "false" +assert_eq "_seed_g keeps explicit 1gb-pages:false (#265)" "$(seed_base _seed_g '{"randomx":{"1gb-pages":false}}')" "false" +assert_eq "_seed_wr defaults to true when wrmsr absent (#265)" "$(seed_base _seed_wr '{"randomx":{}}')" "true" +assert_eq "_seed_g defaults to true when 1gb-pages absent (#265)" "$(seed_base _seed_g '{"randomx":{}}')" "true" + # #66: the opt-in wrmsr knob sweeps MSR presets. A fake whose hashrate depends on randomx.wrmsr proves the # knob is swept and the winner pinned; a single value leaves it off (not pinned, but still recorded). echo "== black-box: tune wrmsr knob (#66) =="