1use std::{
20 collections::{HashMap, HashSet},
21 str::FromStr,
22};
23
24use async_trait::async_trait;
25use hex::FromHex;
26use smol::lock::MutexGuard;
27use tinyjson::JsonValue;
28use tracing::{debug, error, info};
29
30use darkfi::{
31 blockchain::{
32 header_store::PowData,
33 monero::{
34 fixed_array::FixedByteArray, merkle_proof::MerkleProof, monero_block_deserialize,
35 MoneroPowData,
36 },
37 HeaderHash,
38 },
39 rpc::{
40 jsonrpc::{
41 ErrorCode, ErrorCode::InvalidParams, JsonError, JsonRequest, JsonResponse, JsonResult,
42 },
43 server::RequestHandler,
44 },
45 system::StoppableTaskPtr,
46};
47use darkfi_sdk::crypto::keypair::Network;
48
49use crate::{
50 error::{miner_status_response, server_error, RpcError},
51 registry::model::MinerRewardsRecipientConfig,
52 DarkfiNode,
53};
54
55pub struct MmRpcHandler;
59
60#[async_trait]
61#[rustfmt::skip]
62impl RequestHandler<MmRpcHandler> for DarkfiNode {
63 async fn handle_request(&self, req: JsonRequest) -> JsonResult {
64 debug!(target: "darkfid::rpc::rpc_xmr", "--> {}", req.stringify().unwrap());
65
66 match req.method.as_str() {
67 "merge_mining_get_chain_id" => self.xmr_merge_mining_get_chain_id(req.id, req.params).await,
71 "merge_mining_get_aux_block" => self.xmr_merge_mining_get_aux_block(req.id, req.params).await,
72 "merge_mining_submit_solution" => self.xmr_merge_mining_submit_solution(req.id, req.params).await,
73 _ => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
74 }
75 }
76
77 async fn connections_mut(&self) -> MutexGuard<'life0, HashSet<StoppableTaskPtr>> {
78 self.registry.mm_rpc_connections.lock().await
79 }
80}
81
82impl DarkfiNode {
83 pub async fn xmr_merge_mining_get_chain_id(&self, id: u16, params: JsonValue) -> JsonResult {
96 let Some(params) = params.get::<Vec<JsonValue>>() else {
98 return JsonError::new(InvalidParams, None, id).into()
99 };
100 if !params.is_empty() {
101 return JsonError::new(InvalidParams, None, id).into()
102 }
103
104 let (_, genesis_hash) = match self.validator.read().await.blockchain.genesis() {
106 Ok(v) => v,
107 Err(e) => {
108 error!(
109 target: "darkfid::rpc::rpc_xmr::xmr_merge_mining_get_chain_id",
110 "[RPC-XMR] Error fetching genesis block hash: {e}"
111 );
112 return JsonError::new(ErrorCode::InternalError, None, id).into()
113 }
114 };
115
116 let mut hasher = blake3::Hasher::new();
118 hasher.update(genesis_hash.inner());
119 match self.registry.network {
120 Network::Mainnet => hasher.update("mainnet".as_bytes()),
121 Network::Testnet => hasher.update("testnet".as_bytes()),
122 };
123 hasher.update(&0u32.to_le_bytes());
124 let chain_id = hasher.finalize().to_string();
125
126 let response = HashMap::from([("chain_id".to_string(), JsonValue::from(chain_id))]);
127 JsonResponse::new(JsonValue::from(response), id).into()
128 }
129
130 pub async fn xmr_merge_mining_get_aux_block(&self, id: u16, params: JsonValue) -> JsonResult {
166 let validator = self.validator.read().await;
168 if !validator.synced {
169 return JsonResponse::new(JsonValue::from(HashMap::new()), id).into()
170 }
171
172 let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
174 return JsonError::new(InvalidParams, None, id).into()
175 };
176
177 let Some(aux_hash) = params.get("aux_hash") else {
179 return server_error(RpcError::MinerMissingAuxHash, id, None)
180 };
181 let Some(aux_hash) = aux_hash.get::<String>() else {
182 return server_error(RpcError::MinerInvalidAuxHash, id, None)
183 };
184 if HeaderHash::from_str(aux_hash).is_err() {
185 return server_error(RpcError::MinerInvalidAuxHash, id, None)
186 };
187
188 let mut registry = self.registry.state.write().await;
190 if registry.mm_jobs.contains_key(&aux_hash.to_string()) {
191 return JsonResponse::new(JsonValue::from(HashMap::new()), id).into()
192 }
193
194 let Some(wallet) = params.get("address") else {
196 return server_error(RpcError::MinerMissingAddress, id, None)
197 };
198 let Some(wallet) = wallet.get::<String>() else {
199 return server_error(RpcError::MinerInvalidAddress, id, None)
200 };
201 let config =
202 match MinerRewardsRecipientConfig::from_str(&self.registry.network, wallet).await {
203 Ok(c) => c,
204 Err(e) => return server_error(e, id, None),
205 };
206
207 let Some(height) = params.get("height") else {
209 return server_error(RpcError::MinerMissingHeight, id, None)
210 };
211 let Some(height) = height.get::<f64>() else {
212 return server_error(RpcError::MinerInvalidHeight, id, None)
213 };
214 let height = *height as u64;
215
216 let Some(prev_id) = params.get("prev_id") else {
218 return server_error(RpcError::MinerMissingPrevId, id, None)
219 };
220 let Some(prev_id) = prev_id.get::<String>() else {
221 return server_error(RpcError::MinerInvalidPrevId, id, None)
222 };
223 let Ok(prev_id) = hex::decode(prev_id) else {
224 return server_error(RpcError::MinerInvalidPrevId, id, None)
225 };
226 let prev_id = monero::Hash::from_slice(&prev_id);
227
228 let (job_id, difficulty) =
230 match registry.register_merge_miner(&validator, wallet, &config).await {
231 Ok(p) => p,
232 Err(e) => {
233 error!(
234 target: "darkfid::rpc::rpc_xmr::xmr_merge_mining_get_aux_block",
235 "[RPC-XMR] Failed to register merge miner: {e}",
236 );
237 return JsonResponse::new(JsonValue::from(HashMap::new()), id).into()
238 }
239 };
240
241 info!(
243 target: "darkfid::rpc::rpc_xmr::xmr_merge_mining_get_aux_block",
244 "[RPC-XMR] Created new merge mining job: aux_hash={job_id}, height={height}, prev_id={prev_id}"
245 );
246 let response = JsonValue::from(HashMap::from([
247 ("aux_blob".to_string(), JsonValue::from(hex::encode(vec![]))),
248 ("aux_diff".to_string(), JsonValue::from(difficulty)),
249 ("aux_hash".to_string(), JsonValue::from(job_id)),
250 ]));
251 JsonResponse::new(response, id).into()
252 }
253
254 pub async fn xmr_merge_mining_submit_solution(&self, id: u16, params: JsonValue) -> JsonResult {
289 let mut validator = self.validator.write().await;
291 if !validator.synced {
292 return miner_status_response(id, "rejected")
293 }
294
295 let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
297 return JsonError::new(InvalidParams, None, id).into()
298 };
299
300 let Some(aux_hash) = params.get("aux_hash") else {
302 return server_error(RpcError::MinerMissingAuxHash, id, None)
303 };
304 let Some(aux_hash) = aux_hash.get::<String>() else {
305 return server_error(RpcError::MinerInvalidAuxHash, id, None)
306 };
307 if HeaderHash::from_str(aux_hash).is_err() {
308 return server_error(RpcError::MinerInvalidAuxHash, id, None)
309 }
310
311 let mut registry = self.registry.state.write().await;
313 let Some(wallet) = registry.mm_jobs.get(aux_hash) else {
314 return miner_status_response(id, "rejected")
315 };
316 let wallet = wallet.clone();
317
318 let Some(block_template) = registry.block_templates.get(&wallet) else {
321 return miner_status_response(id, "rejected")
322 };
323
324 if block_template.submitted {
327 return miner_status_response(id, "rejected")
328 }
329
330 let Some(aux_blob) = params.get("aux_blob") else {
332 return server_error(RpcError::MinerMissingAuxBlob, id, None)
333 };
334 let Some(aux_blob) = aux_blob.get::<String>() else {
335 return server_error(RpcError::MinerInvalidAuxBlob, id, None)
336 };
337 let Ok(aux_blob) = hex::decode(aux_blob) else {
338 return server_error(RpcError::MinerInvalidAuxBlob, id, None)
339 };
340 if !aux_blob.is_empty() {
341 return server_error(RpcError::MinerInvalidAuxBlob, id, None)
342 }
343
344 let Some(blob) = params.get("blob") else {
346 return server_error(RpcError::MinerMissingBlob, id, None)
347 };
348 let Some(blob) = blob.get::<String>() else {
349 return server_error(RpcError::MinerInvalidBlob, id, None)
350 };
351 let Ok(block) = monero_block_deserialize(blob) else {
352 return server_error(RpcError::MinerInvalidBlob, id, None)
353 };
354
355 let Some(merkle_proof_j) = params.get("merkle_proof") else {
357 return server_error(RpcError::MinerMissingMerkleProof, id, None)
358 };
359 let Some(merkle_proof_j) = merkle_proof_j.get::<Vec<JsonValue>>() else {
360 return server_error(RpcError::MinerInvalidMerkleProof, id, None)
361 };
362 let mut merkle_proof: Vec<monero::Hash> = Vec::with_capacity(merkle_proof_j.len());
363 for hash in merkle_proof_j.iter() {
364 match hash.get::<String>() {
365 Some(v) => {
366 let Ok(val) = monero::Hash::from_hex(v) else {
367 return server_error(RpcError::MinerInvalidMerkleProof, id, None)
368 };
369
370 merkle_proof.push(val);
371 }
372 None => return server_error(RpcError::MinerInvalidMerkleProof, id, None),
373 }
374 }
375
376 let Some(path) = params.get("path") else {
378 return server_error(RpcError::MinerMissingPath, id, None)
379 };
380 let Some(path) = path.get::<f64>() else {
381 return server_error(RpcError::MinerInvalidPath, id, None)
382 };
383 let path = *path as u32;
384
385 let Some(seed_hash) = params.get("seed_hash") else {
387 return server_error(RpcError::MinerMissingSeedHash, id, None)
388 };
389 let Some(seed_hash) = seed_hash.get::<String>() else {
390 return server_error(RpcError::MinerInvalidSeedHash, id, None)
391 };
392 let Ok(seed_hash) = monero::Hash::from_hex(seed_hash) else {
393 return server_error(RpcError::MinerInvalidSeedHash, id, None)
394 };
395 let Ok(seed_hash) = FixedByteArray::from_bytes(seed_hash.as_bytes()) else {
396 return server_error(RpcError::MinerInvalidSeedHash, id, None)
397 };
398
399 info!(
400 target: "darkfid::rpc::rpc_xmr::xmr_merge_mining_submit_solution",
401 "[RPC-XMR] Got solution submission: aux_hash={aux_hash}",
402 );
403
404 let Some(merkle_proof) = MerkleProof::try_construct(merkle_proof, path) else {
406 return server_error(RpcError::MinerMerkleProofConstructionFailed, id, None)
407 };
408 let monero_pow_data = match MoneroPowData::new(block, seed_hash, merkle_proof) {
409 Ok(v) => v,
410 Err(e) => {
411 error!(
412 target: "darkfid::rpc::rpc_xmr::xmr_merge_mining_submit_solution",
413 "[RPC-XMR] Failed constructing MoneroPowData: {e}",
414 );
415 return server_error(RpcError::MinerMoneroPowDataConstructionFailed, id, None)
416 }
417 };
418
419 let mut block = block_template.block.clone();
421 block.header.pow_data = PowData::Monero(monero_pow_data);
422 block.sign(&block_template.secret);
423
424 let mut block_template = block_template.clone();
427
428 if let Err(e) =
430 registry.submit(&mut validator, &self.subscribers, &self.p2p_handler, block).await
431 {
432 error!(
433 target: "darkfid::rpc::rpc_xmr::xmr_merge_mining_submit_solution",
434 "[RPC-XMR] Error submitting new block: {e}",
435 );
436
437 if let Err(e) = registry.refresh(&validator).await {
439 error!(
440 target: "darkfid::rpc::rpc_xmr::xmr_merge_mining_submit_solution",
441 "[RPC-XMR] Error refreshing registry jobs: {e}",
442 );
443 }
444
445 return miner_status_response(id, "rejected")
446 }
447
448 block_template.submitted = true;
450 registry.block_templates.insert(wallet, block_template);
451
452 miner_status_response(id, "accepted")
453 }
454}