Initial commit: jackpotminer Equihash 192,7 miner
GPU-accelerated Equihash 192,7 miner in Rust with three solver backends: - CPU: Wagner's algorithm, AVX2 packed slots (xenoncat-style) - OpenCL: full on-GPU solve (kernels/equihash.cl); runs on NVIDIA and AMD - CUDA: driver-API replay of miniZ's extracted fatbin (src/miniz/) Also includes a default-off pearlhash backend (src/pearl/, native CPU core + NVRTC int8-GEMM GPU kernels) and a WIP Ethash CUDA backend (src/ethash/). Reverse-engineering scratch (alpha-miner, pearl-dump/) and the active runtime config (mine.toml) are gitignored; mine.example.toml is the template. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+210
@@ -0,0 +1,210 @@
|
||||
//! Optional local control server (`--control-port`): a `127.0.0.1` JSON-line
|
||||
//! service that lets the GUI config tool retrieve and adjust a running miner's
|
||||
//! *live* controls on the fly — per-device enable + clock/power, and the CPU
|
||||
//! group size + per-row enable. Bound to localhost only (no auth); intended for
|
||||
//! a tool running on the same machine.
|
||||
//!
|
||||
//! Protocol: one JSON request object per line, one JSON reply per line.
|
||||
//! {"op":"get"} -> snapshot
|
||||
//! {"op":"set_device","index":0,"enabled":true,"power_w":250,
|
||||
//! "core_off":150,"mem_off":0} -> {"ok":true}
|
||||
//! {"op":"set_cpu_group_size","value":4} -> {"ok":true}
|
||||
//! {"op":"set_cpu_row","index":0,"enabled":true} -> {"ok":true}
|
||||
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::controls::Controls;
|
||||
use crate::cpu_groups::CpuMining;
|
||||
use crate::miner::Stats;
|
||||
|
||||
/// Serve until `running` is cleared. Non-blocking accept so shutdown is prompt.
|
||||
pub fn serve(port: u16, controls: Arc<Controls>, cpu_mining: Arc<CpuMining>, stats: Arc<Stats>, running: Arc<AtomicBool>) {
|
||||
let listener = match TcpListener::bind(("127.0.0.1", port)) {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
log::warn!("control server: cannot bind 127.0.0.1:{port}: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
if listener.set_nonblocking(true).is_err() {
|
||||
log::warn!("control server: set_nonblocking failed; control disabled");
|
||||
return;
|
||||
}
|
||||
log::info!("control server on 127.0.0.1:{port} (live retrieve/adjust)");
|
||||
while running.load(Ordering::Relaxed) {
|
||||
match listener.accept() {
|
||||
Ok((stream, _)) => handle(stream, &controls, &cpu_mining, &stats),
|
||||
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
std::thread::sleep(Duration::from_millis(150));
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("control accept error: {e}");
|
||||
std::thread::sleep(Duration::from_millis(150));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Serve one connection: read request lines (with a read timeout so a stalled
|
||||
/// client can't pin the thread past shutdown) and reply to each.
|
||||
fn handle(stream: TcpStream, controls: &Controls, cpu_mining: &CpuMining, stats: &Stats) {
|
||||
let _ = stream.set_nonblocking(false);
|
||||
let _ = stream.set_read_timeout(Some(Duration::from_secs(5)));
|
||||
let mut writer = match stream.try_clone() {
|
||||
Ok(s) => s,
|
||||
Err(_) => return,
|
||||
};
|
||||
let mut reader = BufReader::new(stream);
|
||||
let mut line = String::new();
|
||||
loop {
|
||||
line.clear();
|
||||
match reader.read_line(&mut line) {
|
||||
Ok(0) => return,
|
||||
Ok(_) => {}
|
||||
Err(_) => return,
|
||||
}
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let resp = process(line.trim(), controls, cpu_mining, stats);
|
||||
if writer.write_all(resp.to_string().as_bytes()).is_err()
|
||||
|| writer.write_all(b"\n").is_err()
|
||||
|| writer.flush().is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process(line: &str, controls: &Controls, cpu_mining: &CpuMining, stats: &Stats) -> Value {
|
||||
let req: Value = match serde_json::from_str(line) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return json!({"ok": false, "error": format!("bad json: {e}")}),
|
||||
};
|
||||
match req.get("op").and_then(Value::as_str).unwrap_or("") {
|
||||
"get" => snapshot(controls, cpu_mining, stats),
|
||||
"set_device" => set_device(&req, controls),
|
||||
"set_cpu_group_size" => {
|
||||
if let Some(n) = req.get("value").and_then(Value::as_u64) {
|
||||
cpu_mining.set_group_size(n as usize);
|
||||
}
|
||||
json!({"ok": true})
|
||||
}
|
||||
"set_cpu_row" => {
|
||||
let i = req.get("index").and_then(Value::as_u64).unwrap_or(u64::MAX) as usize;
|
||||
if let Some(e) = req.get("enabled").and_then(Value::as_bool) {
|
||||
let groups = cpu_mining.groups();
|
||||
if i < groups.len() && groups.group(i).enabled() != e {
|
||||
cpu_mining.toggle_group(i);
|
||||
}
|
||||
}
|
||||
json!({"ok": true})
|
||||
}
|
||||
other => json!({"ok": false, "error": format!("unknown op '{other}'")}),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_device(req: &Value, controls: &Controls) -> Value {
|
||||
let i = req.get("index").and_then(Value::as_u64).unwrap_or(u64::MAX) as usize;
|
||||
if i >= controls.device_count() {
|
||||
return json!({"ok": false, "error": "device index out of range"});
|
||||
}
|
||||
let d = controls.device(i);
|
||||
if let Some(e) = req.get("enabled").and_then(Value::as_bool) {
|
||||
d.set_enabled(e);
|
||||
}
|
||||
if let Some(p) = req.get("power_w").and_then(Value::as_u64) {
|
||||
d.set_power_w(p as u32);
|
||||
}
|
||||
if let Some(c) = req.get("core_off").and_then(Value::as_i64) {
|
||||
d.set_core_off(c as i32);
|
||||
}
|
||||
if let Some(m) = req.get("mem_off").and_then(Value::as_i64) {
|
||||
d.set_mem_off(m as i32);
|
||||
}
|
||||
json!({"ok": true})
|
||||
}
|
||||
|
||||
fn snapshot(controls: &Controls, cpu_mining: &CpuMining, stats: &Stats) -> Value {
|
||||
let cards = stats.cards();
|
||||
let devices: Vec<Value> = cards
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, c)| {
|
||||
let d = controls.device(i);
|
||||
json!({
|
||||
"index": i,
|
||||
"label": c.label,
|
||||
"name": c.name,
|
||||
"enabled": d.enabled(),
|
||||
"power_w": d.power_w(),
|
||||
"core_off": d.core_off(),
|
||||
"mem_off": d.mem_off(),
|
||||
"watts": c.power_mw as f64 / 1000.0,
|
||||
"temp_c": c.temp_c,
|
||||
"shares": c.shares,
|
||||
"solutions": c.solutions,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let groups = cpu_mining.groups();
|
||||
let rows: Vec<Value> = groups
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, g)| json!({"index": i, "label": g.label(), "enabled": g.enabled(), "shares": g.shares()}))
|
||||
.collect();
|
||||
json!({
|
||||
"ok": true,
|
||||
"unlocked": controls.unlocked(),
|
||||
"devices": devices,
|
||||
"cpu": {"group_size": cpu_mining.group_size(), "rows": rows},
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::miner::Stats;
|
||||
|
||||
#[test]
|
||||
fn protocol_get_and_set() {
|
||||
let controls = Controls::new(1, 0, 0, 0, 0, true);
|
||||
let cpu = CpuMining::new((0..8).collect(), 4, false); // sizes [1,2,4,8]
|
||||
let stats = Stats::for_test(vec!["GPU 0".into()]);
|
||||
|
||||
// get: one device, cpu rows reflect the grouping (8 cores / 4 = 2 rows).
|
||||
let v = process("{\"op\":\"get\"}", &controls, &cpu, &stats);
|
||||
assert_eq!(v["ok"], true);
|
||||
assert_eq!(v["devices"].as_array().unwrap().len(), 1);
|
||||
assert_eq!(v["cpu"]["group_size"], 4);
|
||||
assert_eq!(v["cpu"]["rows"].as_array().unwrap().len(), 2);
|
||||
|
||||
// set device 0 disabled.
|
||||
assert!(controls.device(0).enabled());
|
||||
process("{\"op\":\"set_device\",\"index\":0,\"enabled\":false,\"power_w\":250}", &controls, &cpu, &stats);
|
||||
assert!(!controls.device(0).enabled());
|
||||
|
||||
// out-of-range device index is rejected.
|
||||
let e = process("{\"op\":\"set_device\",\"index\":9}", &controls, &cpu, &stats);
|
||||
assert_eq!(e["ok"], false);
|
||||
|
||||
// set CPU group size to the nearest available (2).
|
||||
process("{\"op\":\"set_cpu_group_size\",\"value\":2}", &controls, &cpu, &stats);
|
||||
assert_eq!(cpu.group_size(), 2);
|
||||
|
||||
// toggle a CPU row on.
|
||||
assert!(!cpu.groups().group(0).enabled());
|
||||
process("{\"op\":\"set_cpu_row\",\"index\":0,\"enabled\":true}", &controls, &cpu, &stats);
|
||||
assert!(cpu.groups().group(0).enabled());
|
||||
|
||||
// garbage and unknown ops are reported, not panicked.
|
||||
assert_eq!(process("not json", &controls, &cpu, &stats)["ok"], false);
|
||||
assert_eq!(process("{\"op\":\"nope\"}", &controls, &cpu, &stats)["ok"], false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user