proposal.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 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 async_std::sync::Arc;
  19. use log::{debug, error, info, warn};
  20. use super::consensus_sync_task;
  21. use crate::{
  22. consensus::{constants, ValidatorStatePtr},
  23. net::P2pPtr,
  24. util::{async_util::sleep, time::Timestamp},
  25. };
  26. /// async task used for participating in the consensus protocol
  27. pub async fn proposal_task(
  28. consensus_p2p: P2pPtr,
  29. sync_p2p: P2pPtr,
  30. state: ValidatorStatePtr,
  31. ex: Arc<smol::Executor<'_>>,
  32. ) {
  33. // Check if network is configured to start in the future,
  34. // otherwise wait for current or next slot finalization period for optimal sync conditions.
  35. // NOTE: Network beign configured to start in the future should always be the case
  36. // when bootstrapping or restarting a network.
  37. let current_ts = Timestamp::current_time();
  38. let bootstrap_ts = state.read().await.consensus.bootstrap_ts;
  39. if current_ts < bootstrap_ts {
  40. let diff = bootstrap_ts.0 - current_ts.0;
  41. info!(target: "consensus::proposal", "consensus: Waiting for network bootstrap: {} seconds", diff);
  42. sleep(diff).await;
  43. } else {
  44. let mut sleep_time = state.read().await.consensus.time_keeper.next_n_slot_start(1);
  45. let sync_offset = constants::FINAL_SYNC_DUR;
  46. loop {
  47. if sleep_time > sync_offset {
  48. sleep_time -= sync_offset;
  49. break
  50. }
  51. info!(target: "consensus::proposal", "consensus: Waiting for next slot ({:?})", sleep_time);
  52. sleep(sleep_time).await;
  53. sleep_time = state.read().await.consensus.time_keeper.next_n_slot_start(1);
  54. }
  55. info!(target: "consensus::proposal", "consensus: Waiting for finalization sync period ({:?})", sleep_time);
  56. sleep(sleep_time).await;
  57. }
  58. let mut retries = 0;
  59. // Sync loop
  60. loop {
  61. // Resetting consensus state, so node can still follow the finalized blocks by
  62. // the sync p2p network/protocols
  63. state.write().await.consensus.reset();
  64. // Checking sync retries
  65. if retries > constants::SYNC_MAX_RETRIES {
  66. error!(target: "consensus::proposal", "consensus: Node reached max sync retries ({}) due to not being able to follow up with consensus processing.", constants::SYNC_MAX_RETRIES);
  67. warn!(target: "consensus::proposal", "consensus: Terminating consensus participation.");
  68. break
  69. }
  70. // Node syncs its consensus state
  71. match consensus_sync_task(consensus_p2p.clone(), state.clone()).await {
  72. Ok(p) => {
  73. // Check if node is not connected to other nodes and can
  74. // start proposing immediately.
  75. if p {
  76. info!(target: "consensus::proposal", "consensus: Node can start proposing!");
  77. state.write().await.consensus.proposing = p;
  78. }
  79. }
  80. Err(e) => {
  81. error!(target: "consensus::proposal", "consensus: Failed syncing consensus state: {}. Quitting consensus.", e);
  82. // TODO: Perhaps notify over a channel in order to
  83. // stop consensus p2p protocols.
  84. return
  85. }
  86. };
  87. // Node modifies its participating slot to next.
  88. match state.write().await.consensus.set_participating() {
  89. Ok(()) => {
  90. info!(target: "consensus::proposal", "consensus: Node will start participating in the next slot")
  91. }
  92. Err(e) => {
  93. error!(target: "consensus::proposal", "consensus: Failed to set participation slot: {}", e)
  94. }
  95. }
  96. // Record epoch we start the consensus loop
  97. let start_epoch = state.read().await.consensus.time_keeper.current_epoch();
  98. // Start executing consensus
  99. consensus_loop(consensus_p2p.clone(), sync_p2p.clone(), state.clone(), ex.clone()).await;
  100. // Reset retries counter if more epochs have passed than sync retries duration
  101. let break_epoch = state.read().await.consensus.time_keeper.current_epoch();
  102. if (break_epoch - start_epoch) > constants::SYNC_RETRIES_DURATION {
  103. retries = 0;
  104. }
  105. // Increase retries count on consensus loop break
  106. retries += 1;
  107. }
  108. }
  109. /// Consensus protocol loop
  110. async fn consensus_loop(
  111. consensus_p2p: P2pPtr,
  112. sync_p2p: P2pPtr,
  113. state: ValidatorStatePtr,
  114. ex: Arc<smol::Executor<'_>>,
  115. ) {
  116. // Note: when a node can start produce proposals is only enforced in code,
  117. // where we verify if the hardware can keep up with the consensus, by
  118. // counting how many consecutive slots node successfully listened and process
  119. // everything. Additionally, we check each proposer coin creation slot to be
  120. // greater than an epoch length. Later, this will be enforced via contract,
  121. // where it will be explicit when a node can produce proposals,
  122. // and after which slot they can be considered as valid.
  123. let mut listened_slots = 0;
  124. let mut changed_status = false;
  125. loop {
  126. // Check if node can start proposing.
  127. // This code ensures that we only change the status once
  128. // and listened_slots doesn't increment further.
  129. if listened_slots > constants::EPOCH_LENGTH {
  130. if !changed_status {
  131. info!(target: "consensus::proposal", "consensus: Node can start proposing!");
  132. state.write().await.consensus.proposing = true;
  133. changed_status = true;
  134. }
  135. } else {
  136. listened_slots += 1;
  137. }
  138. // Node waits and execute consensus protocol propose period.
  139. if propose_period(consensus_p2p.clone(), state.clone()).await {
  140. // Node needs to resync
  141. warn!(
  142. target: "consensus::proposal",
  143. "consensus: Node missed slot {} due to proposal processing, resyncing...",
  144. state.read().await.consensus.time_keeper.current_slot()
  145. );
  146. break
  147. }
  148. // Node waits and execute consensus protocol finalization period.
  149. if finalization_period(sync_p2p.clone(), state.clone(), ex.clone()).await {
  150. // Node needs to resync
  151. warn!(
  152. target: "consensus::proposal",
  153. "consensus: Node missed slot {} due to finalizated blocks processing, resyncing...",
  154. state.read().await.consensus.time_keeper.current_slot()
  155. );
  156. break
  157. }
  158. }
  159. }
  160. /// async function to wait and execute consensus protocol propose period.
  161. /// Propose period consists of 2 parts:
  162. /// - Generate slot sigmas and checkpoint
  163. /// - Check if slot leader to generate and broadcast proposal
  164. /// Returns flag in case node needs to resync.
  165. async fn propose_period(consensus_p2p: P2pPtr, state: ValidatorStatePtr) -> bool {
  166. // Node sleeps until next slot
  167. let seconds_next_slot = state.read().await.consensus.time_keeper.next_n_slot_start(1);
  168. info!(target: "consensus::proposal", "consensus: Waiting for next slot ({} sec)", seconds_next_slot);
  169. sleep(seconds_next_slot).await;
  170. // Keep a record of slot to verify if next slot got skipped during processing
  171. let processing_slot = state.read().await.consensus.time_keeper.current_slot();
  172. // Retrieve slot sigmas
  173. let (sigma1, sigma2) = state.write().await.consensus.sigmas();
  174. // Node checks if epoch has changed and generate slot checkpoint
  175. let epoch_changed = state.write().await.consensus.epoch_changed(sigma1, sigma2).await;
  176. match epoch_changed {
  177. Ok(changed) => {
  178. if changed {
  179. info!(target: "consensus::proposal", "consensus: New epoch started: {}", state.read().await.consensus.epoch);
  180. }
  181. }
  182. Err(e) => {
  183. error!(target: "consensus::proposal", "consensus: Epoch check failed: {}", e);
  184. return false
  185. }
  186. };
  187. // Node checks if it's the slot leader to generate a new proposal
  188. // for that slot.
  189. let (won, fork_index, coin_index) =
  190. state.write().await.consensus.is_slot_leader(sigma1, sigma2);
  191. let result = if won {
  192. state.write().await.propose(processing_slot, fork_index, coin_index, sigma1, sigma2).await
  193. } else {
  194. Ok(None)
  195. };
  196. let (proposal, coin, derived_blind) = match result {
  197. Ok(pair) => {
  198. if pair.is_none() {
  199. info!(target: "consensus::proposal", "consensus: Node is not the slot lead");
  200. return false
  201. }
  202. pair.unwrap()
  203. }
  204. Err(e) => {
  205. error!(target: "consensus::proposal", "consensus: Block proposal failed: {}", e);
  206. return false
  207. }
  208. };
  209. // Node checks if it missed finalization period due to proposal creation
  210. let next_slot_start = state.read().await.consensus.time_keeper.next_n_slot_start(1);
  211. if next_slot_start <= constants::FINAL_SYNC_DUR {
  212. warn!(
  213. target: "consensus::proposal",
  214. "consensus: Node missed slot {} finalization period due to proposal creation, resyncing...",
  215. state.read().await.consensus.time_keeper.current_slot()
  216. );
  217. return true
  218. }
  219. // Node stores the proposal and broadcast to rest nodes
  220. info!(target: "consensus::proposal", "consensus: Node is the slot leader: Proposed block: {}", proposal);
  221. debug!(target: "consensus::proposal", "consensus: Full proposal: {:?}", proposal);
  222. match state
  223. .write()
  224. .await
  225. .receive_proposal(&proposal, Some((coin_index, coin, derived_blind)))
  226. .await
  227. {
  228. Ok(_) => {
  229. // Here we don't have to check to broadcast, because the flag
  230. // will always be true, since the node is able to produce proposals
  231. info!(target: "consensus::proposal", "consensus: Block proposal saved successfully");
  232. // Broadcast proposal to other consensus nodes
  233. match consensus_p2p.broadcast(proposal).await {
  234. Ok(()) => {
  235. info!(target: "consensus::proposal", "consensus: Proposal broadcasted successfully")
  236. }
  237. Err(e) => {
  238. error!(target: "consensus::proposal", "consensus: Failed broadcasting proposal: {}", e)
  239. }
  240. }
  241. }
  242. Err(e) => {
  243. error!(target: "consensus::proposal", "consensus: Block proposal save failed: {}", e);
  244. }
  245. }
  246. // Verify node didn't skip next slot
  247. processing_slot != state.read().await.consensus.time_keeper.current_slot()
  248. }
  249. /// async function to wait and execute consensus protocol finalization period.
  250. /// Returns flag in case node needs to resync.
  251. async fn finalization_period(
  252. sync_p2p: P2pPtr,
  253. state: ValidatorStatePtr,
  254. ex: Arc<smol::Executor<'_>>,
  255. ) -> bool {
  256. // Node sleeps until finalization sync period starts
  257. let next_slot_start = state.read().await.consensus.time_keeper.next_n_slot_start(1);
  258. if next_slot_start > constants::FINAL_SYNC_DUR {
  259. let seconds_sync_period = next_slot_start - constants::FINAL_SYNC_DUR;
  260. info!(target: "consensus::proposal", "consensus: Waiting for finalization sync period ({} sec)", seconds_sync_period);
  261. sleep(seconds_sync_period).await;
  262. } else {
  263. warn!(
  264. target: "consensus::proposal",
  265. "consensus: Node missed slot {} finalization period due to proposals processing, resyncing...",
  266. state.read().await.consensus.time_keeper.current_slot()
  267. );
  268. return true
  269. }
  270. // Keep a record of slot to verify if next slot got skipped during processing
  271. let completed_slot = state.read().await.consensus.time_keeper.current_slot();
  272. // Check if any forks can be finalized
  273. match state.write().await.chain_finalization().await {
  274. Ok((to_broadcast_block, to_broadcast_slot_checkpoints)) => {
  275. // Broadcasting in background
  276. if !to_broadcast_block.is_empty() || !to_broadcast_slot_checkpoints.is_empty() {
  277. ex.spawn(async move {
  278. // Broadcast finalized blocks info, if any:
  279. info!(target: "consensus::proposal", "consensus: Broadcasting finalized blocks");
  280. for info in to_broadcast_block {
  281. match sync_p2p.broadcast(info).await {
  282. Ok(()) => info!(target: "consensus::proposal", "consensus: Broadcasted block"),
  283. Err(e) => error!(target: "consensus::proposal", "consensus: Failed broadcasting block: {}", e),
  284. }
  285. }
  286. // Broadcast finalized slot checkpoints, if any:
  287. info!(target: "consensus::proposal", "consensus: Broadcasting finalized slot checkpoints");
  288. for slot_checkpoint in to_broadcast_slot_checkpoints {
  289. match sync_p2p.broadcast(slot_checkpoint).await {
  290. Ok(()) => info!(target: "consensus::proposal", "consensus: Broadcasted slot_checkpoint"),
  291. Err(e) => {
  292. error!(target: "consensus::proposal", "consensus: Failed broadcasting slot_checkpoint: {}", e)
  293. }
  294. }
  295. }
  296. })
  297. .detach();
  298. } else {
  299. info!(target: "consensus::proposal", "consensus: No finalized blocks or slot checkpoints to broadcast");
  300. }
  301. }
  302. Err(e) => {
  303. error!(target: "consensus::proposal", "consensus: Finalization check failed: {}", e);
  304. }
  305. }
  306. // Verify node didn't skip next slot
  307. completed_slot != state.read().await.consensus.time_keeper.current_slot()
  308. }