darkfi_mmproxy/
monerod.rs
1use std::{collections::HashMap, str::FromStr};
20
21use darkfi::{
22 rpc::{
23 jsonrpc::{JsonRequest, JsonResponse},
24 util::JsonValue,
25 },
26 Error, Result,
27};
28use log::{debug, error, info};
29use monero::blockdata::transaction::{ExtraField, RawExtraField, SubField::MergeMining};
30
31use super::MiningProxy;
32
33pub(crate) enum MonerodRequest {
35 Get(String),
36 Post(JsonRequest),
37}
38
39impl MiningProxy {
40 pub(crate) async fn monero_request(&self, req: MonerodRequest) -> Result<JsonValue> {
42 let mut rep = match req {
43 MonerodRequest::Get(method) => {
44 let endpoint = format!("{}{}", self.monero_rpc, method);
45
46 match surf::get(&endpoint).await {
47 Ok(v) => v,
48 Err(e) => {
49 let e = format!("Failed sending monerod GET request: {}", e);
50 error!(target: "monerod::monero_request", "{}", e);
51 return Err(Error::Custom(e))
52 }
53 }
54 }
55 MonerodRequest::Post(data) => {
56 let endpoint = format!("{}json_rpc", self.monero_rpc);
57 let client = surf::Client::new();
58
59 match client
60 .get(endpoint)
61 .header("Content-Type", "application/json")
62 .body(data.stringify().unwrap())
63 .send()
64 .await
65 {
66 Ok(v) => v,
67 Err(e) => {
68 let e = format!("Failed sending monerod POST request: {}", e);
69 error!(target: "monerod::monero_request", "{}", e);
70 return Err(Error::Custom(e))
71 }
72 }
73 }
74 };
75
76 let json_rep: JsonValue = match rep.body_string().await {
77 Ok(v) => match v.parse() {
78 Ok(v) => v,
79 Err(e) => {
80 let e = format!("Failed parsing JSON string from monerod response: {}", e);
81 error!(target: "monerod::monero_request", "{}", e);
82 return Err(Error::Custom(e))
83 }
84 },
85 Err(e) => {
86 let e = format!("Failed parsing body string from monerod response: {}", e);
87 error!(target: "monerod::monero_request", "{}", e);
88 return Err(Error::Custom(e))
89 }
90 };
91
92 Ok(json_rep)
93 }
94
95 pub async fn monerod_get_height(&self) -> Result<JsonValue> {
97 info!(target: "monerod::getheight", "Proxying /getheight request");
98 let rep = self.monero_request(MonerodRequest::Get("getheight".to_string())).await?;
99 Ok(rep)
100 }
101
102 pub async fn monerod_get_info(&self) -> Result<JsonValue> {
104 info!(target: "monerod::getinfo", "Proxying /getinfo request");
105 let rep = self.monero_request(MonerodRequest::Get("getinfo".to_string())).await?;
106 Ok(rep)
107 }
108
109 pub async fn monerod_submit_block(&self, req: &JsonValue) -> Result<JsonValue> {
111 info!(target: "monerod::submitblock", "Proxying submitblock request");
112 let request = JsonRequest::try_from(req)?;
113
114 if !request.params.is_array() {
115 return Err(Error::Custom("Invalid request".to_string()))
116 }
117
118 for block in request.params.get::<Vec<JsonValue>>().unwrap() {
119 let Some(block) = block.get::<String>() else {
120 return Err(Error::Custom("Invalid request".to_string()))
121 };
122
123 debug!(
124 target: "monerod::submitblock", "{:#?}",
125 monero::consensus::deserialize::<monero::Block>(&hex::decode(block).unwrap()).unwrap(),
126 );
127 }
128
129 let response = self.monero_request(MonerodRequest::Post(request)).await?;
130 Ok(response)
131 }
132
133 pub async fn monerod_getblocktemplate(&self, req: &JsonValue) -> Result<JsonValue> {
136 info!(target: "monerod::getblocktemplate", "Proxying getblocktemplate request");
137 let mut request = JsonRequest::try_from(req)?;
138
139 if !request.params.is_object() {
140 return Err(Error::Custom("Invalid request".to_string()))
141 }
142
143 let params: &mut HashMap<String, JsonValue> = request.params.get_mut().unwrap();
144 if !params.contains_key("wallet_address") {
145 return Err(Error::Custom("Invalid request".to_string()))
146 }
147
148 let Some(wallet_address) = params["wallet_address"].get::<String>() else {
149 return Err(Error::Custom("Invalid request".to_string()))
150 };
151
152 let Ok(wallet_address) = monero::Address::from_str(wallet_address) else {
153 return Err(Error::Custom("Invalid request".to_string()))
154 };
155
156 if wallet_address.network != self.monero_network {
157 return Err(Error::Custom("Monero network address mismatch".to_string()))
158 }
159
160 if wallet_address.addr_type != monero::AddressType::Standard {
161 return Err(Error::Custom("Non-standard Monero address".to_string()))
162 }
163
164 let mm_tag = MergeMining(monero::VarInt(32), monero::Hash([0_u8; 32]));
168
169 let tx_extra: RawExtraField = ExtraField(vec![mm_tag]).into();
172
173 debug!(target: "monerod::getblocktemplate", "Inserting \"reserve_size\":{}", tx_extra.0.len());
175 params.insert("reserve_size".to_string(), (tx_extra.0.len() as f64).into());
176
177 params.remove("extra_nonce");
179
180 let gbt_response = self.monero_request(MonerodRequest::Post(request)).await?;
182 debug!(target: "monerod::getblocktemplate", "Got {}", gbt_response.stringify()?);
183 let mut gbt_response = JsonResponse::try_from(&gbt_response)?;
184 let gbt_result: &mut HashMap<String, JsonValue> = gbt_response.result.get_mut().unwrap();
185
186 let mut block_template = monero::consensus::deserialize::<monero::Block>(
188 &hex::decode(gbt_result["blocktemplate_blob"].get::<String>().unwrap()).unwrap(),
189 )
190 .unwrap();
191
192 block_template.miner_tx.prefix.extra = tx_extra;
194
195 gbt_result.insert(
197 "blocktemplate_blob".to_string(),
198 hex::encode(monero::consensus::serialize(&block_template)).into(),
199 );
200
201 gbt_result.insert(
203 "blockhashing_blob".to_string(),
204 hex::encode(block_template.serialize_hashable()).into(),
205 );
206
207 Ok((&gbt_response).into())
209 }
210}