sync.rs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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::collections::HashMap;
  19. use darkfi::{
  20. blockchain::HeaderHash, net::ChannelPtr, rpc::jsonrpc::JsonSubscriber, system::sleep,
  21. util::encoding::base64, validator::consensus::Proposal, Error, Result,
  22. };
  23. use darkfi_serial::serialize_async;
  24. use log::{debug, info, warn};
  25. use rand::{prelude::SliceRandom, rngs::OsRng};
  26. use tinyjson::JsonValue;
  27. use crate::{
  28. proto::{
  29. ForkSyncRequest, ForkSyncResponse, HeaderSyncRequest, HeaderSyncResponse, SyncRequest,
  30. SyncResponse, TipRequest, TipResponse, BATCH,
  31. },
  32. Darkfid,
  33. };
  34. // TODO: Parallelize independent requests.
  35. // We can also make them be like torrents, where we retrieve chunks not in order.
  36. /// async task used for block syncing.
  37. /// A checkpoint can be provided to ensure node syncs the correct sequence.
  38. pub async fn sync_task(node: &Darkfid, checkpoint: Option<(u32, HeaderHash)>) -> Result<()> {
  39. info!(target: "darkfid::task::sync_task", "Starting blockchain sync...");
  40. // Grab blocks subscriber
  41. let block_sub = node.subscribers.get("blocks").unwrap();
  42. // Grab last known block header, including existing pending sync ones
  43. let mut last = node.validator.blockchain.last()?;
  44. // If checkpoint is not reached, purge headers and start syncing from scratch
  45. if let Some(checkpoint) = checkpoint {
  46. if checkpoint.0 > last.0 {
  47. node.validator.blockchain.headers.remove_all_sync()?;
  48. }
  49. }
  50. // Check sync headers first record is the next one
  51. if let Some(next) = node.validator.blockchain.headers.get_first_sync()? {
  52. if next.height == last.0 + 1 {
  53. // Grab last sync header to continue syncing from
  54. if let Some(last_sync) = node.validator.blockchain.headers.get_last_sync()? {
  55. last = (last_sync.height, last_sync.hash());
  56. }
  57. } else {
  58. // Purge headers and start syncing from scratch
  59. node.validator.blockchain.headers.remove_all_sync()?;
  60. }
  61. }
  62. info!(target: "darkfid::task::sync_task", "Last known block: {} - {}", last.0, last.1);
  63. // Grab the most common tip and the corresponding peers
  64. let (mut common_tip_height, mut common_tip_peers) =
  65. most_common_tip(node, &last.1, checkpoint).await;
  66. // If last known block header is before the checkpoint, we sync until that first.
  67. if let Some(checkpoint) = checkpoint {
  68. if checkpoint.0 > last.0 {
  69. info!(target: "darkfid::task::sync_task", "Syncing until configured checkpoint: {} - {}", checkpoint.0, checkpoint.1);
  70. // Retrieve all the headers backwards until our last known one and verify them.
  71. // We use the next height, in order to also retrieve the checkpoint header.
  72. retrieve_headers(node, &common_tip_peers, last.0, checkpoint.0 + 1).await?;
  73. // Retrieve all the blocks for those headers and apply them to canonical
  74. last = retrieve_blocks(node, &common_tip_peers, last, block_sub, true).await?;
  75. info!(target: "darkfid::task::sync_task", "Last received block: {} - {}", last.0, last.1);
  76. // Grab synced peers most common tip again
  77. (common_tip_height, common_tip_peers) = most_common_tip(node, &last.1, None).await;
  78. }
  79. }
  80. // Sync headers and blocks
  81. loop {
  82. // Retrieve all the headers backwards until our last known one and verify them.
  83. // We use the next height, in order to also retrieve the peers tip header.
  84. retrieve_headers(node, &common_tip_peers, last.0, common_tip_height + 1).await?;
  85. // Retrieve all the blocks for those headers and apply them to canonical
  86. let last_received =
  87. retrieve_blocks(node, &common_tip_peers, last, block_sub, false).await?;
  88. info!(target: "darkfid::task::sync_task", "Last received block: {} - {}", last_received.0, last_received.1);
  89. if last == last_received {
  90. break
  91. }
  92. last = last_received;
  93. // Grab synced peers most common tip again
  94. (common_tip_height, common_tip_peers) = most_common_tip(node, &last.1, None).await;
  95. }
  96. // Sync best fork
  97. sync_best_fork(node, &common_tip_peers, &last.1).await;
  98. // Perform finalization
  99. let finalized = node.validator.finalization().await?;
  100. if !finalized.is_empty() {
  101. // Notify subscriber
  102. let mut notif_blocks = Vec::with_capacity(finalized.len());
  103. for block in finalized {
  104. notif_blocks.push(JsonValue::String(base64::encode(&serialize_async(&block).await)));
  105. }
  106. block_sub.notify(JsonValue::Array(notif_blocks)).await;
  107. }
  108. *node.validator.synced.write().await = true;
  109. info!(target: "darkfid::task::sync_task", "Blockchain synced!");
  110. Ok(())
  111. }
  112. /// Auxiliary function to block until node is connected to at least one synced peer,
  113. /// and retrieve the synced peers tips.
  114. async fn synced_peers(
  115. node: &Darkfid,
  116. last_tip: &HeaderHash,
  117. checkpoint: Option<(u32, HeaderHash)>,
  118. ) -> HashMap<(u32, [u8; 32]), Vec<ChannelPtr>> {
  119. info!(target: "darkfid::task::sync::synced_peers", "Receiving tip from peers...");
  120. let comms_timeout = node.p2p_handler.p2p.settings().read().await.outbound_connect_timeout;
  121. let mut tips = HashMap::new();
  122. loop {
  123. // Grab channels
  124. let peers = node.p2p_handler.p2p.hosts().channels();
  125. // Ask each peer(if we got any) if they are synced
  126. for peer in peers {
  127. // If a checkpoint was provider, we check that the peer follows that sequence
  128. if let Some(c) = checkpoint {
  129. // Communication setup
  130. let Ok(response_sub) = peer.subscribe_msg::<HeaderSyncResponse>().await else {
  131. debug!(target: "darkfid::task::sync::synced_peers", "Failure during `HeaderSyncResponse` communication setup with peer: {peer:?}");
  132. continue
  133. };
  134. // Node creates a `HeaderSyncRequest` and sends it
  135. let request = HeaderSyncRequest { height: c.0 + 1 };
  136. if let Err(e) = peer.send(&request).await {
  137. debug!(target: "darkfid::task::sync::synced_peers", "Failure during `HeaderSyncRequest` send to peer {peer:?}: {e}");
  138. continue
  139. };
  140. // Node waits for response
  141. let Ok(response) = response_sub.receive_with_timeout(comms_timeout).await else {
  142. debug!(target: "darkfid::task::sync::synced_peers", "Timeout while waiting for `HeaderSyncResponse` from peer: {peer:?}");
  143. continue
  144. };
  145. // Handle response
  146. if response.headers.is_empty() || response.headers.last().unwrap().hash() != c.1 {
  147. debug!(target: "darkfid::task::sync::synced_peers", "Invalid `HeaderSyncResponse` from peer: {peer:?}");
  148. continue
  149. }
  150. }
  151. // Communication setup
  152. let Ok(response_sub) = peer.subscribe_msg::<TipResponse>().await else {
  153. debug!(target: "darkfid::task::sync::synced_peers", "Failure during `TipResponse` communication setup with peer: {peer:?}");
  154. continue
  155. };
  156. // Node creates a `TipRequest` and sends it
  157. let request = TipRequest { tip: *last_tip };
  158. if let Err(e) = peer.send(&request).await {
  159. debug!(target: "darkfid::task::sync::synced_peers", "Failure during `TipRequest` send to peer {peer:?}: {e}");
  160. continue
  161. };
  162. // Node waits for response
  163. let Ok(response) = response_sub.receive_with_timeout(comms_timeout).await else {
  164. debug!(target: "darkfid::task::sync::synced_peers", "Timeout while waiting for `TipResponse` from peer: {peer:?}");
  165. continue
  166. };
  167. // Handle response
  168. if response.synced && response.height.is_some() && response.hash.is_some() {
  169. let tip = (response.height.unwrap(), *response.hash.unwrap().inner());
  170. let Some(tip_peers) = tips.get_mut(&tip) else {
  171. tips.insert(tip, vec![peer.clone()]);
  172. continue
  173. };
  174. tip_peers.push(peer.clone());
  175. }
  176. }
  177. // Check if we got any tips
  178. if !tips.is_empty() {
  179. break
  180. }
  181. warn!(target: "darkfid::task::sync::synced_peers", "Node is not connected to other synced nodes, waiting to retry...");
  182. let subscription = node.p2p_handler.p2p.hosts().subscribe_channel().await;
  183. let _ = subscription.receive().await;
  184. subscription.unsubscribe().await;
  185. info!(target: "darkfid::task::sync::synced_peers", "Sleeping for {comms_timeout} to allow for more nodes to connect...");
  186. sleep(comms_timeout).await;
  187. }
  188. tips
  189. }
  190. /// Auxiliary function to ask all peers for their current tip and find the most common one.
  191. async fn most_common_tip(
  192. node: &Darkfid,
  193. last_tip: &HeaderHash,
  194. checkpoint: Option<(u32, HeaderHash)>,
  195. ) -> (u32, Vec<ChannelPtr>) {
  196. // Grab synced peers tips
  197. let tips = synced_peers(node, last_tip, checkpoint).await;
  198. // Grab the most common highest tip peers
  199. info!(target: "darkfid::task::sync::most_common_tip", "Finding most common tip...");
  200. let mut common_tip = (0, [0u8; 32], vec![]);
  201. for (tip, peers) in tips {
  202. // Check if tip peers is less than the most common tip peers
  203. if peers.len() < common_tip.2.len() {
  204. continue;
  205. }
  206. // If peers are the same length, skip if tip height is less than
  207. // the most common tip height.
  208. if peers.len() == common_tip.2.len() || tip.0 < common_tip.0 {
  209. continue;
  210. }
  211. // Keep the heighest tip with the most peers
  212. common_tip = (tip.0, tip.1, peers);
  213. }
  214. info!(target: "darkfid::task::sync::most_common_tip", "Most common tip: {} - {}", common_tip.0, HeaderHash::new(common_tip.1));
  215. (common_tip.0, common_tip.2)
  216. }
  217. /// Auxiliary function to retrieve headers backwards until our last known one and verify them.
  218. async fn retrieve_headers(
  219. node: &Darkfid,
  220. peers: &[ChannelPtr],
  221. last_known: u32,
  222. tip_height: u32,
  223. ) -> Result<()> {
  224. info!(target: "darkfid::task::sync::retrieve_headers", "Retrieving missing headers from peers...");
  225. // Communication setup
  226. let mut peer_subs = vec![];
  227. for peer in peers {
  228. match peer.subscribe_msg::<HeaderSyncResponse>().await {
  229. Ok(response_sub) => peer_subs.push(Some(response_sub)),
  230. Err(e) => {
  231. debug!(target: "darkfid::task::sync::retrieve_headers", "Failure during `HeaderSyncResponse` communication setup with peer {peer:?}: {e}");
  232. peer_subs.push(None)
  233. }
  234. }
  235. }
  236. let comms_timeout = node.p2p_handler.p2p.settings().read().await.outbound_connect_timeout;
  237. // We subtract 1 since tip_height is increased by one
  238. let total = tip_height - last_known - 1;
  239. let mut last_tip_height = tip_height;
  240. 'headers_loop: loop {
  241. for (index, peer) in peers.iter().enumerate() {
  242. // Grab the response sub reference
  243. let Some(ref response_sub) = peer_subs[index] else {
  244. continue;
  245. };
  246. // Node creates a `HeaderSyncRequest` and sends it
  247. let request = HeaderSyncRequest { height: last_tip_height };
  248. if let Err(e) = peer.send(&request).await {
  249. debug!(target: "darkfid::task::sync::retrieve_headers", "Failure during `HeaderSyncRequest` send to peer {peer:?}: {e}");
  250. continue
  251. };
  252. // Node waits for response
  253. let Ok(response) = response_sub.receive_with_timeout(comms_timeout).await else {
  254. debug!(target: "darkfid::task::sync::retrieve_headers", "Timeout while waiting for `HeaderSyncResponse` from peer: {peer:?}");
  255. continue
  256. };
  257. // Retain only the headers after our last known
  258. let mut response_headers = response.headers.to_vec();
  259. response_headers.retain(|h| h.height > last_known);
  260. if response_headers.is_empty() {
  261. break 'headers_loop
  262. }
  263. // Store the headers
  264. node.validator.blockchain.headers.insert_sync(&response_headers)?;
  265. last_tip_height = response_headers[0].height;
  266. info!(target: "darkfid::task::sync::retrieve_headers", "Headers received: {}/{}", node.validator.blockchain.headers.len_sync(), total);
  267. }
  268. }
  269. // Check if we retrieved any new headers
  270. if node.validator.blockchain.headers.is_empty_sync() {
  271. return Ok(());
  272. }
  273. // Verify headers sequence. Here we do a quick and dirty verification
  274. // of just the hashes and heights sequence. We will formaly verify
  275. // the blocks when we retrieve them. We verify them in batches,
  276. // to not load them all in memory.
  277. info!(target: "darkfid::task::sync::retrieve_headers", "Verifying headers sequence...");
  278. let mut verified_headers = 0;
  279. let total = node.validator.blockchain.headers.len_sync();
  280. // First we verify the first `BATCH` sequence, using the last known header
  281. // as the first sync header previous.
  282. let last_known = node.validator.consensus.best_fork_last_header().await?;
  283. let mut headers = node.validator.blockchain.headers.get_after_sync(0, BATCH)?;
  284. if headers[0].previous != last_known.1 || headers[0].height != last_known.0 + 1 {
  285. node.validator.blockchain.headers.remove_all_sync()?;
  286. return Err(Error::BlockIsInvalid(headers[0].hash().as_string()))
  287. }
  288. verified_headers += 1;
  289. for (index, header) in headers[1..].iter().enumerate() {
  290. if header.previous != headers[index].hash() || header.height != headers[index].height + 1 {
  291. node.validator.blockchain.headers.remove_all_sync()?;
  292. return Err(Error::BlockIsInvalid(header.hash().as_string()))
  293. }
  294. verified_headers += 1;
  295. }
  296. info!(target: "darkfid::task::sync::retrieve_headers", "Headers verified: {}/{}", verified_headers, total);
  297. // Now we verify the rest sequences
  298. let mut last_checked = headers.last().unwrap().clone();
  299. headers = node.validator.blockchain.headers.get_after_sync(last_checked.height, BATCH)?;
  300. while !headers.is_empty() {
  301. if headers[0].previous != last_checked.hash() ||
  302. headers[0].height != last_checked.height + 1
  303. {
  304. node.validator.blockchain.headers.remove_all_sync()?;
  305. return Err(Error::BlockIsInvalid(headers[0].hash().as_string()))
  306. }
  307. verified_headers += 1;
  308. for (index, header) in headers[1..].iter().enumerate() {
  309. if header.previous != headers[index].hash() ||
  310. header.height != headers[index].height + 1
  311. {
  312. node.validator.blockchain.headers.remove_all_sync()?;
  313. return Err(Error::BlockIsInvalid(header.hash().as_string()))
  314. }
  315. verified_headers += 1;
  316. }
  317. last_checked = headers.last().unwrap().clone();
  318. headers = node.validator.blockchain.headers.get_after_sync(last_checked.height, BATCH)?;
  319. info!(target: "darkfid::task::sync::retrieve_headers", "Headers verified: {}/{}", verified_headers, total);
  320. }
  321. info!(target: "darkfid::task::sync::retrieve_headers", "Headers sequence verified!");
  322. Ok(())
  323. }
  324. /// Auxiliary function to retrieve blocks of provided headers and apply them to canonical.
  325. async fn retrieve_blocks(
  326. node: &Darkfid,
  327. peers: &[ChannelPtr],
  328. last_known: (u32, HeaderHash),
  329. block_sub: &JsonSubscriber,
  330. checkpoint_blocks: bool,
  331. ) -> Result<(u32, HeaderHash)> {
  332. info!(target: "darkfid::task::sync::retrieve_blocks", "Retrieving missing blocks from peers...");
  333. let mut last_received = last_known;
  334. // Communication setup
  335. let mut peer_subs = vec![];
  336. for peer in peers {
  337. match peer.subscribe_msg::<SyncResponse>().await {
  338. Ok(response_sub) => peer_subs.push(Some(response_sub)),
  339. Err(e) => {
  340. debug!(target: "darkfid::task::sync::retrieve_blocks", "Failure during `SyncResponse` communication setup with peer {peer:?}: {e}");
  341. peer_subs.push(None)
  342. }
  343. }
  344. }
  345. let comms_timeout = node.p2p_handler.p2p.settings().read().await.outbound_connect_timeout;
  346. let mut received_blocks = 0;
  347. let total = node.validator.blockchain.headers.len_sync();
  348. 'blocks_loop: loop {
  349. 'peers_loop: for (index, peer) in peers.iter().enumerate() {
  350. // Grab the response sub reference
  351. let Some(ref response_sub) = peer_subs[index] else {
  352. continue;
  353. };
  354. // Grab first `BATCH` headers
  355. let headers = node.validator.blockchain.headers.get_after_sync(0, BATCH)?;
  356. if headers.is_empty() {
  357. break 'blocks_loop
  358. }
  359. let mut headers_hashes = Vec::with_capacity(headers.len());
  360. let mut synced_headers = Vec::with_capacity(headers.len());
  361. for header in &headers {
  362. headers_hashes.push(header.hash());
  363. synced_headers.push(header.height);
  364. }
  365. // Node creates a `SyncRequest` and sends it
  366. let request = SyncRequest { headers: headers_hashes.clone() };
  367. if let Err(e) = peer.send(&request).await {
  368. debug!(target: "darkfid::task::sync::retrieve_blocks", "Failure during `SyncRequest` send to peer {peer:?}: {e}");
  369. continue
  370. };
  371. // Node waits for response
  372. let Ok(response) = response_sub.receive_with_timeout(comms_timeout).await else {
  373. debug!(target: "darkfid::task::sync::retrieve_blocks", "Timeout while waiting for `SyncResponse` from peer: {peer:?}");
  374. continue
  375. };
  376. // Verify and store retrieved blocks
  377. debug!(target: "darkfid::task::sync::retrieve_blocks", "Processing received blocks");
  378. received_blocks += response.blocks.len();
  379. if checkpoint_blocks {
  380. if let Err(e) =
  381. node.validator.add_checkpoint_blocks(&response.blocks, &headers_hashes).await
  382. {
  383. debug!(target: "darkfid::task::sync::retrieve_blocks", "Error while adding checkpoint blocks: {e}");
  384. continue
  385. };
  386. } else {
  387. for block in &response.blocks {
  388. if let Err(e) =
  389. node.validator.append_proposal(&Proposal::new(block.clone())).await
  390. {
  391. debug!(target: "darkfid::task::sync::retrieve_blocks", "Error while appending proposal: {e}");
  392. continue 'peers_loop
  393. };
  394. }
  395. }
  396. last_received = (*synced_headers.last().unwrap(), *headers_hashes.last().unwrap());
  397. // Remove synced headers
  398. node.validator.blockchain.headers.remove_sync(&synced_headers)?;
  399. if checkpoint_blocks {
  400. // Notify subscriber
  401. let mut notif_blocks = Vec::with_capacity(response.blocks.len());
  402. info!(target: "darkfid::task::sync::retrieve_blocks", "Blocks added:");
  403. for (index, block) in response.blocks.iter().enumerate() {
  404. info!(target: "darkfid::task::sync::retrieve_blocks", "\t{} - {}", headers_hashes[index], headers[index].height);
  405. notif_blocks
  406. .push(JsonValue::String(base64::encode(&serialize_async(block).await)));
  407. }
  408. block_sub.notify(JsonValue::Array(notif_blocks)).await;
  409. } else {
  410. // Perform finalization for received blocks
  411. let finalized = node.validator.finalization().await?;
  412. if !finalized.is_empty() {
  413. // Notify subscriber
  414. let mut notif_blocks = Vec::with_capacity(finalized.len());
  415. for block in finalized {
  416. notif_blocks.push(JsonValue::String(base64::encode(
  417. &serialize_async(&block).await,
  418. )));
  419. }
  420. block_sub.notify(JsonValue::Array(notif_blocks)).await;
  421. }
  422. }
  423. info!(target: "darkfid::task::sync::retrieve_blocks", "Blocks received: {}/{}", received_blocks, total);
  424. }
  425. }
  426. Ok(last_received)
  427. }
  428. /// Auxiliary function to retrieve best fork state from a random peer.
  429. async fn sync_best_fork(node: &Darkfid, peers: &[ChannelPtr], last_tip: &HeaderHash) {
  430. info!(target: "darkfid::task::sync::sync_best_fork", "Syncing fork states from peers...");
  431. // Getting a random peer to ask for blocks
  432. let peer = &peers.choose(&mut OsRng).unwrap();
  433. // Communication setup
  434. let Ok(response_sub) = peer.subscribe_msg::<ForkSyncResponse>().await else {
  435. debug!(target: "darkfid::task::sync::sync_best_fork", "Failure during `ForkSyncResponse` communication setup with peer: {peer:?}");
  436. return
  437. };
  438. let notif_sub = node.subscribers.get("proposals").unwrap();
  439. // Node creates a `ForkSyncRequest` and sends it
  440. let request = ForkSyncRequest { tip: *last_tip, fork_tip: None };
  441. if let Err(e) = peer.send(&request).await {
  442. debug!(target: "darkfid::task::sync::sync_best_fork", "Failure during `ForkSyncRequest` send to peer {peer:?}: {e}");
  443. return
  444. };
  445. // Node waits for response
  446. let Ok(response) = response_sub
  447. .receive_with_timeout(node.p2p_handler.p2p.settings().read().await.outbound_connect_timeout)
  448. .await
  449. else {
  450. debug!(target: "darkfid::task::sync::sync_best_fork", "Timeout while waiting for `ForkSyncResponse` from peer: {peer:?}");
  451. return
  452. };
  453. // Verify and store retrieved proposals
  454. debug!(target: "darkfid::task::sync::sync_best_fork", "Processing received proposals");
  455. for proposal in &response.proposals {
  456. if let Err(e) = node.validator.append_proposal(proposal).await {
  457. debug!(target: "darkfid::task::sync::sync_best_fork", "Error while appending proposal: {e}");
  458. return
  459. };
  460. // Notify subscriber
  461. let enc_prop = JsonValue::String(base64::encode(&serialize_async(proposal).await));
  462. notif_sub.notify(vec![enc_prop].into()).await;
  463. }
  464. }