stratum_apps/rpc/
mini_rpc_client.rs

1// TODO
2//  - manage id in RpcResult messages
3use base64::Engine;
4use hex::decode;
5use http_body_util::{BodyExt, Full};
6use hyper::{
7    body::Bytes,
8    header::{AUTHORIZATION, CONTENT_TYPE},
9    Request,
10};
11use hyper_util::{
12    client::legacy::{connect::HttpConnector, Client},
13    rt::TokioExecutor,
14};
15use serde::{Deserialize, Serialize};
16use serde_json::json;
17use stratum_core::bitcoin::{consensus::encode::deserialize as consensus_decode, Transaction};
18
19use super::BlockHash;
20
21#[derive(Clone, Debug)]
22pub struct MiniRpcClient {
23    client: Client<HttpConnector, Full<Bytes>>,
24    url: hyper::Uri,
25    auth: Auth,
26}
27
28impl MiniRpcClient {
29    pub fn new(url: hyper::Uri, auth: Auth) -> MiniRpcClient {
30        let client: Client<_, Full<Bytes>> = Client::builder(TokioExecutor::new()).build_http();
31        MiniRpcClient { client, url, auth }
32    }
33
34    pub async fn get_raw_transaction(
35        &self,
36        txid: &String,
37        block_hash: Option<&BlockHash>,
38    ) -> Result<Transaction, RpcError> {
39        let response = match block_hash {
40            Some(hash) => {
41                self.send_json_rpc_request("getrawtransaction", json!([txid, false, hash]))
42            }
43            None => self.send_json_rpc_request("getrawtransaction", json!([txid, false])),
44        }
45        .await;
46        match response {
47            Ok(result_hex) => {
48                let result_deserialized: JsonRpcResult<String> = serde_json::from_str(&result_hex)
49                    .map_err(|e| {
50                        RpcError::Deserialization(e.to_string()) // TODO manage message ids
51                    })?;
52                let transaction_hex: String = result_deserialized
53                    .result
54                    .ok_or_else(|| RpcError::Other("Result not found".to_string()))?;
55                let transaction_bytes = decode(transaction_hex).expect("Decoding failed");
56                Ok(consensus_decode(&transaction_bytes).expect("Deserialization failed"))
57            }
58            Err(error) => Err(error),
59        }
60    }
61
62    pub async fn get_raw_mempool(&self) -> Result<Vec<String>, RpcError> {
63        let response = self.send_json_rpc_request("getrawmempool", json!([])).await;
64        match response {
65            Ok(result_hex) => {
66                let result_deserialized: JsonRpcResult<Vec<String>> =
67                    serde_json::from_str(&result_hex).map_err(|e| {
68                        RpcError::Deserialization(e.to_string()) // TODO manage message ids
69                    })?;
70                let mempool: Vec<String> = result_deserialized
71                    .result
72                    .ok_or_else(|| RpcError::Other("Result not found".to_string()))?;
73                Ok(mempool)
74            }
75            Err(error) => Err(error),
76        }
77    }
78
79    pub async fn submit_block(&self, block_hex: String) -> Result<(), RpcError> {
80        let response = self
81            .send_json_rpc_request("submitblock", json!([block_hex]))
82            .await;
83
84        match response {
85            Ok(_) => Ok(()),
86            Err(error) => Err(error),
87        }
88    }
89
90    /// Checks the health of the RPC connection by sending a request to the blockchain info
91    /// endpoint
92    pub async fn health(&self) -> Result<(), RpcError> {
93        let response = self
94            .send_json_rpc_request("getblockchaininfo", json!([]))
95            .await;
96        match response {
97            Ok(_) => Ok(()),
98            Err(error) => Err(error),
99        }
100    }
101
102    async fn send_json_rpc_request(
103        &self,
104        method: &str,
105        params: serde_json::Value,
106    ) -> Result<String, RpcError> {
107        let client = &self.client;
108        let (username, password) = self.auth.clone().get_user_pass();
109        let request = JsonRpcRequest {
110            jsonrpc: "2.0".to_string(),
111            method: method.to_string(),
112            params,
113            id: 1, //TODO manage message ids
114        };
115
116        let request_body = match serde_json::to_string(&request) {
117            Ok(body) => body,
118            Err(e) => return Err(RpcError::Serialization(e.to_string())),
119        };
120
121        let req = Request::builder()
122            .method("POST")
123            .uri(self.url.clone())
124            .header(CONTENT_TYPE, "application/json")
125            .header(
126                AUTHORIZATION,
127                format!(
128                    "Basic {}",
129                    base64::engine::general_purpose::STANDARD
130                        .encode(format!("{username}:{password}"))
131                ),
132            )
133            .body(Full::<Bytes>::from(request_body))
134            .map_err(|e| RpcError::Http(e.to_string()))?;
135
136        let response = client
137            .request(req)
138            .await
139            .map_err(|e| RpcError::Http(e.to_string()))?;
140
141        let status = response.status();
142        let body = response
143            .into_body()
144            .collect()
145            .await
146            .map_err(|e| RpcError::Http(e.to_string()))?
147            .to_bytes()
148            .to_vec();
149
150        if status.is_success() {
151            String::from_utf8(body).map_err(|e| {
152                RpcError::Deserialization(e.to_string()) // TODO manage message ids
153            })
154        } else {
155            let error_result: Result<JsonRpcResult<_>, _> = serde_json::from_slice(&body);
156            match error_result {
157                Ok(error_response) => Err(error_response.into()),
158                Err(e) => Err(RpcError::Deserialization(e.to_string())),
159            }
160        }
161    }
162}
163
164#[derive(Clone, Debug)]
165pub struct Auth {
166    username: String,
167    password: String,
168}
169
170impl Auth {
171    pub fn get_user_pass(self) -> (String, String) {
172        (self.username, self.password)
173    }
174    pub fn new(username: String, password: String) -> Auth {
175        Auth { username, password }
176    }
177}
178
179#[derive(Debug, Serialize)]
180struct JsonRpcRequest {
181    jsonrpc: String,
182    method: String,
183    params: serde_json::Value,
184    id: u64,
185}
186
187#[derive(Debug, Deserialize)]
188pub struct JsonRpcResult<T> {
189    result: Option<T>,
190    pub error: Option<JsonRpcError>,
191    pub id: u64,
192}
193
194#[derive(Debug, Deserialize, Clone)]
195pub struct JsonRpcError {
196    pub code: i32,
197    pub message: String,
198}
199
200#[derive(Debug, Deserialize)]
201pub enum RpcError {
202    // TODO this type is slightly incorrect, as the JsonRpcError evaluates a generic that is meant
203    // for the result field of JsonRpcResult struct. This should be corrected
204    JsonRpc(JsonRpcResult<JsonRpcError>),
205    Deserialization(String),
206    Serialization(String),
207    Http(String),
208    Other(String),
209}
210
211impl From<JsonRpcResult<JsonRpcError>> for RpcError {
212    fn from(error: JsonRpcResult<JsonRpcError>) -> Self {
213        Self::JsonRpc(error)
214    }
215}