consensus.rs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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::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_serial::serialize_async;
  27. use tracing::{error, info};
  28. use crate::{
  29. task::{garbage_collect::garbage_collect_task, sync_task},
  30. DarkfiNodePtr,
  31. };
  32. /// Auxiliary structure representing node consensus init task configuration.
  33. #[derive(Clone)]
  34. pub struct ConsensusInitTaskConfig {
  35. /// Skip syncing process and start node right away
  36. pub skip_sync: bool,
  37. /// Optional sync checkpoint height
  38. pub checkpoint_height: Option<u32>,
  39. /// Optional sync checkpoint hash
  40. pub checkpoint: Option<String>,
  41. }
  42. /// Sync the node consensus state and start the corresponding task, based on node type.
  43. pub async fn consensus_init_task(
  44. node: DarkfiNodePtr,
  45. config: ConsensusInitTaskConfig,
  46. ex: ExecutorPtr,
  47. ) -> Result<()> {
  48. // Check current canonical blockchain for curruption
  49. // TODO: create a restore method reverting each block backwards
  50. // until its healthy again
  51. let mut validator = node.validator.write().await;
  52. validator.consensus.healthcheck().await?;
  53. // Check if network genesis is in the future.
  54. let current = Timestamp::current_time().inner();
  55. let genesis = validator.consensus.module.genesis.inner();
  56. if current < genesis {
  57. let diff = genesis - current;
  58. info!(target: "darkfid::task::consensus_init_task", "Waiting for network genesis: {diff} seconds");
  59. sleep(diff).await;
  60. }
  61. // Generate a new fork to be able to extend
  62. info!(target: "darkfid::task::consensus_init_task", "Generating new empty fork...");
  63. validator.consensus.generate_empty_fork().await?;
  64. drop(validator);
  65. // Sync blockchain
  66. let comms_timeout =
  67. node.p2p_handler.p2p.settings().read_arc().await.outbound_connect_timeout_max();
  68. let checkpoint = if !config.skip_sync {
  69. // Parse configured checkpoint
  70. if config.checkpoint_height.is_some() && config.checkpoint.is_none() {
  71. return Err(Error::ParseFailed("Blockchain configured checkpoint hash missing"))
  72. }
  73. let checkpoint = if let Some(height) = config.checkpoint_height {
  74. Some((height, HeaderHash::from_str(config.checkpoint.as_ref().unwrap())?))
  75. } else {
  76. None
  77. };
  78. loop {
  79. match sync_task(&node, checkpoint).await {
  80. Ok(_) => break,
  81. Err(e) => {
  82. error!(target: "darkfid::task::consensus_task", "Sync task failed: {e}");
  83. info!(target: "darkfid::task::consensus_task", "Sleeping for {comms_timeout} before retry...");
  84. sleep(comms_timeout).await;
  85. }
  86. }
  87. }
  88. checkpoint
  89. } else {
  90. node.validator.write().await.synced = true;
  91. None
  92. };
  93. // Gracefully handle network disconnections
  94. loop {
  95. match listen_to_network(&node, &ex).await {
  96. Ok(_) => return Ok(()),
  97. Err(Error::NetworkNotConnected) => {
  98. // Sync node again
  99. node.validator.write().await.synced = false;
  100. if !config.skip_sync {
  101. loop {
  102. match sync_task(&node, checkpoint).await {
  103. Ok(_) => break,
  104. Err(e) => {
  105. error!(target: "darkfid::task::consensus_task", "Sync task failed: {e}");
  106. info!(target: "darkfid::task::consensus_task", "Sleeping for {comms_timeout} before retry...");
  107. sleep(comms_timeout).await;
  108. }
  109. }
  110. }
  111. } else {
  112. node.validator.write().await.synced = true;
  113. }
  114. }
  115. Err(e) => return Err(e),
  116. }
  117. }
  118. }
  119. /// Async task to start the consensus task, while monitoring for a network disconnections.
  120. async fn listen_to_network(node: &DarkfiNodePtr, ex: &ExecutorPtr) -> Result<()> {
  121. // Grab proposals subscriber and subscribe to it
  122. let proposals_sub = node.subscribers.get("proposals").unwrap();
  123. let prop_subscription = proposals_sub.publisher.clone().subscribe().await;
  124. // Subscribe to the network disconnect subscriber
  125. let net_subscription = node.p2p_handler.p2p.hosts().subscribe_disconnect().await;
  126. let result = smol::future::or(
  127. monitor_network(&net_subscription),
  128. consensus_task(node, &prop_subscription, ex),
  129. )
  130. .await;
  131. // Terminate the subscriptions
  132. prop_subscription.unsubscribe().await;
  133. net_subscription.unsubscribe().await;
  134. result
  135. }
  136. /// Async task to monitor network disconnections.
  137. async fn monitor_network(subscription: &Subscription<Error>) -> Result<()> {
  138. Err(subscription.receive().await)
  139. }
  140. /// Async task used for listening for new blocks and perform consensus.
  141. async fn consensus_task(
  142. node: &DarkfiNodePtr,
  143. subscription: &Subscription<JsonNotification>,
  144. ex: &ExecutorPtr,
  145. ) -> Result<()> {
  146. info!(target: "darkfid::task::consensus_task", "Starting consensus task...");
  147. // Grab blocks subscriber
  148. let block_sub = node.subscribers.get("blocks").unwrap();
  149. // Create the garbage collection task using a dummy task
  150. let gc_task = StoppableTask::new();
  151. gc_task.clone().start(
  152. async { Ok(()) },
  153. |_| async { /* Do nothing */ },
  154. Error::GarbageCollectionTaskStopped,
  155. ex.clone(),
  156. );
  157. loop {
  158. subscription.receive().await;
  159. // Check if we can confirm anything and broadcast them
  160. let mut validator = node.validator.write().await;
  161. let confirmed = match validator.confirmation().await {
  162. Ok(f) => f,
  163. Err(e) => {
  164. error!(
  165. target: "darkfid::task::consensus_task",
  166. "Confirmation failed: {e}"
  167. );
  168. continue
  169. }
  170. };
  171. // Refresh mining registry
  172. let mut registry = node.registry.state.write().await;
  173. if let Err(e) = registry.refresh(&validator).await {
  174. error!(target: "darkfid", "Failed refreshing mining block templates: {e}")
  175. }
  176. if confirmed.is_empty() {
  177. continue
  178. }
  179. // Purge all unreferenced contract trees from the database
  180. if let Err(e) =
  181. validator.consensus.purge_unreferenced_trees(&mut registry.new_trees()).await
  182. {
  183. error!(target: "darkfid::task::garbage_collect::purge_unreferenced_trees", "Purging unreferenced contract trees from the database failed: {e}");
  184. }
  185. let mut notif_blocks = Vec::with_capacity(confirmed.len());
  186. for block in confirmed {
  187. notif_blocks.push(JsonValue::String(base64::encode(&serialize_async(&block).await)));
  188. }
  189. block_sub.notify(JsonValue::Array(notif_blocks)).await;
  190. // Invoke the detached garbage collection task
  191. gc_task.clone().stop().await;
  192. gc_task.clone().start(
  193. garbage_collect_task(node.clone()),
  194. |res| async {
  195. match res {
  196. Ok(()) | Err(Error::GarbageCollectionTaskStopped) => { /* Do nothing */ }
  197. Err(e) => {
  198. error!(target: "darkfid", "Failed starting garbage collection task: {e}")
  199. }
  200. }
  201. },
  202. Error::GarbageCollectionTaskStopped,
  203. ex.clone(),
  204. );
  205. }
  206. }