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.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 if !*self.validator.synced.read().await {
168 return JsonResponse::new(JsonValue::from(HashMap::new()), id).into()
169 }
170
171 let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
173 return JsonError::new(InvalidParams, None, id).into()
174 };
175
176 let Some(aux_hash) = params.get("aux_hash") else {
178 return server_error(RpcError::MinerMissingAuxHash, id, None)
179 };
180 let Some(aux_hash) = aux_hash.get::<String>() else {
181 return server_error(RpcError::MinerInvalidAuxHash, id, None)
182 };
183 if HeaderHash::from_str(aux_hash).is_err() {
184 return server_error(RpcError::MinerInvalidAuxHash, id, None)
185 };
186
187 if self.registry.mm_jobs.read().await.contains_key(&aux_hash.to_string()) {
189 return JsonResponse::new(JsonValue::from(HashMap::new()), id).into()
190 }
191
192 let Some(wallet) = params.get("address") else {
194 return server_error(RpcError::MinerMissingAddress, id, None)
195 };
196 let Some(wallet) = wallet.get::<String>() else {
197 return server_error(RpcError::MinerInvalidAddress, id, None)
198 };
199 let config =
200 match MinerRewardsRecipientConfig::from_str(&self.registry.network, wallet).await {
201 Ok(c) => c,
202 Err(e) => return server_error(e, id, None),
203 };
204
205 let Some(height) = params.get("height") else {
207 return server_error(RpcError::MinerMissingHeight, id, None)
208 };
209 let Some(height) = height.get::<f64>() else {
210 return server_error(RpcError::MinerInvalidHeight, id, None)
211 };
212 let height = *height as u64;
213
214 let Some(prev_id) = params.get("prev_id") else {
216 return server_error(RpcError::MinerMissingPrevId, id, None)
217 };
218 let Some(prev_id) = prev_id.get::<String>() else {
219 return server_error(RpcError::MinerInvalidPrevId, id, None)
220 };
221 let Ok(prev_id) = hex::decode(prev_id) else {
222 return server_error(RpcError::MinerInvalidPrevId, id, None)
223 };
224 let prev_id = monero::Hash::from_slice(&prev_id);
225
226 let (job_id, difficulty) =
228 match self.registry.register_merge_miner(&self.validator, wallet, &config).await {
229 Ok(p) => p,
230 Err(e) => {
231 error!(
232 target: "darkfid::rpc::rpc_xmr::xmr_merge_mining_get_aux_block",
233 "[RPC-XMR] Failed to register merge miner: {e}",
234 );
235 return JsonResponse::new(JsonValue::from(HashMap::new()), id).into()
236 }
237 };
238
239 info!(
241 target: "darkfid::rpc::rpc_xmr::xmr_merge_mining_get_aux_block",
242 "[RPC-XMR] Created new merge mining job: aux_hash={job_id}, height={height}, prev_id={prev_id}"
243 );
244 let response = JsonValue::from(HashMap::from([
245 ("aux_blob".to_string(), JsonValue::from(hex::encode(vec![]))),
246 ("aux_diff".to_string(), JsonValue::from(difficulty)),
247 ("aux_hash".to_string(), JsonValue::from(job_id)),
248 ]));
249 JsonResponse::new(response, id).into()
250 }
251
252 pub async fn xmr_merge_mining_submit_solution(&self, id: u16, params: JsonValue) -> JsonResult {
287 if !*self.validator.synced.read().await {
289 return miner_status_response(id, "rejected")
290 }
291
292 let submit_lock = self.registry.submit_lock.write().await;
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 mm_jobs = self.registry.mm_jobs.write().await;
313 let Some(wallet) = mm_jobs.get(aux_hash) else {
314 return miner_status_response(id, "rejected")
315 };
316
317 let mut block_templates = self.registry.block_templates.write().await;
320 let Some(block_template) = block_templates.get_mut(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 if let Err(e) =
426 self.registry.submit(&self.validator, &self.subscribers, &self.p2p_handler, block).await
427 {
428 error!(
429 target: "darkfid::rpc::rpc_xmr::xmr_merge_mining_submit_solution",
430 "[RPC-XMR] Error submitting new block: {e}",
431 );
432
433 let mut jobs = self.registry.jobs.write().await;
435 if let Err(e) = self
436 .registry
437 .refresh_jobs(&mut block_templates, &mut jobs, &mut mm_jobs, &self.validator)
438 .await
439 {
440 error!(
441 target: "darkfid::rpc::rpc_xmr::xmr_merge_mining_submit_solution",
442 "[RPC-XMR] Error refreshing registry jobs: {e}",
443 );
444 }
445
446 drop(block_templates);
448 drop(jobs);
449 drop(mm_jobs);
450 drop(submit_lock);
451
452 return miner_status_response(id, "rejected")
453 }
454
455 block_template.submitted = true;
457
458 drop(block_templates);
460 drop(mm_jobs);
461 drop(submit_lock);
462
463 miner_status_response(id, "accepted")
464 }
465}