asic_rs/miners/backends/btminer/v3/
rpc.rs

1use crate::miners::api::RPCAPIClient;
2use crate::miners::api::rpc::status::RPCCommandStatus;
3use crate::miners::api::{APIClient, rpc::errors::RPCError};
4use crate::miners::commands::MinerCommand;
5use anyhow::{Result, anyhow};
6use async_trait::async_trait;
7use serde_json::{Value, json};
8use std::net::IpAddr;
9use tokio::io::{AsyncReadExt, AsyncWriteExt};
10
11#[derive(Debug)]
12pub struct BTMinerRPCAPI {
13    ip: IpAddr,
14    port: u16,
15}
16
17#[async_trait]
18impl APIClient for BTMinerRPCAPI {
19    async fn get_api_result(&self, command: &MinerCommand) -> Result<Value> {
20        match command {
21            MinerCommand::RPC {
22                command,
23                parameters,
24            } => self
25                .send_command(command, false, parameters.clone())
26                .await
27                .map_err(|e| anyhow!(e.to_string())),
28            _ => Err(anyhow!("Cannot send non RPC command to RPC API")),
29        }
30    }
31}
32
33impl RPCCommandStatus {
34    fn from_btminer_v3(response: &str) -> Result<Self, RPCError> {
35        let value: serde_json::Value = serde_json::from_str(response)?;
36
37        match value["code"].as_i64() {
38            None => {
39                let message = value["msg"].as_str();
40
41                Err(RPCError::StatusCheckFailed(
42                    message
43                        .unwrap_or("Unknown error when looking for status code")
44                        .to_owned(),
45                ))
46            }
47            Some(code) => match code {
48                0 => Ok(Self::Success),
49                _ => {
50                    let message = value["msg"].as_str();
51                    Err(RPCError::StatusCheckFailed(
52                        message
53                            .unwrap_or("Unknown error when parsing status")
54                            .to_owned(),
55                    ))
56                }
57            },
58        }
59    }
60}
61
62#[async_trait]
63impl RPCAPIClient for BTMinerRPCAPI {
64    async fn send_command(
65        &self,
66        command: &str,
67        _privileged: bool,
68        parameters: Option<Value>,
69    ) -> Result<Value> {
70        let mut stream = tokio::net::TcpStream::connect((self.ip, self.port))
71            .await
72            .map_err(|_| RPCError::ConnectionFailed)?;
73
74        let request = match parameters {
75            Some(Value::Object(mut obj)) => {
76                // Use the existing object as the base
77                obj.insert("cmd".to_string(), json!(command));
78                Value::Object(obj)
79            }
80            Some(other) => {
81                // Wrap non-objects into the "param" key
82                json!({ "cmd": command, "param": other })
83            }
84            None => {
85                // No parameters at all
86                json!({ "cmd": command })
87            }
88        };
89        let json_str = request.to_string();
90        let json_bytes = json_str.as_bytes();
91        let length = json_bytes.len() as u32;
92
93        stream.write_all(&length.to_le_bytes()).await?;
94        stream.write_all(json_bytes).await?;
95
96        let mut len_buf = [0u8; 4];
97        stream.read_exact(&mut len_buf).await?;
98        let response_len = u32::from_le_bytes(len_buf) as usize;
99
100        let mut resp_buf = vec![0u8; response_len];
101        stream.read_exact(&mut resp_buf).await?;
102
103        let response_str = String::from_utf8_lossy(&resp_buf).into_owned();
104
105        self.parse_rpc_result(&response_str)
106    }
107}
108
109impl BTMinerRPCAPI {
110    pub fn new(ip: IpAddr, port: Option<u16>) -> Self {
111        Self {
112            ip,
113            port: port.unwrap_or(4433),
114        }
115    }
116
117    fn parse_rpc_result(&self, response: &str) -> Result<Value> {
118        let status = RPCCommandStatus::from_btminer_v3(response)?;
119        match status.into_result() {
120            Ok(_) => Ok(serde_json::from_str(response)?),
121            Err(e) => Err(e)?,
122        }
123    }
124}