rpc.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 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::sync::Arc;
  19. use num_bigint::BigUint;
  20. use randomx::RandomXVM;
  21. use tracing::{debug, error, info};
  22. use url::Url;
  23. use darkfi::{
  24. blockchain::{Header, HeaderHash},
  25. rpc::{client::RpcClient, jsonrpc::JsonRequest, util::JsonValue},
  26. system::{sleep, ExecutorPtr, StoppableTask},
  27. util::encoding::base64,
  28. validator::pow::{generate_mining_vms, get_mining_flags, mine_block},
  29. Error, Result,
  30. };
  31. use darkfi_serial::deserialize_async;
  32. use crate::{MinerNode, MinerNodePtr};
  33. /// Structure to hold a JSON-RPC client and its config,
  34. /// so we can recreate it in case of an error.
  35. pub struct DarkfidRpcClient {
  36. endpoint: Url,
  37. ex: ExecutorPtr,
  38. client: Option<RpcClient>,
  39. }
  40. impl DarkfidRpcClient {
  41. pub async fn new(endpoint: Url, ex: ExecutorPtr) -> Self {
  42. let client = RpcClient::new(endpoint.clone(), ex.clone()).await.ok();
  43. Self { endpoint, ex, client }
  44. }
  45. /// Stop the client.
  46. pub async fn stop(&self) {
  47. if let Some(ref client) = self.client {
  48. client.stop().await
  49. }
  50. }
  51. }
  52. impl MinerNode {
  53. /// Auxiliary function to request configured darkfid daemon for
  54. /// its current mining RandomX key.
  55. async fn randomx_key(&self) -> Result<HeaderHash> {
  56. loop {
  57. debug!(target: "minerd::rpc::randomx_key", "Executing mining RandomX key request to darkfid...");
  58. let params = match self
  59. .darkfid_daemon_request(
  60. "miner.get_current_mining_randomx_key",
  61. &JsonValue::Array(vec![]),
  62. )
  63. .await
  64. {
  65. Ok(params) => params,
  66. Err(e) => {
  67. error!(target: "minerd::rpc::randomx_key", "darkfid request failed: {e}");
  68. self.sleep().await?;
  69. continue
  70. }
  71. };
  72. debug!(target: "minerd::rpc::randomx_key", "Got reply: {params:?}");
  73. // Verify response parameters
  74. if !params.is_array() {
  75. error!(target: "minerd::rpc::randomx_key", "darkfid responded with invalid params: {params:?}");
  76. self.sleep().await?;
  77. continue
  78. }
  79. let params = params.get::<Vec<JsonValue>>().unwrap();
  80. if params.is_empty() {
  81. debug!(target: "minerd::rpc::randomx_key", "darkfid response is empty");
  82. self.sleep().await?;
  83. continue
  84. }
  85. if params.len() != 1 || !params[0].is_string() {
  86. error!(target: "minerd::rpc::randomx_key", "darkfid responded with invalid params: {params:?}");
  87. self.sleep().await?;
  88. continue
  89. }
  90. // Parse parameters
  91. let Some(randomx_key_bytes) = base64::decode(params[0].get::<String>().unwrap()) else {
  92. error!(target: "minerd::rpc::randomx_key", "Failed to parse RandomX key bytes");
  93. self.sleep().await?;
  94. continue
  95. };
  96. let Ok(randomx_key) = deserialize_async::<HeaderHash>(&randomx_key_bytes).await else {
  97. error!(target: "minerd::rpc::randomx_key", "Failed to parse RandomX key");
  98. self.sleep().await?;
  99. continue
  100. };
  101. return Ok(randomx_key)
  102. }
  103. }
  104. /// Auxiliary function to poll configured darkfid daemon for a new
  105. /// mining job.
  106. async fn poll(&self, header: &str) -> Result<(HeaderHash, BigUint, Header)> {
  107. loop {
  108. debug!(target: "minerd::rpc::poll", "Executing poll request to darkfid...");
  109. let mut request_params = self.config.wallet_config.clone();
  110. request_params.insert(String::from("header"), JsonValue::String(String::from(header)));
  111. let params = match self
  112. .darkfid_daemon_request("miner.get_header", &JsonValue::from(request_params))
  113. .await
  114. {
  115. Ok(params) => params,
  116. Err(e) => {
  117. error!(target: "minerd::rpc::poll", "darkfid poll failed: {e}");
  118. self.sleep().await?;
  119. continue
  120. }
  121. };
  122. debug!(target: "minerd::rpc::poll", "Got reply: {params:?}");
  123. // Verify response parameters
  124. if !params.is_array() {
  125. error!(target: "minerd::rpc::poll", "darkfid responded with invalid params: {params:?}");
  126. self.sleep().await?;
  127. continue
  128. }
  129. let params = params.get::<Vec<JsonValue>>().unwrap();
  130. if params.is_empty() {
  131. debug!(target: "minerd::rpc::poll", "darkfid response is empty");
  132. self.sleep().await?;
  133. continue
  134. }
  135. if params.len() != 3 ||
  136. !params[0].is_string() ||
  137. !params[1].is_string() ||
  138. !params[2].is_string()
  139. {
  140. error!(target: "minerd::rpc::poll", "darkfid responded with invalid params: {params:?}");
  141. self.sleep().await?;
  142. continue
  143. }
  144. // Parse parameters
  145. let Some(randomx_key_bytes) = base64::decode(params[0].get::<String>().unwrap()) else {
  146. error!(target: "minerd::rpc::poll", "Failed to parse RandomX key bytes");
  147. self.sleep().await?;
  148. continue
  149. };
  150. let Ok(randomx_key) = deserialize_async::<HeaderHash>(&randomx_key_bytes).await else {
  151. error!(target: "minerd::rpc::poll", "Failed to parse RandomX key");
  152. self.sleep().await?;
  153. continue
  154. };
  155. let Some(target_bytes) = base64::decode(params[1].get::<String>().unwrap()) else {
  156. error!(target: "minerd::rpc::poll", "Failed to parse target bytes");
  157. self.sleep().await?;
  158. continue
  159. };
  160. let target = BigUint::from_bytes_le(&target_bytes);
  161. let Some(header_bytes) = base64::decode(params[2].get::<String>().unwrap()) else {
  162. error!(target: "minerd::rpc::poll", "Failed to parse header bytes");
  163. self.sleep().await?;
  164. continue
  165. };
  166. let Ok(header) = deserialize_async::<Header>(&header_bytes).await else {
  167. error!(target: "minerd::rpc::poll", "Failed to parse header");
  168. self.sleep().await?;
  169. continue
  170. };
  171. return Ok((randomx_key, target, header))
  172. }
  173. }
  174. /// Auxiliary function to submit a mining solution to configured
  175. /// darkfid daemon.
  176. async fn submit(&self, nonce: f64) -> String {
  177. debug!(target: "minerd::rpc::submit", "Executing submit request to darkfid...");
  178. let mut request_params = self.config.wallet_config.clone();
  179. request_params.insert(String::from("nonce"), JsonValue::Number(nonce));
  180. let result = match self
  181. .darkfid_daemon_request("miner.submit_solution", &JsonValue::from(request_params))
  182. .await
  183. {
  184. Ok(result) => result,
  185. Err(e) => return format!("darkfid submit failed: {e}"),
  186. };
  187. debug!(target: "minerd::rpc::submit", "Got reply: {result:?}");
  188. // Parse response
  189. match result.get::<String>() {
  190. Some(result) => result.clone(),
  191. None => format!("darkfid responded with invalid params: {result:?}"),
  192. }
  193. }
  194. /// Auxiliary function to execute a request towards the configured
  195. /// darkfid daemon JSON-RPC endpoint.
  196. async fn darkfid_daemon_request(&self, method: &str, params: &JsonValue) -> Result<JsonValue> {
  197. let mut lock = self.rpc_client.write().await;
  198. let req = JsonRequest::new(method, params.clone());
  199. // Check the client is initialized
  200. if let Some(ref client) = lock.client {
  201. // Execute request
  202. if let Ok(rep) = client.request(req.clone()).await {
  203. drop(lock);
  204. return Ok(rep);
  205. }
  206. }
  207. // Reset the rpc client in case of an error and try again
  208. let client = RpcClient::new(lock.endpoint.clone(), lock.ex.clone()).await?;
  209. let rep = client.request(req).await?;
  210. lock.client = Some(client);
  211. drop(lock);
  212. Ok(rep)
  213. }
  214. /// Auxiliary function to stop current JSON-RPC client, if its
  215. /// initialized.
  216. pub async fn stop_rpc_client(&self) {
  217. self.rpc_client.read().await.stop().await;
  218. }
  219. /// Auxiliary function to sleep for configured polling rate time.
  220. async fn sleep(&self) -> Result<()> {
  221. // Check if stop signal is received
  222. if self.mining_channel.1.is_full() {
  223. debug!(target: "minerd::rpc::sleep", "Stop signal received, exiting polling task");
  224. return Err(Error::DetachedTaskStopped);
  225. }
  226. debug!(target: "minerd::rpc::sleep", "Sleeping for {} until next poll...", self.config.polling_rate);
  227. sleep(self.config.polling_rate).await;
  228. Ok(())
  229. }
  230. }
  231. /// Async task to poll darkfid for new mining jobs. Once a new job is
  232. /// received, spawns a mining task in the background.
  233. pub async fn polling_task(miner: MinerNodePtr, ex: ExecutorPtr) -> Result<()> {
  234. // Cache current RandomX key and its VMs
  235. let mut current_randomx_key = miner.randomx_key().await?;
  236. info!(target: "minerd::rpc::mining_task", "Initializing {} mining VMs for key: {current_randomx_key}", miner.config.threads);
  237. let mining_flags =
  238. get_mining_flags(miner.config.fast_mode, miner.config.large_pages, miner.config.secure);
  239. let mut _current_vms = Arc::new(generate_mining_vms(
  240. mining_flags,
  241. &current_randomx_key,
  242. miner.config.threads,
  243. &miner.mining_channel.1.clone(),
  244. )?);
  245. // Use the dummy Header on first poll
  246. let mut current_job = current_randomx_key.to_string();
  247. loop {
  248. // Poll darkfid for a mining job
  249. let (randomx_key, target, header) = miner.poll(&current_job).await?;
  250. let header_hash = header.hash().to_string();
  251. debug!(target: "minerd::rpc::polling_task", "Received job:");
  252. debug!(target: "minerd::rpc::polling_task", "\tRandomX key - {randomx_key}");
  253. debug!(target: "minerd::rpc::polling_task", "\tTarget - {target}");
  254. debug!(target: "minerd::rpc::polling_task", "\tHeader - {header_hash}");
  255. // Check if we are already processing this job
  256. if header_hash == current_job {
  257. debug!(target: "minerd::rpc::polling_task", "Already received job, skipping...");
  258. miner.sleep().await?;
  259. continue
  260. }
  261. // Check if we reached the stop height
  262. if miner.config.stop_at_height > 0 && header.height > miner.config.stop_at_height {
  263. info!(target: "minerd::rpc::polling_task", "Reached requested mining height: {}", miner.config.stop_at_height);
  264. info!(target: "minerd::rpc::polling_task", "Daemon can be safely terminated now!");
  265. break
  266. }
  267. info!(target: "minerd::rpc::polling_task", "Received new job to mine block header {header_hash} with key {randomx_key} for target: 0x{target:064x}");
  268. // Abord pending mining job
  269. miner.abort_mining().await;
  270. // Check if the current RandomX key has changed
  271. if randomx_key != current_randomx_key {
  272. // Drop previous VMs
  273. _current_vms = Arc::new(vec![]);
  274. // Generate the RandomX VMs for the key
  275. info!(target: "minerd::rpc::mining_task", "Initializing {} mining VMs for key: {randomx_key}", miner.config.threads);
  276. _current_vms = Arc::new(generate_mining_vms(
  277. mining_flags,
  278. &randomx_key,
  279. miner.config.threads,
  280. &miner.mining_channel.1.clone(),
  281. )?);
  282. current_randomx_key = randomx_key;
  283. }
  284. // Detach mining task
  285. StoppableTask::new().start(
  286. mining_task(miner.clone(), _current_vms.clone(), target, header),
  287. |res| async {
  288. match res {
  289. Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
  290. Err(e) => error!(target: "minerd::rpc::polling_task", "Failed starting mining task: {e}"),
  291. }
  292. },
  293. Error::DetachedTaskStopped,
  294. ex.clone(),
  295. );
  296. // Update current job
  297. current_job = header_hash;
  298. // Sleep until next poll
  299. miner.sleep().await?;
  300. }
  301. Ok(())
  302. }
  303. /// Async task to mine provided header and submit solution to darkfid.
  304. async fn mining_task(
  305. miner: MinerNodePtr,
  306. vms: Arc<Vec<Arc<RandomXVM>>>,
  307. target: BigUint,
  308. mut header: Header,
  309. ) -> Result<()> {
  310. // Mine provided block header
  311. let header_hash = header.hash().to_string();
  312. info!(target: "minerd::rpc::mining_task", "Mining block header {header_hash} for target: 0x{target:064x}");
  313. if let Err(e) = mine_block(&vms, &target, &mut header, &miner.mining_channel.1.clone()) {
  314. error!(target: "minerd::rpc::mining_task", "Failed mining block header {header_hash} with error: {e}");
  315. return Err(Error::DetachedTaskStopped)
  316. }
  317. info!(target: "minerd::rpc::mining_task", "Mined block header {header_hash} with nonce: {}", header.nonce);
  318. info!(target: "minerd::rpc::mining_task", "Mined block header hash: {}", header.hash());
  319. // Submit solution to darkfid
  320. info!(target: "minerd::rpc::submit", "Submitting solution to darkfid...");
  321. let result = miner.submit(header.nonce as f64).await;
  322. info!(target: "minerd::rpc::submit", "Submition result: {result}");
  323. Ok(())
  324. }