monerod.rs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{collections::HashMap, str::FromStr};
  19. use darkfi::{
  20. rpc::{
  21. jsonrpc::{JsonRequest, JsonResponse},
  22. util::JsonValue,
  23. },
  24. Error, Result,
  25. };
  26. use log::{debug, error, info};
  27. use monero::blockdata::transaction::{ExtraField, RawExtraField, SubField::MergeMining};
  28. use super::MiningProxy;
  29. /// Types of requests that can be sent to monerod
  30. pub(crate) enum MonerodRequest {
  31. Get(String),
  32. Post(JsonRequest),
  33. }
  34. impl MiningProxy {
  35. /// Perform a JSON-RPC request to monerod's endpoint with the given method
  36. pub(crate) async fn monero_request(&self, req: MonerodRequest) -> Result<JsonValue> {
  37. let mut rep = match req {
  38. MonerodRequest::Get(method) => {
  39. let endpoint = format!("{}{}", self.monero_rpc, method);
  40. match surf::get(&endpoint).await {
  41. Ok(v) => v,
  42. Err(e) => {
  43. let e = format!("Failed sending monerod GET request: {}", e);
  44. error!(target: "monerod::monero_request", "{}", e);
  45. return Err(Error::Custom(e))
  46. }
  47. }
  48. }
  49. MonerodRequest::Post(data) => {
  50. let endpoint = format!("{}json_rpc", self.monero_rpc);
  51. let client = surf::Client::new();
  52. match client
  53. .get(endpoint)
  54. .header("Content-Type", "application/json")
  55. .body(data.stringify().unwrap())
  56. .send()
  57. .await
  58. {
  59. Ok(v) => v,
  60. Err(e) => {
  61. let e = format!("Failed sending monerod POST request: {}", e);
  62. error!(target: "monerod::monero_request", "{}", e);
  63. return Err(Error::Custom(e))
  64. }
  65. }
  66. }
  67. };
  68. let json_rep: JsonValue = match rep.body_string().await {
  69. Ok(v) => match v.parse() {
  70. Ok(v) => v,
  71. Err(e) => {
  72. let e = format!("Failed parsing JSON string from monerod response: {}", e);
  73. error!(target: "monerod::monero_request", "{}", e);
  74. return Err(Error::Custom(e))
  75. }
  76. },
  77. Err(e) => {
  78. let e = format!("Failed parsing body string from monerod response: {}", e);
  79. error!(target: "monerod::monero_request", "{}", e);
  80. return Err(Error::Custom(e))
  81. }
  82. };
  83. Ok(json_rep)
  84. }
  85. /// Proxy the `/getheight` RPC request
  86. pub async fn monerod_get_height(&self) -> Result<JsonValue> {
  87. info!(target: "monerod::getheight", "Proxying /getheight request");
  88. let rep = self.monero_request(MonerodRequest::Get("getheight".to_string())).await?;
  89. Ok(rep)
  90. }
  91. /// Proxy the `/getinfo` RPC request
  92. pub async fn monerod_get_info(&self) -> Result<JsonValue> {
  93. info!(target: "monerod::getinfo", "Proxying /getinfo request");
  94. let rep = self.monero_request(MonerodRequest::Get("getinfo".to_string())).await?;
  95. Ok(rep)
  96. }
  97. /// Proxy the `submitblock` RPC request
  98. pub async fn monerod_submit_block(&self, req: &JsonValue) -> Result<JsonValue> {
  99. info!(target: "monerod::submitblock", "Proxying submitblock request");
  100. let request = JsonRequest::try_from(req)?;
  101. if !request.params.is_array() {
  102. return Err(Error::Custom("Invalid request".to_string()))
  103. }
  104. for block in request.params.get::<Vec<JsonValue>>().unwrap() {
  105. let Some(block) = block.get::<String>() else {
  106. return Err(Error::Custom("Invalid request".to_string()))
  107. };
  108. debug!(
  109. target: "monerod::submitblock", "{:#?}",
  110. monero::consensus::deserialize::<monero::Block>(&hex::decode(block).unwrap()).unwrap(),
  111. );
  112. }
  113. let response = self.monero_request(MonerodRequest::Post(request)).await?;
  114. Ok(response)
  115. }
  116. /// Perform the `getblocktemplate` request and modify it with the necessary
  117. /// merge mining data.
  118. pub async fn monerod_getblocktemplate(&self, req: &JsonValue) -> Result<JsonValue> {
  119. info!(target: "monerod::getblocktemplate", "Proxying getblocktemplate request");
  120. let mut request = JsonRequest::try_from(req)?;
  121. if !request.params.is_object() {
  122. return Err(Error::Custom("Invalid request".to_string()))
  123. }
  124. let params: &mut HashMap<String, JsonValue> = request.params.get_mut().unwrap();
  125. if !params.contains_key("wallet_address") {
  126. return Err(Error::Custom("Invalid request".to_string()))
  127. }
  128. let Some(wallet_address) = params["wallet_address"].get::<String>() else {
  129. return Err(Error::Custom("Invalid request".to_string()))
  130. };
  131. let Ok(wallet_address) = monero::Address::from_str(wallet_address) else {
  132. return Err(Error::Custom("Invalid request".to_string()))
  133. };
  134. if wallet_address.network != self.monero_network {
  135. return Err(Error::Custom("Monero network address mismatch".to_string()))
  136. }
  137. if wallet_address.addr_type != monero::AddressType::Standard {
  138. return Err(Error::Custom("Non-standard Monero address".to_string()))
  139. }
  140. // Create the Merge Mining data
  141. // TODO: This is where we're gonna include the necessary DarkFi data
  142. // that has to end up in Monero blocks.
  143. let mm_tag = MergeMining(Some(monero::VarInt(32)), monero::Hash([0_u8; 32]));
  144. // Construct `tx_extra` from all the extra fields we have to add to
  145. // the coinbase transaction in the block we're mining.
  146. let tx_extra: RawExtraField = ExtraField(vec![mm_tag]).into();
  147. // Modify the params `reserve_size` to fit our Merge Mining data
  148. debug!(target: "monerod::getblocktemplate", "Inserting \"reserve_size\":{}", tx_extra.0.len());
  149. params.insert("reserve_size".to_string(), (tx_extra.0.len() as f64).into());
  150. // Remove `extra_nonce` from the request, XMRig tends to send this in daemon-mode
  151. params.remove("extra_nonce");
  152. // Perform the `getblocktemplate` call:
  153. let gbt_response = self.monero_request(MonerodRequest::Post(request)).await?;
  154. debug!(target: "monerod::getblocktemplate", "Got {}", gbt_response.stringify()?);
  155. let mut gbt_response = JsonResponse::try_from(&gbt_response)?;
  156. let gbt_result: &mut HashMap<String, JsonValue> = gbt_response.result.get_mut().unwrap();
  157. // Now we have to modify the block template:
  158. let mut block_template = monero::consensus::deserialize::<monero::Block>(
  159. &hex::decode(gbt_result["blocktemplate_blob"].get::<String>().unwrap()).unwrap(),
  160. )
  161. .unwrap();
  162. // Update coinbase tx with our extra field
  163. block_template.miner_tx.prefix.extra = tx_extra;
  164. // Update `blocktemplate_blob` with the modified block:
  165. gbt_result.insert(
  166. "blocktemplate_blob".to_string(),
  167. hex::encode(monero::consensus::serialize(&block_template)).into(),
  168. );
  169. // Update `blockhashing_blob` in order to perform correct PoW:
  170. gbt_result.insert(
  171. "blockhashing_blob".to_string(),
  172. hex::encode(block_template.serialize_hashable()).into(),
  173. );
  174. // Return the modified JSON response
  175. Ok((&gbt_response).into())
  176. }
  177. }