From c5c2a5a069444535214a9893702ef7ff4e86f61d Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Sun, 4 Oct 2026 16:54:02 +0000 Subject: [PATCH] Igneum Miner jobs: the wsl-prover probe logs why it fails, tries the configured then the default WSL user with cargo and sp1 on PATH, and accepts the shipped wsl2\bin prover; publish-jobs --requires none|"" = no requirement PC 2 on 0.3.3 logged 'needs wsl-prover' although the toolchain is there as user [user]. Every negative probe now lands in the engine log with its exit code and the first 200 characters of output (it reaches the intake). The probe sources ~/.cargo/env and sets ~/.cargo/bin and ~/.sp1/bin itself (a login shell from a console-less process need not), runs as the job's wsl_user or IGNEUM_APP_WSL_USER first and the distro default user second, and a payload with wsl2\bin\igneum-prove-host next to the app meets the requirement when the distro answers. publish-jobs.sh: --requires none or "" publishes an empty list (none was a literal requirement, "" fell back to the kind's default). Co-Authored-By: Claude Fable 5.1 --- app/igneum-app/src/jobrun.rs | 99 +++++++++++++++++++++++++++++------ packaging/ota/publish-jobs.sh | 9 ++-- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/app/igneum-app/src/jobrun.rs b/app/igneum-app/src/jobrun.rs index d4417fbf8..09bea99e3 100644 --- a/app/igneum-app/src/jobrun.rs +++ b/app/igneum-app/src/jobrun.rs @@ -269,20 +269,34 @@ impl Jobs { let now = crate::platform::unix_now(); let platform = manifest::platform_name(); let probes: Mutex> = Mutex::new(std::collections::HashMap::new()); - let have = |req: &str| -> bool { - if let Some(v) = probes.lock().unwrap().get(req) { - return *v; - } - let v = probe(req); - probes.lock().unwrap().insert(req.to_string(), v); - v - }; + let env_user = std::env::var("IGNEUM_APP_WSL_USER").unwrap_or_default(); let mut runnable = Vec::new(); let mut pending = Vec::new(); for j in &f.jobs { if seen.contains(&j.id) { continue; } + // the probe runs once per requirement and WSL user per poll; every negative result is logged + // with its exit code and output (PC 2, 0.3.3: "needs wsl-prover" on a machine that had it) + let user = { let u = j.str_param("wsl_user"); if u.is_empty() { env_user.clone() } else { u } }; + let have = |req: &str| -> bool { + let key = format!("{req}|{user}"); + if let Some(v) = probes.lock().unwrap().get(&key) { + return *v; + } + let v = match probe(req, &user) { + Ok(how) => { + shared2.log(&format!("probe {req}: ok ({how})")); + true + } + Err(e) => { + shared2.log(&format!("probe {req}{}: NOT met: {e}", if user.is_empty() { String::new() } else { format!(" (user {user})") })); + false + } + }; + probes.lock().unwrap().insert(key, v); + v + }; match jobs::eligibility(j, &machine_id, platform, now, &have) { Eligibility::Run => runnable.push(j.clone()), Eligibility::Needs(m) => pending.push((j.id.clone(), m)), @@ -573,15 +587,70 @@ fn fetch_jobs(url: &str, dir: &Path) -> Result { Ok(f) } -/// Does this machine meet a requirement? Probed on the fetch thread, once per poll. -fn probe(req: &str) -> bool { +/// What the toolchain probe runs inside the distro: the cargo and sp1 paths set by hand (a login shell from a +/// process without a console need not source ~/.cargo/env), then the three things the prover needs. +const PROVER_PROBE: &str = "export PATH=\"$HOME/.cargo/bin:$HOME/.sp1/bin:$PATH\"; [ -f \"$HOME/.cargo/env\" ] && . \"$HOME/.cargo/env\"; command -v cargo && ls -d \"$HOME/.sp1\" \"$HOME/igneum-prove\" && echo \"user $(id -un) home $HOME\""; + +fn short_out(s: &str) -> String { + let t: String = s.replace(['\r', '\n'], " ").split_whitespace().collect::>().join(" "); + t.chars().take(200).collect() +} + +/// Does this machine meet a requirement? Probed on the fetch thread, once per poll and WSL user. Ok carries how it +/// was met, Err the exit code and the first 200 characters of the output, for the engine log. +fn probe(req: &str, wsl_user: &str) -> Result { + let wsl = crate::platform::tool("wsl"); match req { - "nvidia" => run_capture(Command::new(crate::platform::tool("nvidia-smi")).arg("-L"), Duration::from_secs(20)).0 == Some(0), - "wsl" => cfg!(windows) && run_capture(Command::new(crate::platform::tool("wsl")).arg("--status"), Duration::from_secs(30)).0 == Some(0), - "wsl-prover" => { - cfg!(windows) && run_capture(Command::new(crate::platform::tool("wsl")).args(["-d", DEFAULT_DISTRO, "--", "bash", "-lc", "command -v cargo && ls ~/.sp1 && ls ~/igneum-prove"]), Duration::from_secs(90)).0 == Some(0) + "nvidia" => { + let (code, out) = run_capture(Command::new(crate::platform::tool("nvidia-smi")).arg("-L"), Duration::from_secs(20)); + if code == Some(0) { Ok(short_out(&out)) } else { Err(format!("nvidia-smi -L exit {code:?}: {}", short_out(&out))) } } - _ => false, + "wsl" => { + if !cfg!(windows) { + return Err("not Windows".into()); + } + let (code, out) = run_capture(Command::new(&wsl).arg("--status"), Duration::from_secs(30)); + if code == Some(0) { Ok("wsl --status".into()) } else { Err(format!("wsl --status exit {code:?}: {}", short_out(&out))) } + } + "wsl-prover" => { + if !cfg!(windows) { + return Err("not Windows".into()); + } + // the configured user first (the job's wsl_user or IGNEUM_APP_WSL_USER), then the distro's default user + let mut tried = Vec::new(); + let mut users: Vec<&str> = Vec::new(); + if !wsl_user.is_empty() { + users.push(wsl_user); + } + users.push(""); + for u in users { + let mut c = Command::new(&wsl); + c.args(["-d", DEFAULT_DISTRO]); + if !u.is_empty() { + c.args(["-u", u]); + } + c.args(["--", "bash", "-lc", PROVER_PROBE]); + let (code, out) = run_capture(&mut c, Duration::from_secs(90)); + if code == Some(0) { + return Ok(format!("toolchain in {DEFAULT_DISTRO}{}: {}", if u.is_empty() { " (default user)".to_string() } else { format!(" as {u}") }, short_out(&out))); + } + tried.push(format!("{}: exit {code:?}: {}", if u.is_empty() { "default user".to_string() } else { format!("user {u}") }, short_out(&out))); + } + // the payload ships a Linux prover next to the app (wsl2\bin\igneum-prove-host): that meets it too, + // provided WSL answers at all + if let Some(dir) = std::env::current_exe().ok().and_then(|p| p.parent().map(|d| d.to_path_buf())) { + let shipped = dir.join("wsl2").join("bin").join("igneum-prove-host"); + if shipped.is_file() { + let (code, out) = run_capture(Command::new(&wsl).args(["-d", DEFAULT_DISTRO, "--", "true"]), Duration::from_secs(60)); + if code == Some(0) { + return Ok(format!("shipped prover {} and {DEFAULT_DISTRO} answers", shipped.display())); + } + tried.push(format!("shipped prover present but {DEFAULT_DISTRO} does not answer: exit {code:?}: {}", short_out(&out))); + } + } + Err(tried.join(" | ")) + } + _ => Err("unknown requirement".into()), } } diff --git a/packaging/ota/publish-jobs.sh b/packaging/ota/publish-jobs.sh index 248ed9fc9..9e1a4cee1 100755 --- a/packaging/ota/publish-jobs.sh +++ b/packaging/ota/publish-jobs.sh @@ -15,7 +15,8 @@ # packaging/ota/publish-jobs.sh add --kind update-now --target all # packaging/ota/publish-jobs.sh add --kind shard-benchmark --target 1ccfe586 [--zip ~/Desktop/igneum-prove-wsl2.zip] \ # [--fixtures "block-338-shard1 block-341-shards2 block-344-shards4"] [--cap-minutes 90] [--distro Ubuntu-24.04] [--wsl-user [user]] -# common: --target [--platform windows|mac|any] [--requires wsl-prover,nvidia] +# common: --target [--platform windows|mac|any] [--requires wsl-prover,nvidia | none] +# (shard-benchmark defaults to --requires wsl-prover; --requires none or "" publishes with no requirement) # [--id custom-id] [--title "..."] [--expires-hours 48] [--deploy] # packaging/ota/publish-jobs.sh list what is published (expired jobs marked) # packaging/ota/publish-jobs.sh remove [--deploy] @@ -35,7 +36,7 @@ TOKEN_FILE="$HOME/.config/igneum/dl-token" SIGNER="$ROOT/app/igneum-app/target/release/igneum-ota-sign" CMD="${1:-}"; [ $# -gt 0 ] && shift -KIND="" TARGET="" PLATFORM="" REQUIRES="" ID="" TITLE="" EXPIRES_H="48" DEPLOY=0 BASE="" DEST="" +KIND="" TARGET="" PLATFORM="" REQUIRES="" REQUIRES_SET=0 ID="" TITLE="" EXPIRES_H="48" DEPLOY=0 BASE="" DEST="" SCRIPT="" SHELL_KIND="" ELEVATED=0 STOP_MINERS=0 TIMEOUT_MIN="" FILE="" URL="" SHA="" SIZE="" DIR="" TO="" EXTRACT=0 EXTRACT_DIR="" FRESH=0 GLOBS=() COMMAND="" WHAT="" @@ -48,7 +49,7 @@ while [ $# -gt 0 ]; do --kind) KIND="$2"; shift 2 ;; --target) TARGET="$2"; shift 2 ;; --platform) PLATFORM="$2"; shift 2 ;; - --requires) REQUIRES="$2"; shift 2 ;; + --requires) REQUIRES="$2"; REQUIRES_SET=1; [ "$REQUIRES" = none ] && REQUIRES=""; shift 2 ;; --id) ID="$2"; shift 2 ;; --title) TITLE="$2"; shift 2 ;; --expires-hours) EXPIRES_H="$2"; shift 2 ;; @@ -183,7 +184,7 @@ print(json.dumps(d))' "$COMMAND" "${GLOBS[@]:-}")" [ -n "$ZIP" ] || ZIP="$HOME/Desktop/igneum-prove-wsl2.zip" read -r ZURL ZSHA ZSIZE < <(hosted_file "$ZIP") [ -n "$PLATFORM" ] || PLATFORM=windows - [ -n "$REQUIRES" ] || REQUIRES=wsl-prover + [ "$REQUIRES_SET" = 1 ] || REQUIRES=wsl-prover PARAMS="$(python3 -c 'import json,sys; a=sys.argv; d={"zip_url": a[1], "sha256": a[2], "size": int(a[3])} if a[4]: d["fixtures"]=a[4].split() if a[5]: d["cap_minutes"]=int(a[5])