mod.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 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::{
  19. collections::{BTreeSet, HashMap, HashSet},
  20. sync::Arc,
  21. };
  22. use smol::lock::{Mutex, RwLock};
  23. use tinyjson::JsonValue;
  24. use tracing::{error, info};
  25. use darkfi::{
  26. blockchain::BlockInfo,
  27. rpc::{
  28. jsonrpc::JsonSubscriber,
  29. server::{listen_and_serve, RequestHandler},
  30. settings::RpcSettings,
  31. },
  32. system::{ExecutorPtr, StoppableTask, StoppableTaskPtr},
  33. util::encoding::base64,
  34. validator::{consensus::Proposal, Validator, ValidatorPtr},
  35. Error, Result,
  36. };
  37. use darkfi_sdk::{
  38. crypto::{keypair::Network, pasta_prelude::PrimeField},
  39. tx::TransactionHash,
  40. };
  41. use darkfi_serial::serialize_async;
  42. use crate::{
  43. proto::{DarkfidP2pHandlerPtr, ProposalMessage},
  44. rpc::{stratum::StratumRpcHandler, xmr::MmRpcHandler},
  45. DarkfiNode, DarkfiNodePtr,
  46. };
  47. /// Block related structures
  48. pub mod model;
  49. use model::{
  50. generate_next_block_template, BlockTemplate, MinerClient, MinerRewardsRecipientConfig,
  51. PowRewardV1Zk,
  52. };
  53. /// Atomic pointer to the DarkFi node miners registry state.
  54. pub type DarkfiMinersRegistryStatePtr = Arc<RwLock<DarkfiMinersRegistryState>>;
  55. /// DarkFi node miners registry state.
  56. pub struct DarkfiMinersRegistryState {
  57. /// PowRewardV1 ZK data
  58. pub powrewardv1_zk: PowRewardV1Zk,
  59. /// Mining block templates of each wallet config
  60. pub block_templates: HashMap<String, BlockTemplate>,
  61. /// Active native clients mapped to their job information.
  62. /// This client information includes their wallet template key,
  63. /// recipient configuration, current mining job key(job id) and
  64. /// its connection publisher. For native jobs the job key is the
  65. /// hex encoded header hash.
  66. pub jobs: HashMap<String, MinerClient>,
  67. /// Active merge mining jobs mapped to the wallet template they
  68. /// represent. The key(job id) is the the header template hash.
  69. pub mm_jobs: HashMap<String, String>,
  70. }
  71. impl DarkfiMinersRegistryState {
  72. pub async fn new(validator: &ValidatorPtr) -> Result<DarkfiMinersRegistryStatePtr> {
  73. // Generate the PowRewardV1 ZK data
  74. let powrewardv1_zk = PowRewardV1Zk::new(validator).await?;
  75. Ok(Arc::new(RwLock::new(Self {
  76. powrewardv1_zk,
  77. block_templates: HashMap::new(),
  78. jobs: HashMap::new(),
  79. mm_jobs: HashMap::new(),
  80. })))
  81. }
  82. /// Create a registry record for provided wallet config. If the
  83. /// record already exists return its template, otherwise create its
  84. /// current template based on provided validator state.
  85. ///
  86. /// Note: Always remember to purge new trees from the database if
  87. /// not needed.
  88. async fn create_template(
  89. &mut self,
  90. validator: &Validator,
  91. wallet: &String,
  92. config: &MinerRewardsRecipientConfig,
  93. ) -> Result<BlockTemplate> {
  94. // Check if a template already exists for this wallet
  95. if let Some(block_template) = self.block_templates.get(wallet) {
  96. return Ok(block_template.clone())
  97. }
  98. // Grab validator best current fork
  99. let mut extended_fork = validator.best_current_fork().await?;
  100. // Generate the next block template
  101. let block_template = generate_next_block_template(
  102. &mut extended_fork,
  103. config,
  104. &self.powrewardv1_zk.zkbin,
  105. &self.powrewardv1_zk.provingkey,
  106. validator.verify_fees,
  107. )
  108. .await?;
  109. // Create the new registry record
  110. self.block_templates.insert(wallet.clone(), block_template.clone());
  111. // Print the new template wallet information
  112. let recipient_str = format!("{}", config.recipient);
  113. let spend_hook_str = match config.spend_hook {
  114. Some(spend_hook) => format!("{spend_hook}"),
  115. None => String::from("-"),
  116. };
  117. let user_data_str = match config.user_data {
  118. Some(user_data) => bs58::encode(user_data.to_repr()).into_string(),
  119. None => String::from("-"),
  120. };
  121. info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::create_template",
  122. "Created new block template for wallet: address={recipient_str}, spend_hook={spend_hook_str}, user_data={user_data_str}",
  123. );
  124. Ok(block_template)
  125. }
  126. /// Register a new miner and create its job.
  127. pub async fn register_miner(
  128. &mut self,
  129. validator: &Validator,
  130. wallet: &String,
  131. config: &MinerRewardsRecipientConfig,
  132. ) -> Result<(String, String, JsonValue, JsonSubscriber)> {
  133. // Create wallet template
  134. let block_template = self.create_template(validator, wallet, config).await?;
  135. // Grab the hex encoded block hash and create the client job record
  136. let (job_id, job) = block_template.job_notification();
  137. let (client_id, client) = MinerClient::new(wallet, config, &job_id);
  138. let publisher = client.publisher.clone();
  139. self.jobs.insert(client_id.clone(), client);
  140. Ok((client_id, job_id, job, publisher))
  141. }
  142. /// Register a new merge miner and create its job.
  143. pub async fn register_merge_miner(
  144. &mut self,
  145. validator: &Validator,
  146. wallet: &String,
  147. config: &MinerRewardsRecipientConfig,
  148. ) -> Result<(String, BlockTemplate)> {
  149. // Create wallet template
  150. let block_template = self.create_template(validator, wallet, config).await?;
  151. // Grab the block template hash and create the job record
  152. let block_template_hash = block_template.block.header.template_hash().as_string();
  153. self.mm_jobs.insert(block_template_hash.clone(), wallet.clone());
  154. Ok((block_template_hash, block_template))
  155. }
  156. /// Submit provided block to the provided node.
  157. pub async fn submit(
  158. &self,
  159. validator: &mut Validator,
  160. subscribers: &HashMap<&'static str, JsonSubscriber>,
  161. p2p_handler: &DarkfidP2pHandlerPtr,
  162. block: BlockInfo,
  163. ) -> Result<()> {
  164. let proposal = Proposal::new(block);
  165. validator.append_proposal(&proposal, None).await?;
  166. info!(
  167. target: "darkfid::registry::mod::DarkfiMinersRegistry::submit",
  168. "Proposing new block to network",
  169. );
  170. let proposals_sub = subscribers.get("proposals").unwrap();
  171. let enc_prop = JsonValue::String(base64::encode(&serialize_async(&proposal).await));
  172. proposals_sub.notify(vec![enc_prop].into()).await;
  173. info!(
  174. target: "darkfid::registry::mod::DarkfiMinersRegistry::submit",
  175. "Broadcasting new block to network",
  176. );
  177. let message = ProposalMessage(proposal);
  178. p2p_handler.p2p.broadcast(&message).await?;
  179. Ok(())
  180. }
  181. /// Refresh outdated jobs in the registry based on provided
  182. /// validator state.
  183. pub async fn refresh(&mut self, validator: &Validator) -> Result<()> {
  184. // Find inactive native jobs and drop them
  185. let mut dropped_jobs = vec![];
  186. let mut active_templates = HashSet::new();
  187. for (client_id, client) in self.jobs.iter() {
  188. // Clear inactive client publisher subscribers. If none
  189. // exists afterwards, the client is considered inactive so
  190. // we mark it for drop.
  191. if client.publisher.publisher.clear_inactive().await {
  192. dropped_jobs.push(client_id.clone());
  193. continue
  194. }
  195. // Mark client block template as active
  196. active_templates.insert(client.wallet.clone());
  197. }
  198. self.jobs.retain(|client_id, _| !dropped_jobs.contains(client_id));
  199. // Grab validator best current fork and its last proposal for
  200. // checks.
  201. let extended_fork = validator.best_current_fork().await?;
  202. let last_proposal = extended_fork.last_proposal()?.hash;
  203. // Find mm jobs not extending the best current fork and drop
  204. // them.
  205. let mut dropped_mm_jobs = vec![];
  206. for (job_id, wallet) in self.mm_jobs.iter() {
  207. // Grab its wallet template. Its safe to unwrap here since
  208. // we know the job exists.
  209. let block_template = self.block_templates.get(wallet).unwrap();
  210. // Check if it extends current best fork
  211. if block_template.block.header.previous == last_proposal {
  212. active_templates.insert(wallet.clone());
  213. continue
  214. }
  215. // This mm job doesn't extend current best fork so we mark
  216. // it for drop.
  217. dropped_mm_jobs.push(job_id.clone());
  218. }
  219. self.mm_jobs.retain(|job_id, _| !dropped_mm_jobs.contains(job_id));
  220. // Drop inactive templates. Merge miners will create a new
  221. // template and job on next poll.
  222. self.block_templates.retain(|wallet, _| active_templates.contains(wallet));
  223. // Return if no wallets templates exists.
  224. if self.block_templates.is_empty() {
  225. return Ok(())
  226. }
  227. // Iterate over active clients to refresh their jobs, if needed
  228. for (job_id, client) in self.jobs.iter_mut() {
  229. // Grab its wallet template. Its safe to unwrap here since
  230. // we know the job exists.
  231. let block_template = self.block_templates.get_mut(&client.wallet).unwrap();
  232. // Check if it extends current best fork
  233. if block_template.block.header.previous == last_proposal {
  234. continue
  235. }
  236. // Clone the fork so each client generates over a new one
  237. let mut extended_fork = extended_fork.full_clone()?;
  238. // Generate the next block template
  239. let result = generate_next_block_template(
  240. &mut extended_fork,
  241. &client.config,
  242. &self.powrewardv1_zk.zkbin,
  243. &self.powrewardv1_zk.provingkey,
  244. validator.verify_fees,
  245. )
  246. .await;
  247. // Check result
  248. *block_template = match result {
  249. Ok(b) => b,
  250. Err(e) => {
  251. error!(target: "darkfid::registry::mod::DarkfiMinersRegistry::create_template",
  252. "Updating block template for job {job_id} failed: {e}",
  253. );
  254. // Mark block template as not submitted so the
  255. // miner can submit another one and don't get stuck
  256. block_template.submitted = false;
  257. continue;
  258. }
  259. };
  260. // Print the updated template wallet information
  261. let recipient_str = format!("{}", client.config.recipient);
  262. let spend_hook_str = match client.config.spend_hook {
  263. Some(spend_hook) => format!("{spend_hook}"),
  264. None => String::from("-"),
  265. };
  266. let user_data_str = match client.config.user_data {
  267. Some(user_data) => bs58::encode(user_data.to_repr()).into_string(),
  268. None => String::from("-"),
  269. };
  270. info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::create_template",
  271. "Updated block template for wallet: address={recipient_str}, spend_hook={spend_hook_str}, user_data={user_data_str}",
  272. );
  273. // Create the new job notification
  274. let (job, notification) = block_template.job_notification();
  275. // Update the client record
  276. client.job = job;
  277. // Push job notification to subscriber
  278. client.publisher.notify(notification).await;
  279. }
  280. Ok(())
  281. }
  282. /// Auxiliary function to retrieve all current block templates
  283. /// newly opened trees.
  284. pub fn new_trees(&self) -> BTreeSet<String> {
  285. let mut new_trees = BTreeSet::new();
  286. for block_template in self.block_templates.values() {
  287. for new_tree in &block_template.new_trees {
  288. new_trees.insert(new_tree.clone());
  289. }
  290. }
  291. new_trees
  292. }
  293. /// Auxiliary function to retrieve all current block templates
  294. /// transactions hashes.
  295. pub fn proposed_transactions(&self) -> HashSet<TransactionHash> {
  296. let mut proposed_txs = HashSet::new();
  297. for block_template in self.block_templates.values() {
  298. for tx in &block_template.block.txs {
  299. proposed_txs.insert(tx.hash());
  300. }
  301. }
  302. proposed_txs
  303. }
  304. }
  305. /// Atomic pointer to the DarkFi node miners registry.
  306. pub type DarkfiMinersRegistryPtr = Arc<DarkfiMinersRegistry>;
  307. /// DarkFi node miners registry.
  308. pub struct DarkfiMinersRegistry {
  309. /// Blockchain network
  310. pub network: Network,
  311. /// Registry state
  312. pub state: DarkfiMinersRegistryStatePtr,
  313. /// Stratum JSON-RPC background task
  314. stratum_rpc_task: StoppableTaskPtr,
  315. /// Stratum JSON-RPC connection tracker
  316. pub stratum_rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
  317. /// HTTP JSON-RPC background task
  318. mm_rpc_task: StoppableTaskPtr,
  319. /// HTTP JSON-RPC connection tracker
  320. pub mm_rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
  321. }
  322. impl DarkfiMinersRegistry {
  323. /// Initialize a DarkFi node miners registry.
  324. pub async fn init(
  325. network: Network,
  326. validator: &ValidatorPtr,
  327. ) -> Result<DarkfiMinersRegistryPtr> {
  328. info!(
  329. target: "darkfid::registry::mod::DarkfiMinersRegistry::init",
  330. "Initializing a new DarkFi node miners registry..."
  331. );
  332. // Generate the registry state
  333. let state = DarkfiMinersRegistryState::new(validator).await?;
  334. // Generate the stratum JSON-RPC background task and its
  335. // connections tracker.
  336. let stratum_rpc_task = StoppableTask::new();
  337. let stratum_rpc_connections = Mutex::new(HashSet::new());
  338. // Generate the HTTP JSON-RPC background task and its
  339. // connections tracker.
  340. let mm_rpc_task = StoppableTask::new();
  341. let mm_rpc_connections = Mutex::new(HashSet::new());
  342. info!(
  343. target: "darkfid::registry::mod::DarkfiMinersRegistry::init",
  344. "DarkFi node miners registry generated successfully!"
  345. );
  346. Ok(Arc::new(Self {
  347. network,
  348. state,
  349. stratum_rpc_task,
  350. stratum_rpc_connections,
  351. mm_rpc_task,
  352. mm_rpc_connections,
  353. }))
  354. }
  355. /// Start the DarkFi node miners registry for provided DarkFi node
  356. /// instance.
  357. pub fn start(
  358. &self,
  359. executor: &ExecutorPtr,
  360. node: &DarkfiNodePtr,
  361. stratum_rpc_settings: &Option<RpcSettings>,
  362. mm_rpc_settings: &Option<RpcSettings>,
  363. ) -> Result<()> {
  364. info!(
  365. target: "darkfid::registry::mod::DarkfiMinersRegistry::start",
  366. "Starting the DarkFi node miners registry..."
  367. );
  368. // Start the stratum server JSON-RPC task
  369. if let Some(stratum_rpc) = stratum_rpc_settings {
  370. info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::start", "Starting Stratum JSON-RPC server");
  371. let node_ = node.clone();
  372. self.stratum_rpc_task.clone().start(
  373. listen_and_serve::<StratumRpcHandler>(stratum_rpc.clone(), node.clone(), None, executor.clone()),
  374. |res| async move {
  375. match res {
  376. Ok(()) | Err(Error::RpcServerStopped) => <DarkfiNode as RequestHandler<StratumRpcHandler>>::stop_connections(&node_).await,
  377. Err(e) => error!(target: "darkfid::registry::mod::DarkfiMinersRegistry::start", "Failed starting Stratum JSON-RPC server: {e}"),
  378. }
  379. },
  380. Error::RpcServerStopped,
  381. executor.clone(),
  382. );
  383. } else {
  384. // Create a dummy task
  385. self.stratum_rpc_task.clone().start(
  386. async { Ok(()) },
  387. |_| async { /* Do nothing */ },
  388. Error::RpcServerStopped,
  389. executor.clone(),
  390. );
  391. }
  392. // Start the merge mining JSON-RPC task
  393. if let Some(mm_rpc) = mm_rpc_settings {
  394. info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::start", "Starting merge mining JSON-RPC server");
  395. let node_ = node.clone();
  396. self.mm_rpc_task.clone().start(
  397. listen_and_serve::<MmRpcHandler>(mm_rpc.clone(), node.clone(), None, executor.clone()),
  398. |res| async move {
  399. match res {
  400. Ok(()) | Err(Error::RpcServerStopped) => <DarkfiNode as RequestHandler<MmRpcHandler>>::stop_connections(&node_).await,
  401. Err(e) => error!(target: "darkfid::registry::mod::DarkfiMinersRegistry::start", "Failed starting merge mining JSON-RPC server: {e}"),
  402. }
  403. },
  404. Error::RpcServerStopped,
  405. executor.clone(),
  406. );
  407. } else {
  408. // Create a dummy task
  409. self.mm_rpc_task.clone().start(
  410. async { Ok(()) },
  411. |_| async { /* Do nothing */ },
  412. Error::RpcServerStopped,
  413. executor.clone(),
  414. );
  415. }
  416. info!(
  417. target: "darkfid::registry::mod::DarkfiMinersRegistry::start",
  418. "DarkFi node miners registry started successfully!"
  419. );
  420. Ok(())
  421. }
  422. /// Stop the DarkFi node miners registry.
  423. pub async fn stop(&self) {
  424. info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::stop", "Terminating DarkFi node miners registry...");
  425. // Stop the Stratum JSON-RPC task
  426. info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::stop", "Stopping Stratum JSON-RPC server...");
  427. self.stratum_rpc_task.stop().await;
  428. // Stop the merge mining JSON-RPC task
  429. info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::stop", "Stopping merge mining JSON-RPC server...");
  430. self.mm_rpc_task.stop().await;
  431. info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::stop", "DarkFi node miners registry terminated successfully!");
  432. }
  433. }