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
19 changes: 19 additions & 0 deletions asic-rs-core/src/traits/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ pub trait HasMinerControl:
+ SetHashboardsEnabled
+ SetTuningPercent
+ Restart
+ RestartMining
+ Resume
+ Pause
+ ChangePassword
Expand All @@ -101,6 +102,7 @@ impl<
+ SetHashboardsEnabled
+ SetTuningPercent
+ Restart
+ RestartMining
+ Resume
+ Pause
+ ChangePassword
Expand Down Expand Up @@ -1056,6 +1058,23 @@ pub trait Restart {
fn supports_restart(&self) -> bool;
}

/// Restart the mining program without rebooting the operating system.
///
/// This is distinct from [`Restart`], which reboots the whole device. A
/// successful request only confirms that the firmware accepted the restart;
/// hashing stops while the mining program comes back up.
#[async_trait]
pub trait RestartMining {
async fn restart_mining(&self) -> anyhow::Result<bool> {
anyhow::bail!("Restarting the mining program is not supported on this platform");
}
/// Defaults to `false`; backends that can restart the mining program
/// without a reboot override this.
fn supports_restart_mining(&self) -> bool {
false
}
}

#[async_trait]
pub trait Pause {
#[allow(unused_variables)]
Expand Down
2 changes: 2 additions & 0 deletions asic-rs-firmwares/antminer/src/backends/v2018/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,8 @@ impl FactoryReset for AntMinerV2018 {
}
}

impl RestartMining for AntMinerV2018 {}

impl RestoreStockOs for AntMinerV2018 {}

#[async_trait]
Expand Down
2 changes: 2 additions & 0 deletions asic-rs-firmwares/antminer/src/backends/v2020/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,8 @@ impl FactoryReset for AntMinerV2020 {
}
}

impl RestartMining for AntMinerV2020 {}

impl RestoreStockOs for AntMinerV2020 {}

#[async_trait]
Expand Down
2 changes: 2 additions & 0 deletions asic-rs-firmwares/antminer/src/backends/v2023_07/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,8 @@ impl FactoryReset for AntMinerV202307 {
}
}

impl RestartMining for AntMinerV202307 {}

impl RestoreStockOs for AntMinerV202307 {}

#[async_trait]
Expand Down
131 changes: 131 additions & 0 deletions asic-rs-firmwares/auradine/src/backends/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,22 @@ impl FactoryReset for AuradineV1 {
}
}

#[async_trait]
impl RestartMining for AuradineV1 {
async fn restart_mining(&self) -> anyhow::Result<bool> {
// `restart` with no parameter reboots the miner; the `gcminer`
// parameter restarts only the mining program.
self.web
.send_command("restart", true, Some(json!("gcminer")), Method::POST)
.await?;
Ok(true)
}

fn supports_restart_mining(&self) -> bool {
true
}
}

impl RestoreStockOs for AuradineV1 {}

#[async_trait]
Expand Down Expand Up @@ -1471,6 +1487,121 @@ mod tests {
},
};

struct MockJsonSequenceServer {
port: u16,
task: tokio::task::JoinHandle<anyhow::Result<Vec<Value>>>,
}

/// Answer consecutive POST requests with the given JSON bodies and return
/// the JSON bodies that were received.
async fn mock_json_sequence_server(
responses: Vec<Value>,
) -> anyhow::Result<MockJsonSequenceServer> {
use tokio::io::{AsyncReadExt, AsyncWriteExt};

let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
let port = listener.local_addr()?.port();
let task = tokio::spawn(async move {
let mut requests = Vec::with_capacity(responses.len());
for response in responses {
let (mut socket, _) =
tokio::time::timeout(Duration::from_secs(2), listener.accept())
.await
.context("timed out waiting for HTTP request")??;

let mut request = Vec::new();
let mut chunk = [0_u8; 1024];
let body = loop {
let text = String::from_utf8_lossy(&request).into_owned();
if let Some((headers, body)) = text.split_once("\r\n\r\n") {
let content_length = headers
.lines()
.filter_map(|line| line.split_once(':'))
.find_map(|(name, value)| {
name.eq_ignore_ascii_case("content-length")
.then(|| value.trim().parse::<usize>().ok())
.flatten()
})
.unwrap_or_default();
if body.len() >= content_length {
break body.to_string();
}
}
let bytes_read = socket.read(&mut chunk).await?;
if bytes_read == 0 {
anyhow::bail!("connection closed before the request was complete");
}
request.extend_from_slice(&chunk[..bytes_read]);
};
requests.push(serde_json::from_str(&body)?);

let body = response.to_string();
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
body
);
socket.write_all(response.as_bytes()).await?;
}
Ok(requests)
});

Ok(MockJsonSequenceServer { port, task })
}

fn token_response() -> Value {
json!({
"STATUS": [{ "STATUS": "S", "Msg": "Token" }],
"Token": [{ "Token": "test-token" }]
})
}

fn miner_with_mock_web(port: u16) -> AuradineV1 {
let ip = IpAddr::from([127, 0, 0, 1]);
let mut miner = AuradineV1::new(ip, AuradineModel::AT1500);
miner.web = AuradineWebAPI::new_with_port(ip, port, AuradineV1::default_auth());
miner
}

#[tokio::test]
async fn restart_mining_restarts_gcminer_instead_of_rebooting() -> anyhow::Result<()> {
let MockJsonSequenceServer { port, task } = mock_json_sequence_server(vec![
token_response(),
json!({ "STATUS": [{ "STATUS": "S", "Msg": "Restarting gcminer" }] }),
])
.await?;
let miner = miner_with_mock_web(port);

assert!(miner.supports_restart_mining());
assert!(miner.restart_mining().await?);

let requests = task.await??;
assert_eq!(requests[0]["command"], "token");
assert_eq!(
requests[1],
json!({ "command": "restart", "parameter": "gcminer" })
);

Ok(())
}

#[tokio::test]
async fn restart_mining_surfaces_firmware_rejection() -> anyhow::Result<()> {
let MockJsonSequenceServer { port, task } = mock_json_sequence_server(vec![
token_response(),
json!({ "STATUS": [{ "STATUS": "E", "Msg": "gcminer restart refused" }] }),
])
.await?;
let miner = miner_with_mock_web(port);

let error = miner.restart_mining().await.unwrap_err();

task.await??;
assert!(error.to_string().contains("gcminer restart refused"));

Ok(())
}

#[tokio::test]
async fn parse_data_test_auradine_at1500() -> anyhow::Result<()> {
let miner = AuradineV1::new(IpAddr::from([127, 0, 0, 1]), AuradineModel::AT1500);
Expand Down
7 changes: 7 additions & 0 deletions asic-rs-firmwares/auradine/src/backends/v1/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ impl AuradineWebAPI {
}
}

#[cfg(test)]
pub(super) fn new_with_port(ip: IpAddr, port: u16, auth: MinerAuth) -> Self {
let mut api = Self::new(ip, auth);
api.port = port;
api
}

pub fn set_auth(&mut self, auth: MinerAuth) {
self.auth = auth;
*self.token.get_mut() = None;
Expand Down
1 change: 1 addition & 0 deletions asic-rs-firmwares/avalonminer/src/backends/avalon_a/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ impl FactoryReset for AvalonAMiner {
}
}

impl RestartMining for AvalonAMiner {}
impl RestoreStockOs for AvalonAMiner {}

#[async_trait]
Expand Down
1 change: 1 addition & 0 deletions asic-rs-firmwares/avalonminer/src/backends/avalon_q/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ impl FactoryReset for AvalonQMiner {
}
}

impl RestartMining for AvalonQMiner {}
impl RestoreStockOs for AvalonQMiner {}

#[async_trait]
Expand Down
1 change: 1 addition & 0 deletions asic-rs-firmwares/bitaxe/src/backends/v2_0_0/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ impl FactoryReset for Bitaxe200 {
}
}

impl RestartMining for Bitaxe200 {}
impl RestoreStockOs for Bitaxe200 {}

#[async_trait]
Expand Down
1 change: 1 addition & 0 deletions asic-rs-firmwares/bitaxe/src/backends/v2_14_0/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ impl FactoryReset for Bitaxe2140 {
}
}

impl RestartMining for Bitaxe2140 {}
impl RestoreStockOs for Bitaxe2140 {}

#[async_trait]
Expand Down
1 change: 1 addition & 0 deletions asic-rs-firmwares/bitaxe/src/backends/v2_9_0/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ impl FactoryReset for Bitaxe290 {
}
}

impl RestartMining for Bitaxe290 {}
impl RestoreStockOs for Bitaxe290 {}

#[async_trait]
Expand Down
Loading
Loading