1use std::{collections::HashMap, str::FromStr};
20
21use darkfi::{
22 blockchain::{
23 header_store::PowData,
24 monero::{
25 fixed_array::FixedByteArray, merkle_proof::MerkleProof, monero_block_deserialize,
26 MoneroPowData,
27 },
28 HeaderHash,
29 },
30 rpc::jsonrpc::{ErrorCode, ErrorCode::InvalidParams, JsonError, JsonResponse, JsonResult},
31 util::encoding::base64,
32 validator::consensus::Proposal,
33};
34use darkfi_sdk::{
35 crypto::{keypair::Address, pasta_prelude::PrimeField, FuncId},
36 pasta::pallas,
37};
38use darkfi_serial::{deserialize_async, serialize_async};
39use hex::FromHex;
40use tinyjson::JsonValue;
41use tracing::{error, info};
42
43use crate::{
44 proto::ProposalMessage,
45 rpc_miner::{generate_next_block, MinerRewardsRecipientConfig},
46 server_error, DarkfiNode, RpcError,
47};
48
49impl DarkfiNode {
52 pub async fn xmr_merge_mining_get_chain_id(&self, id: u16, params: JsonValue) -> JsonResult {
64 let Some(params) = params.get::<Vec<JsonValue>>() else {
66 return JsonError::new(InvalidParams, None, id).into()
67 };
68 if !params.is_empty() {
69 return JsonError::new(InvalidParams, None, id).into()
70 }
71
72 let (_, genesis_hash) = match self.validator.blockchain.genesis() {
74 Ok(v) => v,
75 Err(e) => {
76 error!(
77 target: "darkfid::rpc::xmr_merge_mining_get_chain_id",
78 "[RPC-XMR] Error fetching genesis block hash: {e}"
79 );
80 return JsonError::new(ErrorCode::InternalError, None, id).into()
81 }
82 };
83
84 let response =
88 HashMap::from([("chain_id".to_string(), JsonValue::from(genesis_hash.to_string()))]);
89 JsonResponse::new(JsonValue::from(response), id).into()
90 }
91
92 pub async fn xmr_merge_mining_get_aux_block(&self, id: u16, params: JsonValue) -> JsonResult {
110 if !*self.validator.synced.read().await {
112 return server_error(RpcError::NotSynced, id, None)
113 }
114
115 let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
117 return JsonError::new(InvalidParams, None, id).into()
118 };
119 if params.len() != 4 {
120 return JsonError::new(InvalidParams, None, id).into()
121 }
122
123 let Some(address) = params.get("address") else {
125 return server_error(RpcError::MinerMissingAddress, id, None)
126 };
127 let Some(address) = address.get::<String>() else {
128 return server_error(RpcError::MinerInvalidAddress, id, None)
129 };
130 let Some(address_bytes) = base64::decode(address) else {
131 return server_error(RpcError::MinerInvalidAddress, id, None)
132 };
133 let Ok((recipient, spend_hook, user_data)) =
134 deserialize_async::<(String, Option<String>, Option<String>)>(&address_bytes).await
135 else {
136 return server_error(RpcError::MinerInvalidAddress, id, None)
137 };
138 let Ok(recipient) = Address::from_str(&recipient) else {
139 return server_error(RpcError::MinerInvalidRecipient, id, None)
140 };
141 if recipient.network() != self.network {
142 return server_error(RpcError::MinerInvalidRecipientPrefix, id, None)
143 }
144 let spend_hook = match spend_hook {
145 Some(s) => match FuncId::from_str(&s) {
146 Ok(s) => Some(s),
147 Err(_) => return server_error(RpcError::MinerInvalidSpendHook, id, None),
148 },
149 None => None,
150 };
151 let user_data: Option<pallas::Base> = match user_data {
152 Some(u) => {
153 let Ok(bytes) = bs58::decode(&u).into_vec() else {
154 return server_error(RpcError::MinerInvalidUserData, id, None)
155 };
156 let bytes: [u8; 32] = match bytes.try_into() {
157 Ok(b) => b,
158 Err(_) => return server_error(RpcError::MinerInvalidUserData, id, None),
159 };
160 match pallas::Base::from_repr(bytes).into() {
161 Some(v) => Some(v),
162 None => return server_error(RpcError::MinerInvalidUserData, id, None),
163 }
164 }
165 None => None,
166 };
167
168 let Some(aux_hash) = params.get("aux_hash") else {
170 return server_error(RpcError::MinerMissingAuxHash, id, None)
171 };
172 let Some(aux_hash) = aux_hash.get::<String>() else {
173 return server_error(RpcError::MinerInvalidAuxHash, id, None)
174 };
175 let Ok(aux_hash) = HeaderHash::from_str(aux_hash) else {
176 return server_error(RpcError::MinerInvalidAuxHash, id, None)
177 };
178
179 let Some(height) = params.get("height") else {
181 return server_error(RpcError::MinerMissingHeight, id, None)
182 };
183 let Some(height) = height.get::<f64>() else {
184 return server_error(RpcError::MinerInvalidHeight, id, None)
185 };
186 let height = *height as u64;
187
188 let Some(prev_id) = params.get("prev_id") else {
190 return server_error(RpcError::MinerMissingPrevId, id, None)
191 };
192 let Some(prev_id) = prev_id.get::<String>() else {
193 return server_error(RpcError::MinerInvalidPrevId, id, None)
194 };
195 let Ok(prev_id) = hex::decode(prev_id) else {
196 return server_error(RpcError::MinerInvalidPrevId, id, None)
197 };
198 let prev_id = monero::Hash::from_slice(&prev_id);
199
200 let mut mm_blocktemplates = self.mm_blocktemplates.lock().await;
212 let mut extended_fork = match self.validator.best_current_fork().await {
213 Ok(f) => f,
214 Err(e) => {
215 error!(
216 target: "darkfid::rpc_xmr::xmr_merge_mining_get_aux_block",
217 "[RPC-XMR] Finding best fork index failed: {e}",
218 );
219 return JsonError::new(ErrorCode::InternalError, None, id).into()
220 }
221 };
222 if let Some((block, difficulty, _)) = mm_blocktemplates.get(&address_bytes) {
223 let last_proposal = match extended_fork.last_proposal() {
224 Ok(p) => p,
225 Err(e) => {
226 error!(
227 target: "darkfid::rpc_xmr::xmr_merge_mining_get_aux_block",
228 "[RPC-XMR] Retrieving best fork last proposal failed: {e}",
229 );
230 return JsonError::new(ErrorCode::InternalError, None, id).into()
231 }
232 };
233 if last_proposal.hash == block.header.previous {
234 let blockhash = block.header.template_hash();
235 return if blockhash != aux_hash {
236 JsonResponse::new(
237 JsonValue::from(HashMap::from([
238 ("aux_blob".to_string(), JsonValue::from(hex::encode(address_bytes))),
239 ("aux_diff".to_string(), JsonValue::from(*difficulty)),
240 ("aux_hash".to_string(), JsonValue::from(blockhash.as_string())),
241 ])),
242 id,
243 )
244 .into()
245 } else {
246 JsonResponse::new(JsonValue::from(HashMap::new()), id).into()
247 }
248 }
249 mm_blocktemplates.remove(&address_bytes);
250 }
251
252 let recipient_str = format!("{recipient}");
256 let spend_hook_str = match spend_hook {
257 Some(spend_hook) => format!("{spend_hook}"),
258 None => String::from("-"),
259 };
260 let user_data_str = match user_data {
261 Some(user_data) => bs58::encode(user_data.to_repr()).into_string(),
262 None => String::from("-"),
263 };
264 let recipient_config = MinerRewardsRecipientConfig { recipient, spend_hook, user_data };
265
266 let difficulty: f64 = match extended_fork.module.next_difficulty() {
269 Ok(v) => {
270 v.to_string().parse().unwrap()
272 }
273 Err(e) => {
274 error!(
275 target: "darkfid::rpc_xmr::xmr_merge_mining_get_aux_block",
276 "[RPC-XMR] Finding next mining difficulty failed: {e}",
277 );
278 return JsonError::new(ErrorCode::InternalError, None, id).into()
279 }
280 };
281
282 let (_, blocktemplate, block_signing_secret) = match generate_next_block(
283 &mut extended_fork,
284 &recipient_config,
285 &self.powrewardv1_zk.zkbin,
286 &self.powrewardv1_zk.provingkey,
287 self.validator.consensus.module.read().await.target,
288 self.validator.verify_fees,
289 )
290 .await
291 {
292 Ok(v) => v,
293 Err(e) => {
294 error!(
295 target: "darkfid::rpc_xmr::xmr_merge_mining_get_aux_block",
296 "[RPC-XMR] Failed to generate next blocktemplate: {e}",
297 );
298 return JsonError::new(ErrorCode::InternalError, None, id).into()
299 }
300 };
301
302 let blockhash = blocktemplate.header.template_hash();
305 mm_blocktemplates
306 .insert(address_bytes.clone(), (blocktemplate, difficulty, block_signing_secret));
307 info!(
308 target: "darkfid::rpc_xmr::xmr_merge_mining_get_aux_block",
309 "[RPC-XMR] Created new blocktemplate: address={recipient_str}, spend_hook={spend_hook_str}, user_data={user_data_str}, aux_hash={blockhash}, height={height}, prev_id={prev_id}"
310 );
311
312 let response = JsonValue::from(HashMap::from([
313 ("aux_blob".to_string(), JsonValue::from(hex::encode(address_bytes))),
314 ("aux_diff".to_string(), JsonValue::from(difficulty)),
315 ("aux_hash".to_string(), JsonValue::from(blockhash.as_string())),
316 ]));
317
318 JsonResponse::new(response, id).into()
319 }
320
321 pub async fn xmr_merge_mining_submit_solution(&self, id: u16, params: JsonValue) -> JsonResult {
344 if !*self.validator.synced.read().await {
346 return server_error(RpcError::NotSynced, id, None)
347 }
348
349 let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
351 return JsonError::new(InvalidParams, None, id).into()
352 };
353 if params.len() != 6 {
354 return JsonError::new(InvalidParams, None, id).into()
355 }
356
357 let Some(aux_blob) = params.get("aux_blob") else {
359 return server_error(RpcError::MinerMissingAuxBlob, id, None)
360 };
361 let Some(aux_blob) = aux_blob.get::<String>() else {
362 return server_error(RpcError::MinerInvalidAuxBlob, id, None)
363 };
364 let Ok(address_bytes) = hex::decode(aux_blob) else {
365 return server_error(RpcError::MinerInvalidAuxBlob, id, None)
366 };
367 let Ok((recipient, spend_hook, user_data)) =
368 deserialize_async::<(String, Option<String>, Option<String>)>(&address_bytes).await
369 else {
370 return server_error(RpcError::MinerInvalidAuxBlob, id, None)
371 };
372 let Ok(recipient) = Address::from_str(&recipient) else {
373 return server_error(RpcError::MinerInvalidRecipient, id, None)
374 };
375 if recipient.network() != self.network {
376 return server_error(RpcError::MinerInvalidRecipientPrefix, id, None)
377 }
378 if let Some(spend_hook) = spend_hook {
379 if FuncId::from_str(&spend_hook).is_err() {
380 return server_error(RpcError::MinerInvalidSpendHook, id, None)
381 }
382 };
383 if let Some(user_data) = user_data {
384 let Ok(bytes) = bs58::decode(&user_data).into_vec() else {
385 return server_error(RpcError::MinerInvalidUserData, id, None)
386 };
387 let bytes: [u8; 32] = match bytes.try_into() {
388 Ok(b) => b,
389 Err(_) => return server_error(RpcError::MinerInvalidUserData, id, None),
390 };
391 let _: pallas::Base = match pallas::Base::from_repr(bytes).into() {
392 Some(v) => v,
393 None => return server_error(RpcError::MinerInvalidUserData, id, None),
394 };
395 };
396
397 let Some(aux_hash) = params.get("aux_hash") else {
399 return server_error(RpcError::MinerMissingAuxHash, id, None)
400 };
401 let Some(aux_hash) = aux_hash.get::<String>() else {
402 return server_error(RpcError::MinerInvalidAuxHash, id, None)
403 };
404 let Ok(aux_hash) = HeaderHash::from_str(aux_hash) else {
405 return server_error(RpcError::MinerInvalidAuxHash, id, None)
406 };
407
408 let mut mm_blocktemplates = self.mm_blocktemplates.lock().await;
410 if !mm_blocktemplates.contains_key(&address_bytes) {
411 return server_error(RpcError::MinerUnknownJob, id, None)
412 }
413
414 let Some(blob) = params.get("blob") else {
416 return server_error(RpcError::MinerMissingBlob, id, None)
417 };
418 let Some(blob) = blob.get::<String>() else {
419 return server_error(RpcError::MinerInvalidBlob, id, None)
420 };
421 let Ok(block) = monero_block_deserialize(blob) else {
422 return server_error(RpcError::MinerInvalidBlob, id, None)
423 };
424
425 let Some(merkle_proof_j) = params.get("merkle_proof") else {
427 return server_error(RpcError::MinerMissingMerkleProof, id, None)
428 };
429 let Some(merkle_proof_j) = merkle_proof_j.get::<Vec<JsonValue>>() else {
430 return server_error(RpcError::MinerInvalidMerkleProof, id, None)
431 };
432 let mut merkle_proof: Vec<monero::Hash> = Vec::with_capacity(merkle_proof_j.len());
433 for hash in merkle_proof_j.iter() {
434 match hash.get::<String>() {
435 Some(v) => {
436 let Ok(val) = monero::Hash::from_hex(v) else {
437 return server_error(RpcError::MinerInvalidMerkleProof, id, None)
438 };
439
440 merkle_proof.push(val);
441 }
442 None => return server_error(RpcError::MinerInvalidMerkleProof, id, None),
443 }
444 }
445
446 let Some(path) = params.get("path") else {
448 return server_error(RpcError::MinerMissingPath, id, None)
449 };
450 let Some(path) = path.get::<f64>() else {
451 return server_error(RpcError::MinerInvalidPath, id, None)
452 };
453 let path = *path as u32;
454
455 let Some(seed_hash) = params.get("seed_hash") else {
457 return server_error(RpcError::MinerMissingSeedHash, id, None)
458 };
459 let Some(seed_hash) = seed_hash.get::<String>() else {
460 return server_error(RpcError::MinerInvalidSeedHash, id, None)
461 };
462 let Ok(seed_hash) = monero::Hash::from_hex(seed_hash) else {
463 return server_error(RpcError::MinerInvalidSeedHash, id, None)
464 };
465 let Ok(seed_hash) = FixedByteArray::from_bytes(seed_hash.as_bytes()) else {
466 return server_error(RpcError::MinerInvalidSeedHash, id, None)
467 };
468
469 info!(
470 target: "darkfid::rpc_xmr::xmr_merge_mining_submit_solution",
471 "[RPC-XMR] Got solution submission: aux_hash={aux_hash}",
472 );
473
474 let Some(merkle_proof) = MerkleProof::try_construct(merkle_proof, path) else {
476 return server_error(RpcError::MinerMerkleProofConstructionFailed, id, None)
477 };
478 let monero_pow_data = match MoneroPowData::new(block, seed_hash, merkle_proof) {
479 Ok(v) => v,
480 Err(e) => {
481 error!(
482 target: "darkfid::rpc_xmr::xmr_merge_mining_submit_solution",
483 "[RPC-XMR] Failed constructing MoneroPowData: {e}",
484 );
485 return server_error(RpcError::MinerMoneroPowDataConstructionFailed, id, None)
486 }
487 };
488
489 let (block, _, secret) = &mm_blocktemplates.get(&address_bytes).unwrap();
491 let mut block = block.clone();
492 block.header.pow_data = PowData::Monero(monero_pow_data);
493 block.sign(secret);
494
495 mm_blocktemplates.remove(&address_bytes);
499
500 info!(
502 target: "darkfid::rpc_xmr::xmr_merge_mining_submit_solution",
503 "[RPC-XMR] Proposing new block to network",
504 );
505 let proposal = Proposal::new(block);
506 if let Err(e) = self.validator.append_proposal(&proposal).await {
507 error!(
508 target: "darkfid::rpc_xmr::xmr_merge_submit_solution",
509 "[RPC-XMR] Error proposing new block: {e}",
510 );
511 return JsonResponse::new(
512 JsonValue::from(HashMap::from([(
513 "status".to_string(),
514 JsonValue::from("rejected".to_string()),
515 )])),
516 id,
517 )
518 .into()
519 }
520
521 let proposals_sub = self.subscribers.get("proposals").unwrap();
522 let enc_prop = JsonValue::String(base64::encode(&serialize_async(&proposal).await));
523 proposals_sub.notify(vec![enc_prop].into()).await;
524
525 info!(
526 target: "darkfid::rpc_xmr::xmr_merge_mining_submit_solution",
527 "[RPC-XMR] Broadcasting new block to network",
528 );
529 let message = ProposalMessage(proposal);
530 self.p2p_handler.p2p.broadcast(&message).await;
531
532 JsonResponse::new(
533 JsonValue::from(HashMap::from([(
534 "status".to_string(),
535 JsonValue::from("accepted".to_string()),
536 )])),
537 id,
538 )
539 .into()
540 }
541}