mod.rs 19 KB

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