consensus.rs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 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, sync::Arc};
  19. use darkfi::{
  20. blockchain::HeaderHash,
  21. rpc::{jsonrpc::JsonNotification, util::JsonValue},
  22. system::{sleep, 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 log::{error, info};
  32. use crate::{
  33. task::{garbage_collect_task, miner::MinerRewardsRecipientConfig, miner_task, sync_task},
  34. Darkfid,
  35. };
  36. /// Auxiliary structure representing node consensus init task configuration
  37. pub struct ConsensusInitTaskConfig {
  38. pub skip_sync: bool,
  39. pub checkpoint_height: Option<u32>,
  40. pub checkpoint: Option<String>,
  41. pub miner: bool,
  42. pub recipient: Option<String>,
  43. pub spend_hook: Option<String>,
  44. pub user_data: Option<String>,
  45. pub bootstrap: u64,
  46. }
  47. /// Sync the node consensus state and start the corresponding task, based on node type.
  48. pub async fn consensus_init_task(
  49. node: Arc<Darkfid>,
  50. config: ConsensusInitTaskConfig,
  51. ex: Arc<smol::Executor<'static>>,
  52. ) -> Result<()> {
  53. // Check if network is configured to start in the future.
  54. // NOTE: Always configure the network to start in the future when bootstrapping
  55. // or restarting it.
  56. let current = Timestamp::current_time().inner();
  57. if current < config.bootstrap {
  58. let diff = config.bootstrap - current;
  59. info!(target: "darkfid::task::consensus_init_task", "Waiting for network bootstrap: {diff} seconds");
  60. sleep(diff).await;
  61. }
  62. // Generate a new fork to be able to extend
  63. info!(target: "darkfid::task::consensus_init_task", "Generating new empty fork...");
  64. node.validator.consensus.generate_empty_fork().await?;
  65. // Sync blockchain
  66. let checkpoint = if !config.skip_sync {
  67. // Parse configured checkpoint
  68. if config.checkpoint_height.is_some() && config.checkpoint.is_none() {
  69. return Err(Error::ParseFailed("Blockchain configured checkpoint hash missing"))
  70. }
  71. let checkpoint = if let Some(height) = config.checkpoint_height {
  72. Some((height, HeaderHash::from_str(config.checkpoint.as_ref().unwrap())?))
  73. } else {
  74. None
  75. };
  76. sync_task(&node, checkpoint).await?;
  77. checkpoint
  78. } else {
  79. *node.validator.synced.write().await = true;
  80. None
  81. };
  82. // Grab rewards recipient public key(address) if node is a miner,
  83. // along with configured spend hook and user data.
  84. let recipient_config = if config.miner {
  85. if config.recipient.is_none() {
  86. return Err(Error::ParseFailed("Recipient address missing"))
  87. }
  88. let recipient = match PublicKey::from_str(config.recipient.as_ref().unwrap()) {
  89. Ok(address) => address,
  90. Err(_) => return Err(Error::InvalidAddress),
  91. };
  92. let spend_hook = match config.spend_hook {
  93. Some(s) => match FuncId::from_str(&s) {
  94. Ok(s) => Some(s),
  95. Err(_) => return Err(Error::ParseFailed("Invalid spend hook")),
  96. },
  97. None => None,
  98. };
  99. let user_data = match config.user_data {
  100. Some(u) => {
  101. let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
  102. Ok(b) => b,
  103. Err(_) => return Err(Error::ParseFailed("Invalid user data")),
  104. };
  105. match pallas::Base::from_repr(bytes).into() {
  106. Some(v) => Some(v),
  107. None => return Err(Error::ParseFailed("Invalid user data")),
  108. }
  109. }
  110. None => None,
  111. };
  112. Some(MinerRewardsRecipientConfig { recipient, spend_hook, user_data })
  113. } else {
  114. None
  115. };
  116. // Gracefully handle network disconnections
  117. loop {
  118. let result = if config.miner {
  119. miner_task(
  120. node.clone(),
  121. recipient_config.as_ref().unwrap(),
  122. config.skip_sync,
  123. ex.clone(),
  124. )
  125. .await
  126. } else {
  127. replicator_task(node.clone(), ex.clone()).await
  128. };
  129. match result {
  130. Ok(_) => return Ok(()),
  131. Err(Error::NetworkNotConnected) => {
  132. // Sync node again
  133. *node.validator.synced.write().await = false;
  134. node.validator.consensus.purge_forks().await?;
  135. if !config.skip_sync {
  136. sync_task(&node, checkpoint).await?;
  137. } else {
  138. *node.validator.synced.write().await = true;
  139. }
  140. }
  141. Err(e) => return Err(e),
  142. }
  143. }
  144. }
  145. /// Async task to start the consensus task, while monitoring for a network disconnections.
  146. async fn replicator_task(node: Arc<Darkfid>, ex: Arc<smol::Executor<'static>>) -> Result<()> {
  147. // Grab proposals subscriber and subscribe to it
  148. let proposals_sub = node.subscribers.get("proposals").unwrap();
  149. let prop_subscription = proposals_sub.publisher.clone().subscribe().await;
  150. // Subscribe to the network disconnect subscriber
  151. let net_subscription = node.p2p.hosts().subscribe_disconnect().await;
  152. let result = smol::future::or(
  153. monitor_network(&net_subscription),
  154. consensus_task(node, &prop_subscription, ex),
  155. )
  156. .await;
  157. // Terminate the subscriptions
  158. prop_subscription.unsubscribe().await;
  159. net_subscription.unsubscribe().await;
  160. result
  161. }
  162. /// Async task to monitor network disconnections.
  163. async fn monitor_network(subscription: &Subscription<Error>) -> Result<()> {
  164. Err(subscription.receive().await)
  165. }
  166. /// Async task used for listening for new blocks and perform consensus.
  167. async fn consensus_task(
  168. node: Arc<Darkfid>,
  169. subscription: &Subscription<JsonNotification>,
  170. ex: Arc<smol::Executor<'static>>,
  171. ) -> Result<()> {
  172. info!(target: "darkfid::task::consensus_task", "Starting consensus task...");
  173. // Grab blocks subscriber
  174. let block_sub = node.subscribers.get("blocks").unwrap();
  175. // Create the garbage collection task using a dummy task
  176. let gc_task = StoppableTask::new();
  177. gc_task.clone().start(
  178. async { Ok(()) },
  179. |_| async { /* Do nothing */ },
  180. Error::GarbageCollectionTaskStopped,
  181. ex.clone(),
  182. );
  183. loop {
  184. subscription.receive().await;
  185. // Check if we can finalize anything and broadcast them
  186. let finalized = match node.validator.finalization().await {
  187. Ok(f) => f,
  188. Err(e) => {
  189. error!(
  190. target: "darkfid::task::consensus_task",
  191. "Finalization failed: {e}"
  192. );
  193. continue
  194. }
  195. };
  196. if finalized.is_empty() {
  197. continue
  198. }
  199. let mut notif_blocks = Vec::with_capacity(finalized.len());
  200. for block in finalized {
  201. notif_blocks.push(JsonValue::String(base64::encode(&serialize_async(&block).await)));
  202. }
  203. block_sub.notify(JsonValue::Array(notif_blocks)).await;
  204. // Invoke the detached garbage collection task
  205. gc_task.clone().stop().await;
  206. gc_task.clone().start(
  207. garbage_collect_task(node.clone()),
  208. |res| async {
  209. match res {
  210. Ok(()) | Err(Error::GarbageCollectionTaskStopped) => { /* Do nothing */ }
  211. Err(e) => {
  212. error!(target: "darkfid", "Failed starting garbage collection task: {}", e)
  213. }
  214. }
  215. },
  216. Error::GarbageCollectionTaskStopped,
  217. ex.clone(),
  218. );
  219. }
  220. }