consensus.rs 8.2 KB

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