consensus.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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::str::FromStr;
  19. use darkfi::{
  20. blockchain::HeaderHash,
  21. rpc::{jsonrpc::JsonNotification, util::JsonValue},
  22. system::{sleep, ExecutorPtr, StoppableTask, Subscription},
  23. util::{encoding::base64, time::Timestamp},
  24. Error, Result,
  25. };
  26. use darkfi_sdk::{
  27. crypto::{FuncId, PublicKey},
  28. pasta::{group::ff::PrimeField, pallas},
  29. };
  30. use darkfi_serial::serialize_async;
  31. use tracing::{error, info};
  32. use crate::{
  33. task::{garbage_collect_task, miner::MinerRewardsRecipientConfig, miner_task, sync_task},
  34. DarkfiNodePtr,
  35. };
  36. /// Auxiliary structure representing node consensus init task configuration
  37. #[derive(Clone)]
  38. pub struct ConsensusInitTaskConfig {
  39. pub skip_sync: bool,
  40. pub checkpoint_height: Option<u32>,
  41. pub checkpoint: Option<String>,
  42. pub miner: bool,
  43. pub recipient: Option<String>,
  44. pub spend_hook: Option<String>,
  45. pub user_data: Option<String>,
  46. pub bootstrap: u64,
  47. }
  48. /// Sync the node consensus state and start the corresponding task, based on node type.
  49. pub async fn consensus_init_task(
  50. node: DarkfiNodePtr,
  51. config: ConsensusInitTaskConfig,
  52. ex: ExecutorPtr,
  53. ) -> Result<()> {
  54. // Check current canonical blockchain for curruption
  55. // TODO: create a restore method reverting each block backwards
  56. // until its healthy again
  57. node.validator.consensus.healthcheck().await?;
  58. // Check if network is configured to start in the future.
  59. // NOTE: Always configure the network to start in the future when bootstrapping
  60. // or restarting it.
  61. let current = Timestamp::current_time().inner();
  62. if current < config.bootstrap {
  63. let diff = config.bootstrap - current;
  64. info!(target: "darkfid::task::consensus_init_task", "Waiting for network bootstrap: {diff} seconds");
  65. sleep(diff).await;
  66. }
  67. // Generate a new fork to be able to extend
  68. info!(target: "darkfid::task::consensus_init_task", "Generating new empty fork...");
  69. node.validator.consensus.generate_empty_fork().await?;
  70. // Sync blockchain
  71. let checkpoint = if !config.skip_sync {
  72. // Parse configured checkpoint
  73. if config.checkpoint_height.is_some() && config.checkpoint.is_none() {
  74. return Err(Error::ParseFailed("Blockchain configured checkpoint hash missing"))
  75. }
  76. let checkpoint = if let Some(height) = config.checkpoint_height {
  77. Some((height, HeaderHash::from_str(config.checkpoint.as_ref().unwrap())?))
  78. } else {
  79. None
  80. };
  81. sync_task(&node, checkpoint).await?;
  82. checkpoint
  83. } else {
  84. *node.validator.synced.write().await = true;
  85. None
  86. };
  87. // Grab rewards recipient public key(address) if node is a miner,
  88. // along with configured spend hook and user data.
  89. let recipient_config = if config.miner {
  90. if config.recipient.is_none() {
  91. return Err(Error::ParseFailed("Recipient address missing"))
  92. }
  93. let recipient = match PublicKey::from_str(config.recipient.as_ref().unwrap()) {
  94. Ok(address) => address,
  95. Err(_) => return Err(Error::InvalidAddress),
  96. };
  97. let spend_hook = match &config.spend_hook {
  98. Some(s) => match FuncId::from_str(s) {
  99. Ok(s) => Some(s),
  100. Err(_) => return Err(Error::ParseFailed("Invalid spend hook")),
  101. },
  102. None => None,
  103. };
  104. let user_data = match &config.user_data {
  105. Some(u) => {
  106. let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
  107. Ok(b) => b,
  108. Err(_) => return Err(Error::ParseFailed("Invalid user data")),
  109. };
  110. match pallas::Base::from_repr(bytes).into() {
  111. Some(v) => Some(v),
  112. None => return Err(Error::ParseFailed("Invalid user data")),
  113. }
  114. }
  115. None => None,
  116. };
  117. Some(MinerRewardsRecipientConfig { recipient, spend_hook, user_data })
  118. } else {
  119. None
  120. };
  121. // Gracefully handle network disconnections
  122. loop {
  123. let result = if config.miner {
  124. miner_task(&node, recipient_config.as_ref().unwrap(), config.skip_sync, &ex).await
  125. } else {
  126. replicator_task(&node, &ex).await
  127. };
  128. match result {
  129. Ok(_) => return Ok(()),
  130. Err(Error::NetworkNotConnected) => {
  131. // Sync node again
  132. *node.validator.synced.write().await = false;
  133. node.validator.consensus.purge_forks().await?;
  134. if !config.skip_sync {
  135. sync_task(&node, checkpoint).await?;
  136. } else {
  137. *node.validator.synced.write().await = true;
  138. }
  139. }
  140. Err(e) => return Err(e),
  141. }
  142. }
  143. }
  144. /// Async task to start the consensus task, while monitoring for a network disconnections.
  145. async fn replicator_task(node: &DarkfiNodePtr, ex: &ExecutorPtr) -> Result<()> {
  146. // Grab proposals subscriber and subscribe to it
  147. let proposals_sub = node.subscribers.get("proposals").unwrap();
  148. let prop_subscription = proposals_sub.publisher.clone().subscribe().await;
  149. // Subscribe to the network disconnect subscriber
  150. let net_subscription = node.p2p_handler.p2p.hosts().subscribe_disconnect().await;
  151. let result = smol::future::or(
  152. monitor_network(&net_subscription),
  153. consensus_task(node, &prop_subscription, ex),
  154. )
  155. .await;
  156. // Terminate the subscriptions
  157. prop_subscription.unsubscribe().await;
  158. net_subscription.unsubscribe().await;
  159. result
  160. }
  161. /// Async task to monitor network disconnections.
  162. async fn monitor_network(subscription: &Subscription<Error>) -> Result<()> {
  163. Err(subscription.receive().await)
  164. }
  165. /// Async task used for listening for new blocks and perform consensus.
  166. async fn consensus_task(
  167. node: &DarkfiNodePtr,
  168. subscription: &Subscription<JsonNotification>,
  169. ex: &ExecutorPtr,
  170. ) -> Result<()> {
  171. info!(target: "darkfid::task::consensus_task", "Starting consensus task...");
  172. // Grab blocks subscriber
  173. let block_sub = node.subscribers.get("blocks").unwrap();
  174. // Create the garbage collection task using a dummy task
  175. let gc_task = StoppableTask::new();
  176. gc_task.clone().start(
  177. async { Ok(()) },
  178. |_| async { /* Do nothing */ },
  179. Error::GarbageCollectionTaskStopped,
  180. ex.clone(),
  181. );
  182. loop {
  183. subscription.receive().await;
  184. // Check if we can confirm anything and broadcast them
  185. let confirmed = match node.validator.confirmation().await {
  186. Ok(f) => f,
  187. Err(e) => {
  188. error!(
  189. target: "darkfid::task::consensus_task",
  190. "Confirmation failed: {e}"
  191. );
  192. continue
  193. }
  194. };
  195. if confirmed.is_empty() {
  196. continue
  197. }
  198. if let Err(e) = clean_mm_blocktemplates(node).await {
  199. error!(target: "darkfid", "Failed cleaning merge mining block templates: {e}")
  200. }
  201. let mut notif_blocks = Vec::with_capacity(confirmed.len());
  202. for block in confirmed {
  203. notif_blocks.push(JsonValue::String(base64::encode(&serialize_async(&block).await)));
  204. }
  205. block_sub.notify(JsonValue::Array(notif_blocks)).await;
  206. // Invoke the detached garbage collection task
  207. gc_task.clone().stop().await;
  208. gc_task.clone().start(
  209. garbage_collect_task(node.clone()),
  210. |res| async {
  211. match res {
  212. Ok(()) | Err(Error::GarbageCollectionTaskStopped) => { /* Do nothing */ }
  213. Err(e) => {
  214. error!(target: "darkfid", "Failed starting garbage collection task: {e}")
  215. }
  216. }
  217. },
  218. Error::GarbageCollectionTaskStopped,
  219. ex.clone(),
  220. );
  221. }
  222. }
  223. /// Auxiliary function to drop merge mining block templates not
  224. /// referencing active forks or last confirmed block.
  225. pub async fn clean_mm_blocktemplates(node: &DarkfiNodePtr) -> Result<()> {
  226. // Grab a lock over node merge mining templates
  227. let mut mm_blocktemplates = node.mm_blocktemplates.lock().await;
  228. // Early return if no merge mining block templates exist
  229. if mm_blocktemplates.is_empty() {
  230. return Ok(())
  231. }
  232. // Grab a lock over node forks
  233. let forks = node.validator.consensus.forks.read().await;
  234. // Grab last confirmed block for checks
  235. let (_, last_confirmed) = node.validator.blockchain.last()?;
  236. // Loop through templates to find which can be dropped
  237. let mut dropped_templates = vec![];
  238. 'outer: for (key, (block, _, _)) in mm_blocktemplates.iter() {
  239. // Loop through all the forks
  240. for fork in forks.iter() {
  241. // Traverse fork proposals sequence in reverse
  242. for p_hash in fork.proposals.iter().rev() {
  243. // Check if job extends this fork
  244. if &block.header.previous == p_hash {
  245. continue 'outer
  246. }
  247. }
  248. }
  249. // Check if it extends last confirmed block
  250. if block.header.previous == last_confirmed {
  251. continue
  252. }
  253. // This job doesn't reference something so we drop it
  254. dropped_templates.push(key.clone());
  255. }
  256. // Drop jobs not referencing active forks or last confirmed block
  257. for key in dropped_templates {
  258. mm_blocktemplates.remove(&key);
  259. }
  260. Ok(())
  261. }