lib.rs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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::{collections::HashMap, sync::Arc};
  19. use smol::{
  20. channel::{Receiver, Sender},
  21. lock::RwLock,
  22. };
  23. use tracing::{debug, error, info};
  24. use url::Url;
  25. use darkfi::{
  26. rpc::util::JsonValue,
  27. system::{sleep, ExecutorPtr, StoppableTask, StoppableTaskPtr},
  28. Error,
  29. };
  30. use darkfi_sdk::crypto::Keypair;
  31. /// Miner benchmarking related methods
  32. pub mod benchmark;
  33. /// darkfid JSON-RPC related methods
  34. mod rpc;
  35. use rpc::{polling_task, DarkfidRpcClient};
  36. /// Auxiliary structure representing miner node configuration.
  37. pub struct MinerNodeConfig {
  38. /// PoW miner number of threads to use
  39. threads: usize,
  40. /// Polling rate to ask darkfid for mining jobs
  41. polling_rate: u64,
  42. /// Stop mining at this height (0 mines forever)
  43. stop_at_height: u32,
  44. /// Wallet mining configuration to receive mining rewards
  45. wallet_config: HashMap<String, JsonValue>,
  46. }
  47. impl Default for MinerNodeConfig {
  48. fn default() -> Self {
  49. Self::new(
  50. 1,
  51. 5,
  52. 0,
  53. HashMap::from([(
  54. String::from("recipient"),
  55. JsonValue::String(Keypair::default().public.to_string()),
  56. )]),
  57. )
  58. }
  59. }
  60. impl MinerNodeConfig {
  61. pub fn new(
  62. threads: usize,
  63. polling_rate: u64,
  64. stop_at_height: u32,
  65. wallet_config: HashMap<String, JsonValue>,
  66. ) -> Self {
  67. Self { threads, polling_rate, stop_at_height, wallet_config }
  68. }
  69. }
  70. /// Atomic pointer to the DarkFi mining node
  71. pub type MinerNodePtr = Arc<MinerNode>;
  72. /// Structure representing a DarkFi mining node
  73. pub struct MinerNode {
  74. /// Node configuration
  75. config: MinerNodeConfig,
  76. /// Sender and receiver to stop mining threads
  77. mining_channel: (Sender<()>, Receiver<()>),
  78. /// Sender and receiver to stop background threads
  79. background_channel: (Sender<()>, Receiver<()>),
  80. /// JSON-RPC client to execute requests to darkfid daemon
  81. rpc_client: RwLock<DarkfidRpcClient>,
  82. }
  83. impl MinerNode {
  84. pub async fn new(config: MinerNodeConfig, endpoint: Url, ex: &ExecutorPtr) -> MinerNodePtr {
  85. // Initialize the smol channels to send signal between the threads
  86. let mining_channel = smol::channel::bounded(1);
  87. let background_channel = smol::channel::bounded(1);
  88. // Initialize JSON-RPC client
  89. let rpc_client = RwLock::new(DarkfidRpcClient::new(endpoint, ex.clone()).await);
  90. Arc::new(Self { config, mining_channel, background_channel, rpc_client })
  91. }
  92. /// Auxiliary function to abort all pending tasks.
  93. pub async fn abort(&self) {
  94. self.abort_mining().await;
  95. self.abort_background().await;
  96. }
  97. /// Auxiliary function to abort pending mining task.
  98. pub async fn abort_mining(&self) {
  99. Self::abort_task(&self.mining_channel.0, &self.mining_channel.1, "mining").await;
  100. }
  101. /// Auxiliary function to abort pending background Randomx VMs
  102. /// generation task.
  103. pub async fn abort_background(&self) {
  104. Self::abort_task(&self.background_channel.0, &self.background_channel.1, "VMs generation")
  105. .await;
  106. }
  107. /// Auxiliary function to abort pending task by signaling provided
  108. /// channels.
  109. async fn abort_task(sender: &Sender<()>, stop_signal: &Receiver<()>, task: &str) {
  110. // Check if a pending task is being processed
  111. debug!(target: "minerd::abort_task", "Checking if a pending {task} task is being processed...");
  112. if stop_signal.receiver_count() <= 1 {
  113. debug!(target: "minerd::abort_task", "No pending {task} task!");
  114. return
  115. }
  116. info!(target: "minerd::abort_task", "Pending {task} is in progress, sending stop signal...");
  117. // Send stop signal to worker
  118. if let Err(e) = sender.try_send(()) {
  119. error!(target: "minerd::abort_task", "Failed to stop pending {task} task: {e}");
  120. return
  121. }
  122. // Wait for worker to terminate
  123. info!(target: "minerd::abort_task", "Waiting for {task} task to terminate...");
  124. while stop_signal.receiver_count() > 1 {
  125. sleep(1).await;
  126. }
  127. info!(target: "minerd::abort_task", "Pending {task} task terminated!");
  128. // Consume channel item so its empty again
  129. if let Err(e) = stop_signal.try_recv() {
  130. error!(target: "minerd::abort_task", "Failed to cleanup stop signal channel: {e}");
  131. }
  132. }
  133. }
  134. /// Atomic pointer to the DarkFi mining daemon
  135. pub type MinerdPtr = Arc<Minerd>;
  136. /// Structure representing a DarkFi mining daemon
  137. pub struct Minerd {
  138. /// Miner node instance conducting the mining operations
  139. node: MinerNodePtr,
  140. /// Miner darkfid polling background task
  141. polling_task: StoppableTaskPtr,
  142. }
  143. impl Minerd {
  144. /// Initialize a DarkFi mining daemon.
  145. ///
  146. /// Generate a new `MinerNode` and a new task to handle the darkfid
  147. /// polling.
  148. pub async fn init(config: MinerNodeConfig, endpoint: Url, ex: &ExecutorPtr) -> MinerdPtr {
  149. info!(target: "minerd::Minerd::init", "Initializing a new mining daemon...");
  150. // Generate the node
  151. let node = MinerNode::new(config, endpoint, ex).await;
  152. // Generate the polling task
  153. let polling_task = StoppableTask::new();
  154. info!(target: "minerd::Minerd::init", "Mining daemon initialized successfully!");
  155. Arc::new(Self { node, polling_task })
  156. }
  157. /// Start the DarkFi mining daemon in the given executor.
  158. pub fn start(&self, ex: &ExecutorPtr) {
  159. info!(target: "minerd::Minerd::start", "Starting mining daemon...");
  160. // Start the polling task
  161. self.polling_task.clone().start(
  162. polling_task(self.node.clone(), ex.clone()),
  163. |res| async {
  164. match res {
  165. Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
  166. Err(e) => {
  167. error!(target: "minerd::Minerd::start", "Failed starting polling task: {e}")
  168. }
  169. }
  170. },
  171. Error::DetachedTaskStopped,
  172. ex.clone(),
  173. );
  174. info!(target: "minerd::Minerd::start", "Mining daemon started successfully!");
  175. }
  176. /// Stop the DarkFi mining daemon.
  177. pub async fn stop(&self) {
  178. info!(target: "minerd::Minerd::stop", "Terminating mining daemon...");
  179. // Stop the mining node
  180. info!(target: "minerd::Minerd::stop", "Stopping miner background tasks...");
  181. self.node.abort().await;
  182. // Stop the polling task
  183. info!(target: "minerd::Minerd::stop", "Stopping polling task...");
  184. self.polling_task.stop().await;
  185. // Close the JSON-RPC client
  186. info!(target: "minerd::Minerd::stop", "Stopping JSON-RPC client...");
  187. self.node.stop_rpc_client().await;
  188. info!(target: "minerd::Minerd::stop", "Mining daemon terminated successfully!");
  189. }
  190. }
  191. #[cfg(test)]
  192. use {
  193. darkfi::util::logger::{setup_test_logger, Level},
  194. tracing::warn,
  195. };
  196. #[test]
  197. /// Test the programmatic control of `Minerd`.
  198. ///
  199. /// First we initialize a daemon, start it and then perform
  200. /// couple of restarts to verify everything works as expected.
  201. fn minerd_programmatic_control() {
  202. // We check this error so we can execute same file tests in parallel,
  203. // otherwise second one fails to init logger here.
  204. if setup_test_logger(
  205. &[],
  206. false,
  207. Level::Info,
  208. //Level::Verbose,
  209. //Level::Debug,
  210. //Level::Trace,
  211. )
  212. .is_err()
  213. {
  214. warn!(target: "minerd_programmatic_control", "Logger already initialized");
  215. }
  216. // Create an executor and communication signals
  217. let ex = Arc::new(smol::Executor::new());
  218. let (signal, shutdown) = smol::channel::unbounded::<()>();
  219. easy_parallel::Parallel::new().each(0..1, |_| smol::block_on(ex.run(shutdown.recv()))).finish(
  220. || {
  221. smol::block_on(async {
  222. // Initialize a daemon
  223. let daemon = Minerd::init(
  224. MinerNodeConfig::default(),
  225. Url::parse("tcp://127.0.0.1:12345").unwrap(),
  226. &ex,
  227. )
  228. .await;
  229. // Start it
  230. daemon.start(&ex);
  231. // Stop it
  232. daemon.stop().await;
  233. // Start it again
  234. daemon.start(&ex);
  235. // Stop it
  236. daemon.stop().await;
  237. // Shutdown entirely
  238. drop(signal);
  239. })
  240. },
  241. );
  242. }