monero.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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;
  19. use darkfi::{
  20. rpc::{
  21. jsonrpc::{
  22. ErrorCode::{InternalError, InvalidParams},
  23. JsonError, JsonRequest, JsonResponse, JsonResult,
  24. },
  25. util::JsonValue,
  26. },
  27. Error, Result,
  28. };
  29. use log::{debug, error, info};
  30. use monero::blockdata::transaction::{ExtraField, RawExtraField, SubField::MergeMining};
  31. use super::MiningProxy;
  32. impl MiningProxy {
  33. /// Perform a oneshot HTTP JSON-RPC request to the set monerod endpoint.
  34. /// This is a single request-reply which we disconnect after recieving the reply.
  35. pub async fn oneshot_request(&self, req: JsonRequest) -> Result<JsonValue> {
  36. let client = surf::Client::new();
  37. let mut response = match client
  38. .get(&self.monerod.monerod_rpc)
  39. .header("Content-Type", "application/json")
  40. .body(req.stringify().unwrap())
  41. .send()
  42. .await
  43. {
  44. Ok(v) => v,
  45. Err(e) => {
  46. error!(target: "rpc::monero::oneshot_request", "[RPC] Error sending RPC request to monerod: {}", e);
  47. return Err(Error::ParseFailed("Failed sending monerod RPC request"))
  48. }
  49. };
  50. let response_bytes = match response.body_bytes().await {
  51. Ok(v) => v,
  52. Err(e) => {
  53. error!(target: "rpc::monero::get_block_count", "[RPC] Error reading monerod RPC response: {}", e);
  54. return Err(Error::ParseFailed("Failed reading monerod RPC reponse"))
  55. }
  56. };
  57. let response_string = match String::from_utf8(response_bytes) {
  58. Ok(v) => v,
  59. Err(e) => {
  60. error!(target: "rpc::monero::get_block_count", "[RPC] Error parsing monerod RPC response: {}", e);
  61. return Err(Error::ParseFailed("Failed parsing monerod RPC reponse"))
  62. }
  63. };
  64. let response_json: JsonValue = match response_string.parse() {
  65. Ok(v) => v,
  66. Err(e) => {
  67. error!(target: "rpc::monero::get_block_count", "[RPC] Error parsing monerod RPC response: {}", e);
  68. return Err(Error::ParseFailed("Failed parsing monerod RPC reponse"))
  69. }
  70. };
  71. Ok(response_json)
  72. }
  73. /// Look up how many blocks are in the longest chain known to the node.
  74. /// <https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_block_count>
  75. pub async fn monero_get_block_count(&self, id: u16, _params: JsonValue) -> JsonResult {
  76. debug!(target: "rpc::monero", "get_block_count()");
  77. // This request can just passthrough
  78. let req = JsonRequest::new("get_block_count", vec![].into());
  79. let rep = match self.oneshot_request(req).await {
  80. Ok(v) => v,
  81. Err(e) => {
  82. error!(target: "rpc::monero::get_block_count", "[RPC] {}", e);
  83. return JsonError::new(InternalError, Some(e.to_string()), id).into()
  84. }
  85. };
  86. JsonResponse::new(rep, id).into()
  87. }
  88. /// Look up a block's hash by its height.
  89. /// <https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#on_get_block_hash>
  90. pub async fn monero_on_get_block_hash(&self, id: u16, params: JsonValue) -> JsonResult {
  91. debug!(target: "rpc::monero", "on_get_block_hash()");
  92. let Some(params) = params.get::<Vec<JsonValue>>() else {
  93. return JsonError::new(InvalidParams, None, id).into()
  94. };
  95. if !params.len() != 1 || params[0].is_number() {
  96. return JsonError::new(InvalidParams, None, id).into()
  97. }
  98. let Some(block_height) = params[0].get::<f64>() else {
  99. return JsonError::new(InvalidParams, None, id).into()
  100. };
  101. let req =
  102. JsonRequest::new("on_get_block_hash", vec![JsonValue::Number(*block_height)].into());
  103. let rep = match self.oneshot_request(req).await {
  104. Ok(v) => v,
  105. Err(e) => {
  106. error!(target: "rpc::monero::get_block_count", "[RPC] {}", e);
  107. return JsonError::new(InternalError, Some(e.to_string()), id).into()
  108. }
  109. };
  110. JsonResponse::new(rep, id).into()
  111. }
  112. /// Get a block template on which mining a new block.
  113. /// <https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_block_template>
  114. pub async fn monero_get_block_template(&self, id: u16, params: JsonValue) -> JsonResult {
  115. debug!(target: "rpc::monero", "get_block_template()");
  116. if !params.is_object() {
  117. return JsonError::new(InvalidParams, None, id).into()
  118. }
  119. let params = params.get::<HashMap<String, JsonValue>>().unwrap();
  120. if !params.contains_key("wallet_address") || !params.contains_key("reserve_size") {
  121. return JsonError::new(InvalidParams, None, id).into()
  122. }
  123. let Some(wallet_address) = params["wallet_address"].get::<String>() else {
  124. return JsonError::new(InvalidParams, None, id).into()
  125. };
  126. let Some(_reserve_size) = params["reserve_size"].get::<f64>() else {
  127. return JsonError::new(InvalidParams, None, id).into()
  128. };
  129. // The MergeMining tag and anything else going into ExtraField should
  130. // be done here, so we can pass the correct reserve_size.
  131. // Create the Merge Mining Tag: (`depth`, `merkle_root`)
  132. let mm_tag = MergeMining(Some(monero::VarInt(32)), monero::Hash([0_u8; 32]));
  133. // Construct tx_extra from all the extra fields we have to add to the coinbase
  134. // transaction in the block we're mining.
  135. let tx_extra: RawExtraField = ExtraField(vec![mm_tag]).into();
  136. // Create request. Usually, xmrig will just request a job, so this endpoint
  137. // isn't really used through JSON-RPC. We use it from other methods, which
  138. // should then include the proper wallet address to plug in. The wallet
  139. // address can be set in mmproxy's config or via CLI flags.
  140. //
  141. // `reserve_size` is overridden with the size of `tx_extra` created above.
  142. let req = JsonRequest::new(
  143. "get_block_template",
  144. HashMap::from([
  145. ("wallet_address".to_string(), (*wallet_address).clone().into()),
  146. ("reserve_size".to_string(), (tx_extra.0.len() as f64).into()),
  147. ])
  148. .into(),
  149. );
  150. // Get block template from monerod
  151. let mut rep = match self.oneshot_request(req).await {
  152. Ok(v) => v,
  153. Err(e) => {
  154. error!(target: "rpc::monero::get_block_template", "[RPC] {}", e);
  155. return JsonError::new(InternalError, Some(e.to_string()), id).into()
  156. }
  157. };
  158. // Now we have to modify the block template:
  159. // * reserve_size has to be the size of the data we want to put in the block
  160. // * blocktemplate_blob has the reserved bytes, they're in the tx_extra field
  161. // of the coinbase tx, which is then hashed with other txs into Merkle root
  162. // which is what's in the blockhashing_blob
  163. // Deserialize the block template
  164. let mut block_template = monero::consensus::deserialize::<monero::Block>(
  165. &hex::decode(rep["result"]["blocktemplate_blob"].get::<String>().unwrap()).unwrap(),
  166. )
  167. .unwrap();
  168. // Modify the coinbase tx with our additional merge mining data
  169. block_template.miner_tx.prefix.extra = tx_extra;
  170. // Replace the blocktemplate blob
  171. rep["result"]["blocktemplate_blob"] =
  172. JsonValue::String(hex::encode(monero::consensus::serialize(&block_template)));
  173. // Replace the blockhashing blob
  174. rep["result"]["blockhashing_blob"] =
  175. JsonValue::String(hex::encode(&block_template.serialize_hashable()));
  176. // Pass the modified response to the client
  177. JsonResponse::new(rep, id).into()
  178. }
  179. /// Submit a mined block to the network
  180. /// <https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#submit_block>
  181. pub async fn monero_submit_block(&self, id: u16, params: JsonValue) -> JsonResult {
  182. debug!(target: "rpc::monero", "submit_block()");
  183. let Some(params_vec) = params.get::<Vec<JsonValue>>() else {
  184. return JsonError::new(InvalidParams, None, id).into()
  185. };
  186. if params_vec.is_empty() {
  187. return JsonError::new(InvalidParams, None, id).into()
  188. }
  189. // Deserialize the block blob(s) to make sure it's a valid block
  190. for element in params_vec.iter() {
  191. let Some(block_hex) = element.get::<String>() else {
  192. return JsonError::new(InvalidParams, None, id).into()
  193. };
  194. let Ok(block_bytes) = hex::decode(block_hex) else {
  195. return JsonError::new(InvalidParams, None, id).into()
  196. };
  197. let Ok(block) = monero::consensus::deserialize::<monero::Block>(&block_bytes) else {
  198. return JsonError::new(InvalidParams, None, id).into()
  199. };
  200. info!("[RPC] Got submitted Monero block id {}", block.id());
  201. }
  202. // Now when all the blocks submitted are valid, we'll just forward them to
  203. // monerod to submit onto the network.
  204. let req = JsonRequest::new("submit_block", params);
  205. let rep = match self.oneshot_request(req).await {
  206. Ok(v) => v,
  207. Err(e) => {
  208. error!(target: "rpc::monero::submit_block", "[RPC] {}", e);
  209. return JsonError::new(InternalError, Some(e.to_string()), id).into()
  210. }
  211. };
  212. JsonResponse::new(rep, id).into()
  213. }
  214. /*
  215. pub async fn monero_generateblocks(&self, id: u16, params: JsonValue) -> JsonResult {
  216. todo!()
  217. }
  218. pub async fn monero_get_last_block_header(&self, id: u16, params: JsonValue) -> JsonResult {
  219. todo!()
  220. }
  221. pub async fn monero_get_block_header_by_hash(&self, id: u16, params: JsonValue) -> JsonResult {
  222. todo!()
  223. }
  224. pub async fn monero_get_block_header_by_height(
  225. &self,
  226. id: u16,
  227. params: JsonValue,
  228. ) -> JsonResult {
  229. todo!()
  230. }
  231. pub async fn monero_get_block_headers_range(&self, id: u16, params: JsonValue) -> JsonResult {
  232. todo!()
  233. }
  234. pub async fn monero_get_block(&self, id: u16, params: JsonValue) -> JsonResult {
  235. todo!()
  236. }
  237. pub async fn monero_get_connections(&self, id: u16, params: JsonValue) -> JsonResult {
  238. todo!()
  239. }
  240. pub async fn monero_get_info(&self, id: u16, params: JsonValue) -> JsonResult {
  241. todo!()
  242. }
  243. pub async fn monero_hard_fork_info(&self, id: u16, params: JsonValue) -> JsonResult {
  244. todo!()
  245. }
  246. pub async fn monero_set_bans(&self, id: u16, params: JsonValue) -> JsonResult {
  247. todo!()
  248. }
  249. pub async fn monero_get_bans(&self, id: u16, params: JsonValue) -> JsonResult {
  250. todo!()
  251. }
  252. pub async fn monero_banned(&self, id: u16, params: JsonValue) -> JsonResult {
  253. todo!()
  254. }
  255. pub async fn monero_flush_txpool(&self, id: u16, params: JsonValue) -> JsonResult {
  256. todo!()
  257. }
  258. pub async fn monero_get_output_histogram(&self, id: u16, params: JsonValue) -> JsonResult {
  259. todo!()
  260. }
  261. pub async fn monero_get_version(&self, id: u16, params: JsonValue) -> JsonResult {
  262. todo!()
  263. }
  264. pub async fn monero_get_coinbase_tx_sum(&self, id: u16, params: JsonValue) -> JsonResult {
  265. todo!()
  266. }
  267. pub async fn monero_get_fee_estimate(&self, id: u16, params: JsonValue) -> JsonResult {
  268. todo!()
  269. }
  270. pub async fn monero_get_alternate_chains(&self, id: u16, params: JsonValue) -> JsonResult {
  271. todo!()
  272. }
  273. pub async fn monero_relay_tx(&self, id: u16, params: JsonValue) -> JsonResult {
  274. todo!()
  275. }
  276. pub async fn monero_sync_info(&self, id: u16, params: JsonValue) -> JsonResult {
  277. todo!()
  278. }
  279. pub async fn monero_get_txpool_backlog(&self, id: u16, params: JsonValue) -> JsonResult {
  280. todo!()
  281. }
  282. pub async fn monero_get_output_distribution(&self, id: u16, params: JsonValue) -> JsonResult {
  283. todo!()
  284. }
  285. pub async fn monero_get_miner_data(&self, id: u16, params: JsonValue) -> JsonResult {
  286. todo!()
  287. }
  288. pub async fn monero_prune_blockchain(&self, id: u16, params: JsonValue) -> JsonResult {
  289. todo!()
  290. }
  291. pub async fn monero_calc_pow(&self, id: u16, params: JsonValue) -> JsonResult {
  292. todo!()
  293. }
  294. pub async fn monero_flush_cache(&self, id: u16, params: JsonValue) -> JsonResult {
  295. todo!()
  296. }
  297. pub async fn monero_add_aux_pow(&self, id: u16, params: JsonValue) -> JsonResult {
  298. todo!()
  299. }
  300. */
  301. }