Skip to content
Open
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
78 changes: 56 additions & 22 deletions asic-rs-firmwares/antminer/src/backends/v2020/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,6 @@ impl MinerMode {
}
}

fn miner_conf_mode_matches(conf: &Value, mode: MinerMode) -> bool {
let expected = mode.to_string();
["miner-mode", "bitmain-work-mode"]
.iter()
.filter_map(|key| conf.get(key))
.any(|value| {
value
.as_str()
.map(|mode| mode == expected)
.or_else(|| value.as_i64().map(|mode| mode.to_string() == expected))
.unwrap_or(false)
})
}

fn miner_mode_config_key(miner_conf: &Value) -> Option<&'static str> {
if miner_conf.get("miner-mode").is_some() {
Some("miner-mode")
Expand Down Expand Up @@ -150,9 +136,11 @@ fn miner_conf_with_pools(miner_conf: &Value, pools: Vec<Value>) -> Value {
}

fn miner_conf_with_miner_mode(miner_conf: &Value, mode: MinerMode) -> Option<Value> {
let mode_key = miner_mode_config_key(miner_conf)?;
miner_mode_config_key(miner_conf)?;
let mut payload = browser_miner_conf_payload(miner_conf);
payload.insert(mode_key.to_string(), Value::from(mode.as_web_value()));
let mode = Value::from(mode.as_web_value());
payload.insert("miner-mode".to_string(), mode.clone());
payload.insert("bitmain-work-mode".to_string(), mode);
Some(Value::Object(payload))
}

Expand Down Expand Up @@ -1043,9 +1031,15 @@ impl Pause for AntMinerV2020 {
return Ok(false);
};

self.web.set_miner_conf(miner_conf).await?;
let post = self.web.get_miner_conf().await?;
Ok(miner_conf_mode_matches(&post, MinerMode::Sleep))
let response = self.web.set_miner_conf(miner_conf).await?;
if response.get("stats").and_then(Value::as_str) != Some("success") {
return Ok(false);
}

// set_miner_conf.cgi writes the config before starting a miner
// reload/restart in the background; an immediate follow-up read can
// race the device becoming temporarily unavailable.
Ok(true)
}
}

Expand All @@ -1061,9 +1055,12 @@ impl Resume for AntMinerV2020 {
return Ok(false);
};

self.web.set_miner_conf(miner_conf).await?;
let post = self.web.get_miner_conf().await?;
Ok(miner_conf_mode_matches(&post, MinerMode::Normal))
let response = self.web.set_miner_conf(miner_conf).await?;
if response.get("stats").and_then(Value::as_str) != Some("success") {
return Ok(false);
}

Ok(true)
}
}

Expand Down Expand Up @@ -1253,6 +1250,43 @@ mod tests {
AM_DEVS, AM_POOLS, AM_STATS, AM_SUMMARY, AM_SYSTEM_INFO, AM_VERSION,
};

#[test]
fn set_miner_conf_payload_matches_cgi_contract_for_pause_resume() {
let pre = json!({
"pools": [
{"url": "stratum+tcp://pool1.example:3333", "user": "worker.1", "pass": "x"},
{"url": "stratum+tcp://pool2.example:3333", "user": "worker.2", "pass": "x"},
{"url": "stratum+tcp://pool3.example:3333", "user": "worker.3", "pass": "x"}
],
"bitmain-fan-ctrl": true,
"bitmain-fan-pwm": "70",
"bitmain-work-mode": "0",
"bitmain-freq-level": "100",
"api-listen": true
});

assert_eq!(
miner_conf_with_miner_mode(&pre, MinerMode::Sleep).unwrap(),
json!({
"pools": [
{"url": "stratum+tcp://pool1.example:3333", "user": "worker.1", "pass": "x"},
{"url": "stratum+tcp://pool2.example:3333", "user": "worker.2", "pass": "x"},
{"url": "stratum+tcp://pool3.example:3333", "user": "worker.3", "pass": "x"}
],
"bitmain-fan-ctrl": true,
"bitmain-fan-pwm": "70",
"freq-level": "100",
"miner-mode": 1,
"bitmain-work-mode": 1
})
);

assert_eq!(
miner_conf_with_miner_mode(&pre, MinerMode::Normal).unwrap()["miner-mode"],
json!(0)
);
}

#[tokio::test]
async fn test_antminer() {
let miner = AntMinerV2020::new(IpAddr::from([127, 0, 0, 1]), AntMinerModel::S19Pro);
Expand Down
153 changes: 121 additions & 32 deletions asic-rs-firmwares/antminer/src/backends/v2023_07/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ impl Display for MinerMode {
}
}

impl MinerMode {
fn as_web_value(&self) -> u8 {
match self {
MinerMode::Sleep => 1,
MinerMode::Low => 3,
MinerMode::Normal => 0,
MinerMode::High => 2,
}
}
}

fn miner_mode_config_key(miner_conf: &Value) -> Option<&'static str> {
if miner_conf.get("miner-mode").is_some() {
Some("miner-mode")
Expand All @@ -76,6 +87,56 @@ fn miner_mode_config_key(miner_conf: &Value) -> Option<&'static str> {
}
}

fn browser_miner_conf_payload(miner_conf: &Value) -> serde_json::Map<String, Value> {
let mut payload = serde_json::Map::new();
payload.insert(
"bitmain-fan-ctrl".to_string(),
miner_conf
.get("bitmain-fan-ctrl")
.cloned()
.unwrap_or(Value::Bool(false)),
);
payload.insert(
"bitmain-fan-pwm".to_string(),
miner_conf
.get("bitmain-fan-pwm")
.cloned()
.unwrap_or(Value::String("100".to_string())),
);

if let Some(mode_key) = miner_mode_config_key(miner_conf)
&& let Some(mode) = miner_conf.get(mode_key)
{
payload.insert(mode_key.to_string(), mode.clone());
}
payload.insert(
"freq-level".to_string(),
miner_conf
.get("freq-level")
.or_else(|| miner_conf.get("bitmain-freq-level"))
.cloned()
.unwrap_or(Value::String("100".to_string())),
);
payload.insert(
"pools".to_string(),
miner_conf
.get("pools")
.cloned()
.unwrap_or_else(|| Value::Array(Vec::new())),
);

payload
}

fn miner_conf_with_miner_mode(miner_conf: &Value, mode: MinerMode) -> Option<Value> {
miner_mode_config_key(miner_conf)?;
let mut payload = browser_miner_conf_payload(miner_conf);
let mode = Value::from(mode.as_web_value());
payload.insert("miner-mode".to_string(), mode.clone());
payload.insert("bitmain-work-mode".to_string(), mode);
Some(Value::Object(payload))
}

fn miner_mode_from_value(mode: &Value) -> Option<MiningMode> {
let mode = mode
.as_str()
Expand Down Expand Up @@ -635,6 +696,7 @@ impl GetIsMining for AntMinerV202307 {
&& status_lower != "idle"
&& status_lower != "sleep"
&& status_lower != "1"
&& status_lower != "5"
})
.unwrap_or(true)
}
Expand Down Expand Up @@ -944,24 +1006,16 @@ impl Pause for AntMinerV202307 {
#[allow(unused_variables)]
async fn pause(&self, at_time: Option<Duration>) -> anyhow::Result<bool> {
let pre = self.web.get_miner_conf().await?;
let Some(miner_conf) = miner_conf_with_miner_mode(&pre, MinerMode::Sleep) else {
return Ok(false);
};

if pre.get("miner-mode").is_some() {
return Ok(self
.web
.set_miner_conf(json!({"miner-mode": MinerMode::Sleep.to_string()}))
.await
.is_ok());
}

if pre.get("bitmain-work-mode").is_some() {
return Ok(self
.web
.set_miner_conf(json!({"bitmain-work-mode": MinerMode::Sleep.to_string()}))
.await
.is_ok());
let response = self.web.set_miner_conf(miner_conf).await?;
if response.get("stats").and_then(Value::as_str) != Some("success") {
return Ok(false);
}

Ok(false)
Ok(true)
}
}

Expand All @@ -973,24 +1027,19 @@ impl Resume for AntMinerV202307 {
#[allow(unused_variables)]
async fn resume(&self, at_time: Option<Duration>) -> anyhow::Result<bool> {
let pre = self.web.get_miner_conf().await?;
let Some(miner_conf) = miner_conf_with_miner_mode(&pre, MinerMode::Normal) else {
return Ok(false);
};

if pre.get("miner-mode").is_some() {
return Ok(self
.web
.set_miner_conf(json!({"miner-mode": MinerMode::Normal.to_string()}))
.await
.is_ok());
}

if pre.get("bitmain-work-mode").is_some() {
return Ok(self
.web
.set_miner_conf(json!({"bitmain-work-mode": MinerMode::Normal.to_string()}))
.await
.is_ok());
let response = self.web.set_miner_conf(miner_conf).await?;
if response.get("stats").and_then(Value::as_str) != Some("success") {
return Ok(false);
}

Ok(false)
// set_miner_conf.cgi writes the config before starting a miner
// reload/restart in the background; an immediate follow-up read can
// race the device becoming temporarily unavailable.
Ok(true)
}
}

Expand Down Expand Up @@ -1104,12 +1153,15 @@ impl SupportsTuningConfig for AntMinerV202307 {
};

let pre = self.web.get_miner_conf().await?;
let Some(mode_key) = miner_mode_config_key(&pre) else {
if miner_mode_config_key(&pre).is_none() {
anyhow::bail!("No Antminer mining mode field found in miner config")
};

self.web
.set_miner_conf(json!({ mode_key: mode.to_string() }))
.set_miner_conf(json!({
"miner-mode": mode.to_string(),
"bitmain-work-mode": mode.to_string(),
}))
.await?;
Ok(true)
}
Expand Down Expand Up @@ -1187,6 +1239,43 @@ mod tests {
AM_DEVS, AM_POOLS, AM_STATS, AM_SUMMARY, AM_SYSTEM_INFO, AM_VERSION,
};

#[test]
fn set_miner_conf_payload_matches_cgi_contract_for_pause_resume() {
let pre = json!({
"pools": [
{"url": "stratum+tcp://pool1.example:3333", "user": "worker.1", "pass": "x"},
{"url": "stratum+tcp://pool2.example:3333", "user": "worker.2", "pass": "x"},
{"url": "stratum+tcp://pool3.example:3333", "user": "worker.3", "pass": "x"}
],
"bitmain-fan-ctrl": true,
"bitmain-fan-pwm": "70",
"bitmain-work-mode": "0",
"bitmain-freq-level": "100",
"api-listen": true
});

assert_eq!(
miner_conf_with_miner_mode(&pre, MinerMode::Sleep).unwrap(),
json!({
"pools": [
{"url": "stratum+tcp://pool1.example:3333", "user": "worker.1", "pass": "x"},
{"url": "stratum+tcp://pool2.example:3333", "user": "worker.2", "pass": "x"},
{"url": "stratum+tcp://pool3.example:3333", "user": "worker.3", "pass": "x"}
],
"bitmain-fan-ctrl": true,
"bitmain-fan-pwm": "70",
"freq-level": "100",
"miner-mode": 1,
"bitmain-work-mode": 1
})
);

assert_eq!(
miner_conf_with_miner_mode(&pre, MinerMode::Normal).unwrap()["miner-mode"],
json!(0)
);
}

#[tokio::test]
async fn test_antminer() {
let miner = AntMinerV202307::new(IpAddr::from([127, 0, 0, 1]), AntMinerModel::S19Pro);
Expand Down
Loading