1use crate::{
17 helpers::{PeerPair, PrepareSyncRequest, SyncRequest},
18 locators::BlockLocators,
19};
20use futures::future::BoxFuture;
21use snarkos_node_bft_ledger_service::{BeginLedgerUpdateError, LedgerService};
22use snarkos_node_network::ConnectionMode;
23use snarkos_node_router::messages::DataBlocks;
24use snarkos_node_sync_communication_service::CommunicationService;
25use snarkos_node_sync_locators::{CHECKPOINT_INTERVAL, NUM_RECENT_BLOCKS};
26
27use snarkvm::{
28 console::network::{ConsensusVersion, Network},
29 ledger::{Block, CheckBlockError},
30 utilities::flatten_error,
31};
32
33use anyhow::{Context, Result, anyhow, bail, ensure};
34use futures::FutureExt;
35use indexmap::{IndexMap, IndexSet};
36use itertools::Itertools;
37#[cfg(feature = "locktick")]
38use locktick::parking_lot::{Mutex, RwLock};
39#[cfg(feature = "locktick")]
40use locktick::tokio::Mutex as TMutex;
41#[cfg(not(feature = "locktick"))]
42use parking_lot::Mutex;
43#[cfg(not(feature = "locktick"))]
44use parking_lot::RwLock;
45use rand::seq::{IteratorRandom, SliceRandom};
46use std::{
47 collections::{BTreeMap, HashMap, HashSet, VecDeque, hash_map},
48 net::{IpAddr, Ipv4Addr, SocketAddr},
49 sync::Arc,
50 time::{Duration, Instant},
51};
52#[cfg(not(feature = "locktick"))]
53use tokio::sync::Mutex as TMutex;
54use tokio::sync::Notify;
55use tracing::info;
56
57mod helpers;
58use helpers::rangify_heights;
59
60mod sync_state;
61pub use sync_state::BftSyncMode;
62use sync_state::SyncState;
63
64mod metrics;
65use metrics::BlockSyncMetrics;
66
67#[cfg(not(test))]
71pub const REDUNDANCY_FACTOR: usize = 1;
72#[cfg(test)]
73pub const REDUNDANCY_FACTOR: usize = 3;
74
75pub const BLOCK_REQUEST_BATCH_DELAY: Duration = Duration::from_millis(10);
82
83const EXTRA_REDUNDANCY_FACTOR: usize = REDUNDANCY_FACTOR * 3;
84const NUM_SYNC_CANDIDATE_PEERS: usize = REDUNDANCY_FACTOR * 5;
85
86const BLOCK_REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
87
88const MAX_BLOCK_REQUESTS: usize = 50; pub const MAX_BLOCKS_BEHIND: u32 = 1; pub const DUMMY_SELF_IP: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
98
99type FailedRequests<H> = BTreeMap<u32, (Option<H>, Option<H>)>;
101
102#[derive(Clone)]
105struct OutstandingRequest<N: Network> {
106 request: SyncRequest<N>,
107 timestamp: Instant,
108 response: Option<Block<N>>,
111}
112
113#[derive(Clone, serde::Serialize)]
115pub struct BlockRequestInfo {
116 elapsed: u64,
118 done: bool,
120}
121
122#[derive(Clone, serde::Serialize)]
124pub struct BlockRequestsSummary {
125 outstanding: String,
126 completed: String,
127}
128
129#[derive(thiserror::Error, Debug)]
130pub enum InsertBlockResponseError<N: Network> {
131 #[error("Empty block response")]
132 EmptyBlockResponse,
133 #[error("The peer did not send a consensus version")]
134 NoConsensusVersion,
135 #[error(
136 "The peer's consensus version for height {last_height} is ahead of ours: expected {expected_version}, got {peer_version}"
137 )]
138 ConsensusVersionAhead { peer_version: ConsensusVersion, expected_version: ConsensusVersion, last_height: u32 },
139 #[error(
140 "The peer's consensus version for height {last_height} is behind ours: expected {expected_version}, got {peer_version}"
141 )]
142 ConsensusVersionBehind { peer_version: ConsensusVersion, expected_version: ConsensusVersion, last_height: u32 },
143 #[error("Block Sync already advanced to block {height}")]
144 BlockSyncAlreadyAdvanced { height: u32 },
145 #[error("No such request for height {height}")]
146 NoSuchRequest { height: u32 },
147 #[error("Invalid block hash for height {height} from '{peer_ip}': expected {expected_hash}, got {actual_hash}")]
148 InvalidBlockHash { height: u32, peer_ip: SocketAddr, expected_hash: N::BlockHash, actual_hash: N::BlockHash },
149 #[error(
150 "The previous block hash in candidate block {height} from '{peer_ip}' is incorrect: expected {expected}, but got {actual}"
151 )]
152 InvalidPreviousBlockHash { height: u32, peer_ip: SocketAddr, expected: N::BlockHash, actual: N::BlockHash },
153 #[error("Candidate block {height} from '{peer_ip}' is malformed")]
154 MalformedBlock { height: u32, peer_ip: SocketAddr },
155 #[error("The sync pool did not request block {height} from '{peer_ip}'")]
156 WrongSyncPeer { height: u32, peer_ip: SocketAddr },
157 #[error("{}", flatten_error(.0))]
158 Other(#[from] anyhow::Error),
159}
160
161impl<N: Network> InsertBlockResponseError<N> {
162 pub fn is_benign(&self) -> bool {
164 matches!(self, Self::NoSuchRequest { .. } | Self::BlockSyncAlreadyAdvanced { .. })
165 }
166
167 pub fn is_consensus_version_ahead(&self) -> bool {
169 matches!(self, Self::ConsensusVersionAhead { .. })
170 }
171
172 pub fn is_consensus_version_behind(&self) -> bool {
174 matches!(self, Self::ConsensusVersionBehind { .. } | Self::NoConsensusVersion)
175 }
176}
177
178impl<N: Network> OutstandingRequest<N> {
179 fn sync_ips(&self) -> &IndexSet<SocketAddr> {
181 let (_, _, sync_ips) = &self.request;
182 sync_ips
183 }
184
185 fn sync_ips_mut(&mut self) -> &mut IndexSet<SocketAddr> {
187 let (_, _, sync_ips) = &mut self.request;
188 sync_ips
189 }
190}
191
192pub struct BlockSync<N: Network> {
205 ledger: Arc<dyn LedgerService<N>>,
207
208 connection_mode: ConnectionMode,
210
211 locators: RwLock<HashMap<SocketAddr, BlockLocators<N>>>,
214
215 common_ancestors: RwLock<IndexMap<PeerPair, u32>>,
220
221 requests: RwLock<BTreeMap<u32, OutstandingRequest<N>>>,
223
224 sync_state: RwLock<SyncState>,
228
229 advance_with_sync_blocks_lock: TMutex<()>,
231
232 peer_notify: Notify,
234
235 response_notify: Notify,
237
238 metrics: BlockSyncMetrics,
240
241 prepare_requests_lock: Mutex<()>,
243
244 failed_requests: Mutex<FailedRequests<N::BlockHash>>,
246
247 last_response_at: Mutex<HashMap<SocketAddr, Instant>>,
252
253 synced_notify: Notify,
255}
256
257impl<N: Network> BlockSync<N> {
258 pub fn new(ledger: Arc<dyn LedgerService<N>>, connection_mode: ConnectionMode) -> Self {
260 let sync_state = SyncState::new_with_height(ledger.latest_block_height());
262
263 Self {
264 ledger,
265 connection_mode,
266 sync_state: RwLock::new(sync_state),
267 peer_notify: Default::default(),
268 response_notify: Default::default(),
269 locators: Default::default(),
270 requests: Default::default(),
271 common_ancestors: Default::default(),
272 advance_with_sync_blocks_lock: Default::default(),
273 metrics: Default::default(),
274 prepare_requests_lock: Default::default(),
275 failed_requests: Default::default(),
276 last_response_at: Default::default(),
277 synced_notify: Default::default(),
278 }
279 }
280
281 pub async fn wait_for_peer_update(&self) {
289 self.peer_notify.notified().await
290 }
291
292 pub async fn wait_for_block_responses(&self) {
299 self.response_notify.notified().await
300 }
301
302 #[inline]
304 pub fn is_block_synced(&self) -> bool {
305 self.sync_state.read().is_block_synced()
306 }
307
308 pub async fn wait_for_synced(&self) {
313 loop {
314 let mut fut = std::pin::pin!(self.synced_notify.notified());
315
316 {
317 let sync_state = self.sync_state.read();
318 if sync_state.is_block_synced() {
319 return;
320 }
321
322 fut.as_mut().enable();
324 }
325
326 fut.await;
327 }
328 }
329
330 pub fn wait_for_synced_if_syncing(&self) -> Option<BoxFuture<()>> {
337 let mut notified = Box::pin(self.synced_notify.notified());
338
339 {
340 let sync_state = self.sync_state.read();
341 if sync_state.is_block_synced() {
342 return None;
343 }
344
345 notified.as_mut().enable();
347 }
348
349 Some(
350 async move {
351 notified.await;
352 self.wait_for_synced().await;
353 }
354 .boxed(),
355 )
356 }
357
358 #[inline]
361 pub fn num_blocks_behind(&self) -> Option<u32> {
362 self.sync_state.read().num_blocks_behind()
363 }
364
365 #[inline]
367 pub fn greatest_peer_block_height(&self) -> Option<u32> {
368 self.sync_state.read().get_greatest_peer_height()
369 }
370
371 #[inline]
374 pub fn get_sync_height(&self) -> u32 {
375 self.sync_state.read().get_sync_height()
376 }
377
378 #[inline]
380 pub fn get_bft_sync_mode(&self) -> Option<BftSyncMode> {
381 self.sync_state.read().get_bft_sync_mode()
382 }
383
384 #[inline]
389 pub fn set_bft_sync_mode(&self, mode: BftSyncMode) -> Option<BftSyncMode> {
390 self.sync_state.write().set_bft_sync_mode(mode)
391 }
392
393 #[inline]
395 pub fn num_outstanding_block_requests(&self) -> usize {
396 self.requests.read().iter().filter(|(_, e)| !e.sync_ips().is_empty()).count()
397 }
398
399 #[inline]
401 pub fn num_total_block_requests(&self) -> usize {
402 self.requests.read().len()
403 }
404
405 pub fn get_peer_heights(&self) -> HashMap<SocketAddr, u32> {
407 self.locators.read().iter().map(|(addr, locators)| (*addr, locators.latest_locator_height())).collect()
408 }
409
410 pub fn get_block_requests_info(&self) -> BTreeMap<u32, BlockRequestInfo> {
412 self.requests
413 .read()
414 .iter()
415 .map(|(height, request)| {
416 (*height, BlockRequestInfo {
417 done: request.sync_ips().is_empty(),
418 elapsed: request.timestamp.elapsed().as_secs(),
419 })
420 })
421 .collect()
422 }
423
424 pub fn get_block_requests_summary(&self) -> BlockRequestsSummary {
426 let requests = self.requests.read();
427 let completed = requests.iter().filter_map(|(h, e)| if e.sync_ips().is_empty() { Some(*h) } else { None });
428 let outstanding = requests.iter().filter_map(|(h, e)| if !e.sync_ips().is_empty() { Some(*h) } else { None });
429
430 BlockRequestsSummary { completed: rangify_heights(completed), outstanding: rangify_heights(outstanding) }
431 }
432
433 pub fn get_sync_speed(&self) -> f64 {
434 self.metrics.get_sync_speed()
435 }
436}
437
438#[cfg(test)]
440impl<N: Network> BlockSync<N> {
441 fn get_peer_height(&self, peer_ip: &SocketAddr) -> Option<u32> {
443 self.locators.read().get(peer_ip).map(|locators| locators.latest_locator_height())
444 }
445
446 fn get_common_ancestor(&self, peer_a: SocketAddr, peer_b: SocketAddr) -> Option<u32> {
448 self.common_ancestors.read().get(&PeerPair(peer_a, peer_b)).copied()
449 }
450
451 fn get_block_request(&self, height: u32) -> Option<SyncRequest<N>> {
453 self.requests.read().get(&height).map(|e| e.request.clone())
454 }
455
456 fn get_block_request_timestamp(&self, height: u32) -> Option<Instant> {
458 self.requests.read().get(&height).map(|e| e.timestamp)
459 }
460}
461
462impl<N: Network> BlockSync<N> {
463 #[inline]
465 pub fn get_block_locators(&self) -> Result<BlockLocators<N>> {
466 let latest_height = self.ledger.latest_block_height();
468
469 let mut recents = IndexMap::with_capacity(NUM_RECENT_BLOCKS);
472 for height in latest_height.saturating_sub((NUM_RECENT_BLOCKS - 1) as u32)..=latest_height {
474 recents.insert(height, self.ledger.get_block_hash(height)?);
475 }
476
477 let mut checkpoints = IndexMap::with_capacity((latest_height / CHECKPOINT_INTERVAL + 1).try_into()?);
479 for height in (0..=latest_height).step_by(CHECKPOINT_INTERVAL as usize) {
481 checkpoints.insert(height, self.ledger.get_block_hash(height)?);
482 }
483
484 BlockLocators::new(recents, checkpoints)
486 }
487
488 pub fn has_pending_responses(&self) -> bool {
490 self.requests.read().iter().filter(|(_, req)| req.response.is_some() && req.sync_ips().is_empty()).count() > 0
491 }
492
493 pub async fn send_block_requests<C: CommunicationService>(
495 &self,
496 communication: &C,
497 sync_peers: &IndexMap<SocketAddr, BlockLocators<N>>,
498 requests: &[(u32, PrepareSyncRequest<N>)],
499 ) -> bool {
500 let (start_height, max_num_sync_ips) = match requests.first() {
501 Some((height, (_, _, max_num_sync_ips))) => (*height, *max_num_sync_ips),
502 None => {
503 warn!("Block sync failed - no block requests");
504 return false;
505 }
506 };
507
508 let sync_ips: IndexSet<_> =
510 sync_peers.keys().copied().sample(&mut rand::rng(), max_num_sync_ips).into_iter().collect();
511
512 let end_height = start_height.saturating_add(requests.len() as u32);
514
515 {
520 let _prepare_requests_lock = self.prepare_requests_lock.lock();
521 let all_still_connected = {
522 let locators = self.locators.read();
523 sync_ips.iter().all(|ip| locators.contains_key(ip))
524 };
525
526 if !all_still_connected {
527 trace!(
528 "Skipping block request batch for heights {start_height}-{inclusive_end}: at least one of the selected peer(s) has disconnected",
529 inclusive_end = end_height.saturating_sub(1)
530 );
531 return false;
532 }
533
534 for (height, (hash, previous_hash, _)) in requests.iter() {
536 if let Err(err) = self.insert_block_request(*height, (*hash, *previous_hash, sync_ips.clone())) {
538 let err = err.context(format!("Failed to insert block request for height {height}"));
539 warn!("{}", flatten_error(&err));
540 return false;
541 }
542 }
543 }
544
545 debug!("Sending {len} block requests to peer(s) at {peers:?}", len = requests.len(), peers = sync_ips);
546
547 let message = C::prepare_block_request(start_height, end_height);
551
552 let mut tasks = Vec::with_capacity(sync_ips.len());
554 for sync_ip in sync_ips {
555 let sender = communication.send(sync_ip, message.clone()).await;
556 let task = tokio::spawn(async move {
557 match sender {
559 Some(sender) => {
560 if let Err(err) = sender.await {
561 warn!("Failed to send block request to peer '{sync_ip}': {err}");
562 false
563 } else {
564 true
565 }
566 }
567 None => {
568 warn!("Failed to send block request to peer '{sync_ip}': no such peer");
569 false
570 }
571 }
572 });
573
574 tasks.push(task);
575 }
576
577 for result in futures::future::join_all(tasks).await {
579 let success = match result {
580 Ok(success) => success,
581 Err(err) => {
582 error!("tokio join error: {err}");
583 false
584 }
585 };
586
587 if !success {
589 let mut requests = self.requests.write();
591 for height in start_height..end_height {
592 requests.remove(&height);
593 }
594 return false;
596 }
597 }
598 true
599 }
600
601 pub async fn try_issuing_block_requests<C: CommunicationService>(&self, communication: &C) {
607 self.handle_block_request_timeouts();
608
609 if self.is_block_synced() {
610 trace!("Node is already synced. Will not issue new block requests");
611 return;
612 }
613
614 if !self.sync_state.read().can_issue_new_block_requests() && self.failed_requests.lock().is_empty() {
615 trace!("Nothing to sync. Will not issue new block requests");
616 return;
617 }
618
619 let batches = self.prepare_block_requests();
620
621 if batches.is_empty() {
622 let total_requests = self.num_total_block_requests();
623 let num_outstanding = self.num_outstanding_block_requests();
624 if total_requests != 0 {
625 trace!(
626 "Not block synced yet, but there are still {total_requests} in-flight requests. {num_outstanding} are still awaiting responses."
627 );
628 } else {
629 debug!(
630 "Not block synced yet, and there are no outstanding block requests or \
631 new block requests to send"
632 );
633 }
634 } else {
635 for (block_requests, sync_peers) in batches {
636 for requests in block_requests.chunks(DataBlocks::<N>::MAXIMUM_NUMBER_OF_BLOCKS as usize) {
637 if !self.send_block_requests(communication, &sync_peers, requests).await {
638 break;
639 }
640 tokio::time::sleep(BLOCK_REQUEST_BATCH_DELAY).await;
641 }
642 }
643 }
644 }
645
646 #[inline]
654 pub fn insert_block_responses(
655 &self,
656 peer_ip: SocketAddr,
657 blocks: Vec<Block<N>>,
658 latest_consensus_version: Option<ConsensusVersion>,
659 ) -> Result<(), InsertBlockResponseError<N>> {
660 let result = 'outer: {
662 let Some(last_height) = blocks.as_slice().last().map(|b| b.height()) else {
663 break 'outer Err(InsertBlockResponseError::EmptyBlockResponse);
664 };
665
666 let expected_consensus_version =
667 N::CONSENSUS_VERSION(last_height).map_err(InsertBlockResponseError::Other)?;
668
669 if expected_consensus_version >= ConsensusVersion::V12 {
672 if let Some(peer_version) = latest_consensus_version {
673 if peer_version != expected_consensus_version {
674 break 'outer Err(if peer_version > expected_consensus_version {
675 InsertBlockResponseError::ConsensusVersionAhead {
676 peer_version,
677 expected_version: expected_consensus_version,
678 last_height,
679 }
680 } else {
681 InsertBlockResponseError::ConsensusVersionBehind {
682 peer_version,
683 expected_version: expected_consensus_version,
684 last_height,
685 }
686 });
687 }
688 } else {
689 break 'outer Err(InsertBlockResponseError::NoConsensusVersion);
690 }
691 }
692
693 for block in blocks {
695 if let Err(error) = self.insert_block_response(peer_ip, block) {
696 break 'outer Err(error);
697 }
698 }
699
700 Ok(())
701 };
702
703 if result.is_err() {
705 self.remove_block_requests_to_peer(&peer_ip);
706 }
707
708 result
710 }
711
712 #[inline]
715 pub fn peek_next_block(&self, next_height: u32) -> Option<Block<N>> {
716 if let Some(entry) = self.requests.read().get(&next_height) {
719 let is_complete = entry.sync_ips().is_empty();
720 if !is_complete {
721 return None;
722 }
723
724 if entry.response.is_none() {
726 warn!("Request for height {next_height} is complete but no response exists");
727 }
728 entry.response.clone()
729 } else {
730 None
731 }
732 }
733
734 #[inline]
743 pub async fn try_advancing_block_synchronization(&self) -> Result<bool> {
744 let Ok(_lock) = self.advance_with_sync_blocks_lock.try_lock() else {
751 trace!("Skipping attempt to advance block synchronziation as it is already in progress");
752 return Ok(false);
753 };
754
755 let mut current_height = self.ledger.latest_block_height();
757 let start_height = current_height;
758 trace!(
759 "Try advancing with block responses (at block {current_height}, current sync speed is {})",
760 self.get_sync_speed()
761 );
762
763 loop {
764 let next_height = current_height + 1;
765
766 let Some(block) = self.peek_next_block(next_height) else {
767 break;
768 };
769
770 if block.height() != next_height {
772 warn!("Block height mismatch: expected {}, found {}", current_height + 1, block.height());
773 break;
774 }
775
776 let ledger = self.ledger.clone();
777
778 let (advanced, stop) = tokio::task::spawn_blocking(move || {
779 let ledger_update = match ledger.begin_ledger_update() {
780 Ok(update) => update,
781 Err(BeginLedgerUpdateError::ShuttingDown) => {
782 info!("BlockSync cannot advance the ledger any more. The node is shutting down.");
783 return Ok((false, true));
784 }
785 Err(err) => {
786 return Err(anyhow!("Unexpected error when beginning ledger update: {err}"));
787 }
788 };
789
790 let block = match ledger_update.check_next_block(block) {
792 Ok(block) => block,
793 Err(CheckBlockError::InvalidHeight { .. })
794 | Err(CheckBlockError::BlockAlreadyExists { .. })
795 | Err(CheckBlockError::InvalidRound { .. }) => {
796 debug!("Skipping a block at height {next_height}. The ledger already advanced",);
797 return Ok((false, false));
798 }
799 Err(err) => {
800 warn!("{err}");
801 return Err(err.into_anyhow());
802 }
803 };
804
805 ledger_update.advance_to_next_block(&block).with_context(|| {
806 format!(
807 "Failed to advance to next block (height: {height}, hash: {hash})",
808 height = block.height(),
809 hash = block.hash(),
810 )
811 })?;
812
813 Ok((true, false))
814 })
815 .await??;
816
817 if advanced {
820 self.count_request_completed();
821 }
822
823 self.remove_block_response(next_height);
825
826 if stop {
828 break;
829 }
830
831 current_height = next_height;
833 }
834
835 if current_height > start_height {
836 self.set_sync_height(current_height);
837 Ok(true)
838 } else {
839 Ok(false)
840 }
841 }
842}
843
844impl<N: Network> BlockSync<N> {
845 pub fn find_sync_peers(&self) -> Option<(IndexMap<SocketAddr, u32>, u32)> {
852 let current_height = self.get_sync_height();
854
855 if let Some((sync_peers, min_common_ancestor)) = self.find_sync_peers_inner(current_height) {
856 let sync_peers =
858 sync_peers.into_iter().map(|(ip, locators)| (ip, locators.latest_locator_height())).collect();
859 Some((sync_peers, min_common_ancestor))
861 } else {
862 None
863 }
864 }
865
866 pub fn update_peer_locators(&self, peer_ip: SocketAddr, locators: &BlockLocators<N>) -> Result<()> {
874 let connection_mode = self.connection_mode;
875 match self.locators.write().entry(peer_ip) {
878 hash_map::Entry::Occupied(mut e) => {
879 if e.get() == locators {
881 return Ok(());
882 }
883
884 let old_height = e.get().latest_locator_height();
885 let new_height = locators.latest_locator_height();
886
887 if old_height > new_height {
888 debug!("Block height for peer {peer_ip} decreased from {old_height} to {new_height}",);
889 }
890 e.insert(locators.clone());
891 }
892 hash_map::Entry::Vacant(e) => {
893 e.insert(locators.clone());
894 }
895 }
896
897 let new_local_ancestor = {
899 let mut ancestor = 0;
900 for (height, hash) in locators.clone().into_iter() {
904 if let Ok(ledger_hash) = self.ledger.get_block_hash(height) {
905 match ledger_hash == hash {
906 true => ancestor = height,
907 false => {
908 warn!(
909 "[{connection_mode}] Detected fork between this node and peer \"{peer_ip}\" at height {height}"
910 );
911 break;
912 }
913 }
914 }
915 }
916 ancestor
917 };
918
919 let ancestor_updates: Vec<_> = self
922 .locators
923 .read()
924 .iter()
925 .filter_map(|(other_ip, other_locators)| {
926 if other_ip == &peer_ip {
928 return None;
929 }
930 let mut ancestor = 0;
932 for (height, hash) in other_locators.clone().into_iter() {
933 if let Some(expected_hash) = locators.get_hash(height) {
934 match expected_hash == hash {
935 true => ancestor = height,
936 false => {
937 debug!(
938 "[{connection_mode}] Detected fork between peers \"{other_ip}\" and \"{peer_ip}\" at height {height}"
939 );
940 break;
941 }
942 }
943 }
944 }
945
946 Some((PeerPair(peer_ip, *other_ip), ancestor))
947 })
948 .collect();
949
950 {
953 let mut common_ancestors = self.common_ancestors.write();
954 common_ancestors.insert(PeerPair(DUMMY_SELF_IP, peer_ip), new_local_ancestor);
955
956 for (peer_pair, new_ancestor) in ancestor_updates.into_iter() {
957 common_ancestors.insert(peer_pair, new_ancestor);
958 }
959 }
960
961 let is_synced = if let Some(greatest_peer_height) =
963 self.locators.read().values().map(|l| l.latest_locator_height()).max()
964 {
965 let mut sync_state = self.sync_state.write();
966 sync_state.set_greatest_peer_height(greatest_peer_height);
967 sync_state.is_block_synced()
968 } else {
969 error!("Got new block locators but greatest peer height is zero.");
970 false
971 };
972
973 if is_synced {
975 self.synced_notify.notify_waiters();
976 }
977
978 self.peer_notify.notify_one();
981
982 Ok(())
983 }
984
985 pub fn remove_peer(&self, peer_ip: &SocketAddr) {
989 trace!("Removing peer {peer_ip} from block sync");
990
991 let _prepare_requests_lock = self.prepare_requests_lock.lock();
993
994 self.locators.write().remove(peer_ip);
996 self.common_ancestors.write().retain(|pair, _| !pair.contains(peer_ip));
998 self.last_response_at.lock().remove(peer_ip);
1000 self.remove_block_requests_to_peer(peer_ip);
1002
1003 let synced = {
1004 let max_height = self.locators.read().values().map(|l| l.latest_locator_height()).max();
1006 let mut sync_state = self.sync_state.write();
1007
1008 if let Some(greatest_peer_height) = max_height {
1010 sync_state.set_greatest_peer_height(greatest_peer_height);
1011 } else {
1012 sync_state.clear_greatest_peer_height();
1014 }
1015
1016 sync_state.is_block_synced()
1017 };
1018
1019 if synced {
1021 self.synced_notify.notify_waiters();
1022 }
1023
1024 self.peer_notify.notify_one();
1026 }
1027}
1028
1029pub type BlockRequestBatch<N> = (Vec<(u32, PrepareSyncRequest<N>)>, IndexMap<SocketAddr, BlockLocators<N>>);
1031
1032impl<N: Network> BlockSync<N> {
1033 pub fn prepare_block_requests(&self) -> Vec<BlockRequestBatch<N>> {
1051 let _block_requests_lock = self.prepare_requests_lock.lock();
1052
1053 let print_requests = || {
1055 if tracing::enabled!(tracing::Level::TRACE) {
1056 let summary = self.get_block_requests_summary();
1057
1058 if summary.completed.is_empty() {
1059 trace!("There are no completed requests that have not been processed yet.");
1060 } else {
1061 trace!("The following requests are complete but not processed yet: {:?}", summary.completed);
1062 }
1063
1064 if summary.outstanding.is_empty() {
1065 trace!("There are no outstanding requests.");
1066 } else {
1067 trace!("The following requests are still outstanding: {:?}", summary.outstanding);
1068 }
1069 }
1070 };
1071
1072 let current_height = self.get_sync_height();
1074
1075 let mut failed_requests = self.failed_requests.lock();
1079
1080 while let Some(height) = failed_requests.keys().next()
1082 && *height <= current_height
1083 {
1084 failed_requests.pop_first();
1085 }
1086
1087 if !failed_requests.is_empty() {
1089 trace!("There are {} failed requests that need to be re-issued.", failed_requests.len());
1090
1091 let iter = failed_requests.iter();
1093 let mut batches: VecDeque<Vec<(u32, _, _)>> = VecDeque::new();
1094
1095 for (height, (hash, previous_hash)) in iter {
1096 if let Some(prev_batch) = batches.back_mut() {
1097 if let Some((last_height, _, _)) = prev_batch.last()
1098 && *last_height + 1 != *height
1099 {
1100 batches.push_back(vec![(*height, *hash, *previous_hash)]);
1102 } else {
1103 prev_batch.push((*height, *hash, *previous_hash));
1105 }
1106 } else {
1107 batches.push_back(vec![(*height, *hash, *previous_hash)]);
1109 }
1110 }
1111
1112 let mut result = vec![];
1113 while let Some(batch) = batches.pop_front() {
1114 let start_height = batch.first().unwrap().0;
1116 let end_height = batch.last().unwrap().0 + 1;
1117
1118 let max_new_blocks_to_request = end_height - start_height;
1120
1121 let Some((sync_peers, min_common_ancestor)) = self.find_sync_peers_inner(start_height) else {
1122 warn!("Cannot re-request blocks because no or not enough peers are connected");
1124 return result;
1125 };
1126
1127 let Some(greatest_peer_height) = sync_peers.values().map(|l| l.latest_locator_height()).max() else {
1129 error!(
1131 "Cannot re-request blocks because no or not enough peers with sufficient height are connected"
1132 );
1133 return result;
1134 };
1135
1136 let requests = self.construct_requests(
1138 &sync_peers,
1139 start_height.saturating_sub(1),
1140 min_common_ancestor,
1141 max_new_blocks_to_request,
1142 greatest_peer_height,
1143 );
1144
1145 for (height, _) in &requests {
1148 failed_requests.remove(height);
1149 }
1150
1151 result.push((requests, sync_peers));
1152 }
1153
1154 return result;
1155 }
1156
1157 let max_outstanding_block_requests =
1159 (MAX_BLOCK_REQUESTS as u32) * (DataBlocks::<N>::MAXIMUM_NUMBER_OF_BLOCKS as u32);
1160
1161 let max_total_requests = 4 * max_outstanding_block_requests;
1163
1164 let max_new_blocks_to_request =
1165 max_outstanding_block_requests.saturating_sub(self.num_outstanding_block_requests() as u32);
1166
1167 if self.num_total_block_requests() >= max_total_requests as usize {
1169 trace!(
1170 "We are already requested at least {max_total_requests} blocks that have not been fully processed yet. Will not issue more."
1171 );
1172
1173 print_requests();
1174 vec![]
1175 } else if max_new_blocks_to_request == 0 {
1176 trace!(
1177 "Already reached the maximum number of outstanding blocks ({max_outstanding_block_requests}). Will not issue more."
1178 );
1179
1180 print_requests();
1181 vec![]
1182 } else if let Some((sync_peers, min_common_ancestor)) = self.find_sync_peers_inner(current_height) {
1183 let greatest_peer_height = sync_peers.values().map(|l| l.latest_locator_height()).max().unwrap_or(0);
1186
1187 let requests = self.construct_requests(
1189 &sync_peers,
1190 current_height,
1191 min_common_ancestor,
1192 max_new_blocks_to_request,
1193 greatest_peer_height,
1194 );
1195
1196 if !requests.is_empty() {
1197 trace!(
1198 "Generated new block requests for the following heights: {}",
1199 rangify_heights(requests.iter().map(|(h, _)| *h))
1200 );
1201 }
1202
1203 vec![(requests, sync_peers)]
1204 } else if self.requests.read().is_empty() {
1205 vec![]
1211 } else {
1212 trace!("No new blocks can be requested, but there are still outstanding requests.");
1214
1215 print_requests();
1216 vec![]
1217 }
1218 }
1219
1220 pub fn count_request_completed(&self) {
1225 self.metrics.count_request_completed();
1226 }
1227
1228 pub fn set_sync_height(&self, new_height: u32) {
1231 let (synced, fully_synced) = {
1233 let mut state = self.sync_state.write();
1234 state.set_sync_height(new_height);
1235 (state.is_block_synced(), !state.can_issue_new_block_requests())
1236 };
1237
1238 if fully_synced {
1239 self.metrics.mark_fully_synced();
1240 }
1241
1242 if synced {
1243 self.synced_notify.notify_waiters();
1244 }
1245 }
1246
1247 fn insert_block_request(&self, height: u32, (hash, previous_hash, sync_ips): SyncRequest<N>) -> Result<()> {
1255 self.check_block_request(height)?;
1257 ensure!(!sync_ips.is_empty(), "Cannot insert a block request with no sync IPs");
1259 self.requests.write().insert(height, OutstandingRequest {
1261 request: (hash, previous_hash, sync_ips),
1262 timestamp: Instant::now(),
1263 response: None,
1264 });
1265 Ok(())
1266 }
1267
1268 fn insert_block_response(&self, peer_ip: SocketAddr, block: Block<N>) -> Result<(), InsertBlockResponseError<N>> {
1271 let height = block.height();
1273 let mut requests = self.requests.write();
1274
1275 if self.ledger.contains_block_height(height) {
1276 return Err(InsertBlockResponseError::BlockSyncAlreadyAdvanced { height });
1277 }
1278
1279 let Some(entry) = requests.get_mut(&height) else {
1280 return Err(InsertBlockResponseError::NoSuchRequest { height });
1281 };
1282
1283 let (expected_hash, expected_previous_hash, sync_ips) = &entry.request;
1285
1286 if let Some(expected_hash) = expected_hash
1288 && block.hash() != *expected_hash
1289 {
1290 return Err(InsertBlockResponseError::InvalidBlockHash {
1291 height,
1292 peer_ip,
1293 expected_hash: *expected_hash,
1294 actual_hash: block.hash(),
1295 });
1296 }
1297 if let Some(expected_previous_hash) = expected_previous_hash
1299 && block.previous_hash() != *expected_previous_hash
1300 {
1301 return Err(InsertBlockResponseError::InvalidPreviousBlockHash {
1302 height,
1303 peer_ip,
1304 expected: *expected_previous_hash,
1305 actual: block.previous_hash(),
1306 });
1307 }
1308 if !sync_ips.contains(&peer_ip) {
1310 return Err(InsertBlockResponseError::WrongSyncPeer { height, peer_ip });
1311 }
1312
1313 entry.sync_ips_mut().swap_remove(&peer_ip);
1315
1316 if let Some(existing_block) = &entry.response {
1317 if block != *existing_block {
1319 return Err(InsertBlockResponseError::MalformedBlock { height, peer_ip });
1320 }
1321 } else {
1322 entry.response = Some(block.clone());
1323 }
1324
1325 trace!("Received a new and valid block response for height {height}");
1326
1327 self.last_response_at.lock().insert(peer_ip, Instant::now());
1330
1331 self.response_notify.notify_one();
1333
1334 Ok(())
1335 }
1336
1337 fn check_block_request(&self, height: u32) -> Result<()> {
1339 if self.ledger.contains_block_height(height) {
1341 bail!("Failed to add block request, as block {height} exists in the ledger");
1342 }
1343 if self.requests.read().contains_key(&height) {
1345 bail!("Failed to add block request, as block {height} exists in the requests map");
1346 }
1347
1348 Ok(())
1349 }
1350
1351 pub fn remove_block_response(&self, height: u32) {
1358 if let Some(e) = self.requests.write().remove(&height) {
1360 trace!(
1361 "Block request for height {height} was completed in {}ms (sync speed is {})",
1362 e.timestamp.elapsed().as_millis(),
1363 self.get_sync_speed()
1364 );
1365
1366 self.peer_notify.notify_one();
1368 }
1369 }
1370
1371 fn remove_block_requests_to_peer(&self, peer_ip: &SocketAddr) {
1375 trace!("Block sync is removing all block requests to peer {peer_ip}...");
1376 let mut heights = vec![];
1377 let mut removed_requests = vec![];
1378
1379 self.requests.write().retain(|height, e| {
1382 let had_peer = e.sync_ips_mut().swap_remove(peer_ip);
1383
1384 if had_peer && e.response.is_none() {
1385 trace!("Removed outstanding block request to peer {peer_ip} at height {height}");
1386 heights.push(*height);
1387 }
1388
1389 let retain = !had_peer || !e.sync_ips().is_empty() || e.response.is_some();
1392 if !retain {
1393 let (hash, previous_hash, _) = &e.request;
1395 removed_requests.push((*height, (*hash, *previous_hash)));
1396 }
1397 retain
1398 });
1399
1400 if !heights.is_empty() {
1401 debug!(
1402 "Removed outstanding block requests to disconnecting peer '{peer_ip}' at heights: {}. {} were fully removed.",
1403 rangify_heights(heights),
1404 removed_requests.len(),
1405 );
1406 }
1407
1408 if !removed_requests.is_empty() {
1410 let mut failed_requests = self.failed_requests.lock();
1411 for (height, e) in removed_requests.into_iter() {
1412 let prev = failed_requests.insert(height, e);
1413 if prev.is_some() {
1414 warn!(
1415 "Failed to mark block request at height {height} as failed, as it already exists in the failed requests map"
1416 );
1417 }
1418 }
1419 }
1420
1421 }
1423
1424 pub fn handle_block_request_timeouts(&self) {
1428 let responsive_peers: HashSet<SocketAddr> = {
1433 let last_response_at = self.last_response_at.lock();
1434 let now = Instant::now();
1435 last_response_at
1436 .iter()
1437 .filter_map(|(peer, t)| (now.duration_since(*t) <= BLOCK_REQUEST_TIMEOUT).then_some(*peer))
1438 .collect()
1439 };
1440
1441 let (timed_out_requests, peers_to_ban) = {
1443 let mut requests = self.requests.write();
1445
1446 let now = Instant::now();
1448
1449 let current_height = self.ledger.latest_block_height();
1451
1452 let mut timed_out_requests = vec![];
1454
1455 let mut peers_to_ban: HashSet<SocketAddr> = HashSet::new();
1457
1458 requests.retain(|height, e| {
1460 let is_obsolete = *height <= current_height;
1461 let timer_elapsed = now.duration_since(e.timestamp) > BLOCK_REQUEST_TIMEOUT;
1463 let is_complete = e.sync_ips().is_empty() && e.response.is_some();
1465 let has_responsive_peer = e.sync_ips().iter().any(|ip| responsive_peers.contains(ip));
1467
1468 let is_timeout = timer_elapsed && !is_complete && !has_responsive_peer;
1470
1471 let retain = !is_timeout && !is_obsolete;
1473
1474 if is_timeout {
1475 trace!("Block request at height {height} has timed out: timer_elapsed={timer_elapsed}, is_complete={is_complete}, is_obsolete={is_obsolete}");
1476
1477 let (hash, previous_hash, _) = &e.request;
1479 timed_out_requests.push((*height, (*hash, *previous_hash)));
1480 } else if is_obsolete {
1481 trace!("Block request at height {height} became obsolete (current_height={current_height})");
1482 }
1483
1484 if is_timeout {
1486 for peer_ip in e.sync_ips().iter() {
1487 peers_to_ban.insert(*peer_ip);
1488 }
1489 }
1490
1491 retain
1492 });
1493
1494 if !timed_out_requests.is_empty() {
1495 debug!(
1496 "{num} block requests timed out: {list}",
1497 num = timed_out_requests.len(),
1498 list = rangify_heights(timed_out_requests.iter().map(|(height, _)| *height))
1499 );
1500 }
1501
1502 (timed_out_requests, peers_to_ban)
1503 };
1504
1505 if !timed_out_requests.is_empty() {
1507 let mut failed_requests = self.failed_requests.lock();
1508 for (height, e) in timed_out_requests.into_iter() {
1509 let prev = failed_requests.insert(height, e);
1510 if prev.is_some() {
1511 warn!(
1512 "Failed to mark block request at height {height} as failed, as it already exists in the failed requests map"
1513 );
1514 }
1515 }
1516 }
1517
1518 for peer_ip in peers_to_ban {
1522 self.remove_peer(&peer_ip);
1523 }
1526 }
1527
1528 fn find_sync_peers_inner(&self, current_height: u32) -> Option<(IndexMap<SocketAddr, BlockLocators<N>>, u32)> {
1536 let latest_ledger_height = self.ledger.latest_block_height();
1538
1539 let candidate_locators: IndexMap<_, _> = self
1542 .locators
1543 .read()
1544 .iter()
1545 .filter(|(_, locators)| locators.latest_locator_height() > current_height)
1546 .sorted_by(|(_, a), (_, b)| b.latest_locator_height().cmp(&a.latest_locator_height()))
1547 .take(NUM_SYNC_CANDIDATE_PEERS)
1548 .map(|(peer_ip, locators)| (*peer_ip, locators.clone()))
1549 .collect();
1550
1551 if candidate_locators.is_empty() {
1553 trace!("Found no sync peers with height greater {current_height}");
1554 return None;
1555 }
1556
1557 let threshold_to_request = candidate_locators.len().min(REDUNDANCY_FACTOR);
1564
1565 for (idx, (peer_ip, peer_locators)) in candidate_locators.iter().enumerate() {
1568 let mut min_common_ancestor = peer_locators.latest_locator_height();
1570
1571 let mut sync_peers = vec![(*peer_ip, peer_locators.clone())];
1574
1575 for (other_ip, other_locators) in candidate_locators.iter().skip(idx + 1) {
1577 if let Some(common_ancestor) = self.common_ancestors.read().get(&PeerPair(*peer_ip, *other_ip)) {
1579 if *common_ancestor > latest_ledger_height && peer_locators.is_consistent_with(other_locators) {
1581 min_common_ancestor = min_common_ancestor.min(*common_ancestor);
1583
1584 sync_peers.push((*other_ip, other_locators.clone()));
1586 }
1587 }
1588 }
1589
1590 if min_common_ancestor > latest_ledger_height && sync_peers.len() >= threshold_to_request {
1592 sync_peers.shuffle(&mut rand::rng());
1595
1596 return Some((sync_peers.into_iter().collect(), min_common_ancestor));
1598 }
1599 }
1600
1601 None
1603 }
1604
1605 fn construct_requests(
1610 &self,
1611 sync_peers: &IndexMap<SocketAddr, BlockLocators<N>>,
1612 sync_height: u32,
1613 min_common_ancestor: u32,
1614 max_blocks_to_request: u32,
1615 greatest_peer_height: u32,
1616 ) -> Vec<(u32, PrepareSyncRequest<N>)> {
1617 let start_height = {
1619 let requests = self.requests.read();
1620 let ledger_height = self.ledger.latest_block_height();
1621
1622 let mut start_height = ledger_height.max(sync_height + 1);
1624
1625 while requests.contains_key(&start_height) {
1627 start_height += 1;
1628 }
1629
1630 start_height
1631 };
1632
1633 if min_common_ancestor < start_height {
1635 if start_height < greatest_peer_height {
1636 trace!(
1637 "No request to construct. Height for the next block request is {start_height}, but minimum common block locator ancestor is only {min_common_ancestor} (sync_height={sync_height} greatest_peer_height={greatest_peer_height})"
1638 );
1639 }
1640 return Default::default();
1641 }
1642
1643 let end_height = (min_common_ancestor + 1).min(start_height + max_blocks_to_request);
1645
1646 let mut request_hashes = IndexMap::with_capacity((start_height..end_height).len());
1648 let mut max_num_sync_ips = 1;
1650
1651 for height in start_height..end_height {
1652 if self.check_block_request(height).is_err() {
1654 match request_hashes.is_empty() {
1657 true => continue,
1658 false => break,
1659 }
1660 }
1661
1662 let (hash, previous_hash, num_sync_ips, is_honest) = construct_request(height, sync_peers);
1664
1665 if !is_honest {
1667 warn!("Detected dishonest peer(s) when preparing block request");
1669 if sync_peers.len() < num_sync_ips {
1671 break;
1672 }
1673 }
1674
1675 max_num_sync_ips = max_num_sync_ips.max(num_sync_ips);
1677
1678 request_hashes.insert(height, (hash, previous_hash));
1680 }
1681
1682 request_hashes
1684 .into_iter()
1685 .map(|(height, (hash, previous_hash))| (height, (hash, previous_hash, max_num_sync_ips)))
1686 .collect()
1687 }
1688}
1689
1690fn construct_request<N: Network>(
1693 height: u32,
1694 sync_peers: &IndexMap<SocketAddr, BlockLocators<N>>,
1695) -> (Option<N::BlockHash>, Option<N::BlockHash>, usize, bool) {
1696 let mut hash = None;
1697 let mut hash_redundancy: usize = 0;
1698 let mut previous_hash = None;
1699 let mut is_honest = true;
1700
1701 for peer_locators in sync_peers.values() {
1702 if let Some(candidate_hash) = peer_locators.get_hash(height) {
1703 match hash {
1704 Some(hash) if hash == candidate_hash => hash_redundancy += 1,
1706 Some(_) => {
1708 hash = None;
1709 hash_redundancy = 0;
1710 previous_hash = None;
1711 is_honest = false;
1712 break;
1713 }
1714 None => {
1716 hash = Some(candidate_hash);
1717 hash_redundancy = 1;
1718 }
1719 }
1720 }
1721 if let Some(candidate_previous_hash) = peer_locators.get_hash(height.saturating_sub(1)) {
1722 match previous_hash {
1723 Some(previous_hash) if previous_hash == candidate_previous_hash => (),
1725 Some(_) => {
1727 hash = None;
1728 hash_redundancy = 0;
1729 previous_hash = None;
1730 is_honest = false;
1731 break;
1732 }
1733 None => previous_hash = Some(candidate_previous_hash),
1735 }
1736 }
1737 }
1738
1739 let num_sync_ips = {
1742 if !is_honest {
1744 EXTRA_REDUNDANCY_FACTOR
1746 }
1747 else if hash.is_some() && hash_redundancy >= REDUNDANCY_FACTOR {
1749 1
1751 }
1752 else {
1754 REDUNDANCY_FACTOR
1756 }
1757 };
1758
1759 (hash, previous_hash, num_sync_ips, is_honest)
1760}
1761
1762#[cfg(test)]
1763mod tests {
1764 use super::*;
1765 use crate::locators::{
1766 CHECKPOINT_INTERVAL,
1767 NUM_RECENT_BLOCKS,
1768 test_helpers::{sample_block_locators, sample_block_locators_with_fork},
1769 };
1770
1771 use snarkos_node_bft_ledger_service::MockLedgerService;
1772 use snarkvm::{
1773 ledger::committee::Committee,
1774 prelude::{Field, TestRng},
1775 };
1776
1777 use indexmap::{IndexSet, indexset};
1778 #[cfg(feature = "locktick")]
1779 use locktick::parking_lot::RwLock;
1780 #[cfg(not(feature = "locktick"))]
1781 use parking_lot::RwLock;
1782 use rand::RngExt;
1783 use std::net::{IpAddr, Ipv4Addr};
1784
1785 type CurrentNetwork = snarkvm::prelude::MainnetV0;
1786
1787 fn sample_peer_ip(id: u16) -> SocketAddr {
1789 assert_ne!(id, 0, "The peer ID must not be 0 (reserved for local IP in testing)");
1790 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), id)
1791 }
1792
1793 fn sample_committee() -> Committee<CurrentNetwork> {
1795 let rng = &mut TestRng::default();
1796 snarkvm::ledger::committee::test_helpers::sample_committee(rng)
1797 }
1798
1799 fn sample_ledger_service(height: u32) -> MockLedgerService<CurrentNetwork> {
1801 MockLedgerService::new_at_height(sample_committee(), height)
1802 }
1803
1804 fn sample_sync_at_height(height: u32) -> BlockSync<CurrentNetwork> {
1806 BlockSync::<CurrentNetwork>::new(Arc::new(sample_ledger_service(height)), ConnectionMode::Router)
1807 }
1808
1809 fn generate_block_heights(max_height: u32, num_values: usize) -> Vec<u32> {
1813 assert!(num_values > 0, "Cannot generate an empty vector");
1814 assert!((max_height as usize) >= num_values);
1815
1816 let mut rng = TestRng::default();
1817
1818 let mut heights: Vec<u32> = (0..(max_height - 1)).sample(&mut rng, num_values);
1819
1820 heights.push(max_height);
1821
1822 heights
1823 }
1824
1825 fn duplicate_sync_at_new_height(sync: &BlockSync<CurrentNetwork>, height: u32) -> BlockSync<CurrentNetwork> {
1827 BlockSync::<CurrentNetwork> {
1828 failed_requests: Default::default(),
1829 peer_notify: Notify::new(),
1830 response_notify: Default::default(),
1831 ledger: Arc::new(sample_ledger_service(height)),
1832 connection_mode: sync.connection_mode,
1833 locators: RwLock::new(sync.locators.read().clone()),
1834 common_ancestors: RwLock::new(sync.common_ancestors.read().clone()),
1835 requests: RwLock::new(sync.requests.read().clone()),
1836 sync_state: RwLock::new(sync.sync_state.read().clone()),
1837 synced_notify: Notify::new(),
1838 advance_with_sync_blocks_lock: Default::default(),
1839 metrics: Default::default(),
1840 prepare_requests_lock: Default::default(),
1841 last_response_at: Default::default(),
1842 }
1843 }
1844
1845 fn check_prepare_block_requests(
1847 sync: BlockSync<CurrentNetwork>,
1848 min_common_ancestor: u32,
1849 peers: IndexSet<SocketAddr>,
1850 ) {
1851 let rng = &mut TestRng::default();
1852
1853 assert_eq!(sync.ledger.latest_block_height(), 0, "This test assumes the sync pool is at genesis");
1855
1856 let num_peers_within_recent_range_of_ledger = {
1858 if min_common_ancestor >= NUM_RECENT_BLOCKS as u32 {
1860 0
1861 }
1862 else {
1864 peers.iter().filter(|peer_ip| sync.get_peer_height(peer_ip).unwrap() < NUM_RECENT_BLOCKS as u32).count()
1865 }
1866 };
1867
1868 let mut batches = sync.prepare_block_requests();
1870
1871 if peers.is_empty() {
1873 assert!(batches.is_empty());
1874 return;
1875 }
1876
1877 let (requests, sync_peers) = batches.pop().unwrap();
1878
1879 let expected_num_requests = core::cmp::min(min_common_ancestor as usize, MAX_BLOCK_REQUESTS);
1881 assert_eq!(requests.len(), expected_num_requests);
1882
1883 for (idx, (height, (hash, previous_hash, num_sync_ips))) in requests.into_iter().enumerate() {
1884 let sync_ips: IndexSet<_> = sync_peers.keys().sample(rng, num_sync_ips).into_iter().copied().collect();
1886 assert_eq!(height, 1 + idx as u32);
1887 assert_eq!(hash, Some((Field::<CurrentNetwork>::from_u32(height)).into()));
1888 assert_eq!(previous_hash, Some((Field::<CurrentNetwork>::from_u32(height - 1)).into()));
1889
1890 if num_peers_within_recent_range_of_ledger >= REDUNDANCY_FACTOR {
1891 assert_eq!(sync_ips.len(), 1);
1892 } else {
1893 assert_eq!(sync_ips.len(), num_peers_within_recent_range_of_ledger);
1894 assert_eq!(sync_ips, peers);
1895 }
1896 }
1897 }
1898
1899 #[test]
1901 fn test_latest_block_height() {
1902 for height in generate_block_heights(100_001, 5000) {
1903 let sync = sample_sync_at_height(height);
1904 assert_eq!(sync.ledger.latest_block_height(), height);
1906
1907 assert_eq!(sync.ledger.get_block_height(&(Field::<CurrentNetwork>::from_u32(0)).into()).unwrap(), 0);
1909 assert_eq!(
1910 sync.ledger.get_block_height(&(Field::<CurrentNetwork>::from_u32(height)).into()).unwrap(),
1911 height
1912 );
1913 }
1914 }
1915
1916 #[test]
1917 fn test_get_block_hash() {
1918 for height in generate_block_heights(100_001, 5000) {
1919 let sync = sample_sync_at_height(height);
1920
1921 assert_eq!(sync.ledger.get_block_hash(0).unwrap(), (Field::<CurrentNetwork>::from_u32(0)).into());
1923 assert_eq!(sync.ledger.get_block_hash(height).unwrap(), (Field::<CurrentNetwork>::from_u32(height)).into());
1924 }
1925 }
1926
1927 #[test]
1928 fn test_prepare_block_requests() {
1929 for num_peers in 0..111 {
1930 println!("Testing with {num_peers} peers");
1931
1932 let sync = sample_sync_at_height(0);
1933
1934 let mut peers = indexset![];
1935
1936 for peer_id in 1..=num_peers {
1937 sync.update_peer_locators(sample_peer_ip(peer_id), &sample_block_locators(10)).unwrap();
1939 peers.insert(sample_peer_ip(peer_id));
1941 }
1942
1943 check_prepare_block_requests(sync, 10, peers);
1945 }
1946 }
1947
1948 #[test]
1949 fn test_prepare_block_requests_with_leading_fork_at_11() {
1950 let sync = sample_sync_at_height(0);
1951
1952 let peer_1 = sample_peer_ip(1);
1963 sync.update_peer_locators(peer_1, &sample_block_locators_with_fork(20, 11)).unwrap();
1964
1965 let peer_2 = sample_peer_ip(2);
1967 sync.update_peer_locators(peer_2, &sample_block_locators(10)).unwrap();
1968
1969 let peer_3 = sample_peer_ip(3);
1971 sync.update_peer_locators(peer_3, &sample_block_locators(10)).unwrap();
1972
1973 let (requests, _) = sync.prepare_block_requests().pop().unwrap();
1975 assert_eq!(requests.len(), 10);
1976
1977 for (idx, (height, (hash, previous_hash, num_sync_ips))) in requests.into_iter().enumerate() {
1979 assert_eq!(height, 1 + idx as u32);
1980 assert_eq!(hash, Some((Field::<CurrentNetwork>::from_u32(height)).into()));
1981 assert_eq!(previous_hash, Some((Field::<CurrentNetwork>::from_u32(height - 1)).into()));
1982 assert_eq!(num_sync_ips, 1); }
1984 }
1985
1986 #[test]
1987 fn test_prepare_block_requests_with_leading_fork_at_10() {
1988 let rng = &mut TestRng::default();
1989 let sync = sample_sync_at_height(0);
1990
1991 let peer_1 = sample_peer_ip(1);
2006 sync.update_peer_locators(peer_1, &sample_block_locators_with_fork(20, 10)).unwrap();
2007
2008 let peer_2 = sample_peer_ip(2);
2010 sync.update_peer_locators(peer_2, &sample_block_locators(10)).unwrap();
2011
2012 let peer_3 = sample_peer_ip(3);
2014 sync.update_peer_locators(peer_3, &sample_block_locators(10)).unwrap();
2015
2016 let batches = sync.prepare_block_requests();
2018 assert!(batches.is_empty());
2019
2020 let peer_4 = sample_peer_ip(4);
2024 sync.update_peer_locators(peer_4, &sample_block_locators(10)).unwrap();
2025
2026 let (requests, sync_peers) = sync.prepare_block_requests().pop().unwrap();
2028 assert_eq!(requests.len(), 10);
2029
2030 for (idx, (height, (hash, previous_hash, num_sync_ips))) in requests.into_iter().enumerate() {
2032 let sync_ips: IndexSet<_> = sync_peers.keys().sample(rng, num_sync_ips).into_iter().copied().collect();
2034 assert_eq!(height, 1 + idx as u32);
2035 assert_eq!(hash, Some((Field::<CurrentNetwork>::from_u32(height)).into()));
2036 assert_eq!(previous_hash, Some((Field::<CurrentNetwork>::from_u32(height - 1)).into()));
2037 assert_eq!(sync_ips.len(), 1); assert_ne!(sync_ips[0], peer_1); }
2040 }
2041
2042 #[test]
2043 fn test_prepare_block_requests_with_trailing_fork_at_9() {
2044 let rng = &mut TestRng::default();
2045 let sync = sample_sync_at_height(0);
2046
2047 let peer_1 = sample_peer_ip(1);
2053 sync.update_peer_locators(peer_1, &sample_block_locators(10)).unwrap();
2054
2055 let peer_2 = sample_peer_ip(2);
2057 sync.update_peer_locators(peer_2, &sample_block_locators(10)).unwrap();
2058
2059 let peer_3 = sample_peer_ip(3);
2061 sync.update_peer_locators(peer_3, &sample_block_locators_with_fork(20, 10)).unwrap();
2062
2063 let batches = sync.prepare_block_requests();
2065 assert!(batches.is_empty());
2066
2067 let peer_4 = sample_peer_ip(4);
2071 sync.update_peer_locators(peer_4, &sample_block_locators(10)).unwrap();
2072
2073 let (requests, sync_peers) = sync.prepare_block_requests().pop().unwrap();
2075 assert_eq!(requests.len(), 10);
2076
2077 for (idx, (height, (hash, previous_hash, num_sync_ips))) in requests.into_iter().enumerate() {
2079 let sync_ips: IndexSet<_> = sync_peers.keys().sample(rng, num_sync_ips).into_iter().copied().collect();
2081 assert_eq!(height, 1 + idx as u32);
2082 assert_eq!(hash, Some((Field::<CurrentNetwork>::from_u32(height)).into()));
2083 assert_eq!(previous_hash, Some((Field::<CurrentNetwork>::from_u32(height - 1)).into()));
2084 assert_eq!(sync_ips.len(), 1); assert_ne!(sync_ips[0], peer_3); }
2087 }
2088
2089 #[test]
2090 fn test_insert_block_requests() {
2091 let rng = &mut TestRng::default();
2092 let sync = sample_sync_at_height(0);
2093
2094 sync.update_peer_locators(sample_peer_ip(1), &sample_block_locators(10)).unwrap();
2096
2097 let (requests, sync_peers) = sync.prepare_block_requests().pop().unwrap();
2099 assert_eq!(requests.len(), 10);
2100
2101 for (height, (hash, previous_hash, num_sync_ips)) in requests.clone() {
2102 let sync_ips: IndexSet<_> = sync_peers.keys().sample(rng, num_sync_ips).into_iter().copied().collect();
2104 sync.insert_block_request(height, (hash, previous_hash, sync_ips.clone())).unwrap();
2106 assert_eq!(sync.get_block_request(height), Some((hash, previous_hash, sync_ips)));
2108 assert!(sync.get_block_request_timestamp(height).is_some());
2109 }
2110
2111 for (height, (hash, previous_hash, num_sync_ips)) in requests.clone() {
2112 let sync_ips: IndexSet<_> = sync_peers.keys().sample(rng, num_sync_ips).into_iter().copied().collect();
2114 assert_eq!(sync.get_block_request(height), Some((hash, previous_hash, sync_ips)));
2116 assert!(sync.get_block_request_timestamp(height).is_some());
2117 }
2118
2119 for (height, (hash, previous_hash, num_sync_ips)) in requests {
2120 let sync_ips: IndexSet<_> = sync_peers.keys().sample(rng, num_sync_ips).into_iter().copied().collect();
2122 sync.insert_block_request(height, (hash, previous_hash, sync_ips.clone())).unwrap_err();
2124 assert_eq!(sync.get_block_request(height), Some((hash, previous_hash, sync_ips)));
2126 assert!(sync.get_block_request_timestamp(height).is_some());
2127 }
2128 }
2129
2130 #[test]
2131 fn test_insert_block_requests_fails() {
2132 let sync = sample_sync_at_height(9);
2133
2134 sync.update_peer_locators(sample_peer_ip(1), &sample_block_locators(10)).unwrap();
2136
2137 sync.insert_block_request(9, (None, None, indexset![sample_peer_ip(1)])).unwrap_err();
2139 sync.insert_block_request(10, (None, None, indexset![sample_peer_ip(1)])).unwrap();
2141 }
2142
2143 #[test]
2144 fn test_update_peer_locators() {
2145 let sync = sample_sync_at_height(0);
2146
2147 let peer1_ip = sample_peer_ip(1);
2149 for peer1_height in 0..500u32 {
2150 sync.update_peer_locators(peer1_ip, &sample_block_locators(peer1_height)).unwrap();
2151 assert_eq!(sync.get_peer_height(&peer1_ip), Some(peer1_height));
2152
2153 let peer2_ip = sample_peer_ip(2);
2154 for peer2_height in 0..500u32 {
2155 println!("Testing peer 1 height at {peer1_height} and peer 2 height at {peer2_height}");
2156
2157 sync.update_peer_locators(peer2_ip, &sample_block_locators(peer2_height)).unwrap();
2158 assert_eq!(sync.get_peer_height(&peer2_ip), Some(peer2_height));
2159
2160 let distance = peer1_height.abs_diff(peer2_height);
2162
2163 if distance < NUM_RECENT_BLOCKS as u32 {
2165 let expected_ancestor = core::cmp::min(peer1_height, peer2_height);
2166 assert_eq!(sync.get_common_ancestor(peer1_ip, peer2_ip), Some(expected_ancestor));
2167 assert_eq!(sync.get_common_ancestor(peer2_ip, peer1_ip), Some(expected_ancestor));
2168 } else {
2169 let min_checkpoints =
2170 core::cmp::min(peer1_height / CHECKPOINT_INTERVAL, peer2_height / CHECKPOINT_INTERVAL);
2171 let expected_ancestor = min_checkpoints * CHECKPOINT_INTERVAL;
2172 assert_eq!(sync.get_common_ancestor(peer1_ip, peer2_ip), Some(expected_ancestor));
2173 assert_eq!(sync.get_common_ancestor(peer2_ip, peer1_ip), Some(expected_ancestor));
2174 }
2175 }
2176 }
2177 }
2178
2179 #[test]
2180 fn test_remove_peer() {
2181 let sync = sample_sync_at_height(0);
2182
2183 let peer_ip = sample_peer_ip(1);
2184 sync.update_peer_locators(peer_ip, &sample_block_locators(100)).unwrap();
2185 assert_eq!(sync.get_peer_height(&peer_ip), Some(100));
2186
2187 sync.remove_peer(&peer_ip);
2188 assert_eq!(sync.get_peer_height(&peer_ip), None);
2189
2190 sync.update_peer_locators(peer_ip, &sample_block_locators(200)).unwrap();
2191 assert_eq!(sync.get_peer_height(&peer_ip), Some(200));
2192
2193 sync.remove_peer(&peer_ip);
2194 assert_eq!(sync.get_peer_height(&peer_ip), None);
2195 }
2196
2197 #[test]
2198 fn test_locators_insert_remove_insert() {
2199 let sync = sample_sync_at_height(0);
2200
2201 let peer_ip = sample_peer_ip(1);
2202 sync.update_peer_locators(peer_ip, &sample_block_locators(100)).unwrap();
2203 assert_eq!(sync.get_peer_height(&peer_ip), Some(100));
2204
2205 sync.remove_peer(&peer_ip);
2206 assert_eq!(sync.get_peer_height(&peer_ip), None);
2207
2208 sync.update_peer_locators(peer_ip, &sample_block_locators(200)).unwrap();
2209 assert_eq!(sync.get_peer_height(&peer_ip), Some(200));
2210 }
2211
2212 #[test]
2213 fn test_requests_insert_remove_insert() {
2214 let rng = &mut TestRng::default();
2215 let sync = sample_sync_at_height(0);
2216
2217 let peer_ip = sample_peer_ip(1);
2219 sync.update_peer_locators(peer_ip, &sample_block_locators(10)).unwrap();
2220
2221 let (requests, sync_peers) = sync.prepare_block_requests().pop().unwrap();
2223 assert_eq!(requests.len(), 10);
2224
2225 for (height, (hash, previous_hash, num_sync_ips)) in requests.clone() {
2226 let sync_ips: IndexSet<_> = sync_peers.keys().sample(rng, num_sync_ips).into_iter().copied().collect();
2228 sync.insert_block_request(height, (hash, previous_hash, sync_ips.clone())).unwrap();
2230 assert_eq!(sync.get_block_request(height), Some((hash, previous_hash, sync_ips)));
2232 assert!(sync.get_block_request_timestamp(height).is_some());
2233 }
2234
2235 sync.remove_peer(&peer_ip);
2237
2238 for (height, _) in requests {
2239 assert_eq!(sync.get_block_request(height), None);
2241 assert!(sync.get_block_request_timestamp(height).is_none());
2242 }
2243
2244 let batches = sync.prepare_block_requests();
2246 assert!(batches.is_empty());
2247
2248 sync.update_peer_locators(peer_ip, &sample_block_locators(10)).unwrap();
2250
2251 let (requests, _) = sync.prepare_block_requests().pop().unwrap();
2253 assert_eq!(requests.len(), 10);
2254
2255 for (height, (hash, previous_hash, num_sync_ips)) in requests {
2256 let sync_ips: IndexSet<_> = sync_peers.keys().sample(rng, num_sync_ips).into_iter().copied().collect();
2258 sync.insert_block_request(height, (hash, previous_hash, sync_ips.clone())).unwrap();
2260 assert_eq!(sync.get_block_request(height), Some((hash, previous_hash, sync_ips)));
2262 assert!(sync.get_block_request_timestamp(height).is_some());
2263 }
2264 }
2265
2266 #[test]
2267 fn test_obsolete_block_requests() {
2268 let rng = &mut TestRng::default();
2269 let sync = sample_sync_at_height(0);
2270
2271 let locator_height = rng.random_range(1..50);
2275
2276 let locators = sample_block_locators(locator_height);
2278 sync.update_peer_locators(sample_peer_ip(1), &locators).unwrap();
2279
2280 let (requests, sync_peers) = sync.prepare_block_requests().pop().unwrap();
2282 assert_eq!(requests.len(), locator_height as usize);
2283
2284 for (height, (hash, previous_hash, num_sync_ips)) in requests.clone() {
2286 let sync_ips: IndexSet<_> = sync_peers.keys().sample(rng, num_sync_ips).into_iter().copied().collect();
2288 sync.insert_block_request(height, (hash, previous_hash, sync_ips.clone())).unwrap();
2290 assert_eq!(sync.get_block_request(height), Some((hash, previous_hash, sync_ips)));
2292 assert!(sync.get_block_request_timestamp(height).is_some());
2293 }
2294
2295 let ledger_height = rng.random_range(0..=locator_height);
2299 let new_sync = duplicate_sync_at_new_height(&sync, ledger_height);
2300
2301 assert_eq!(new_sync.requests.read().len(), requests.len());
2303
2304 new_sync.handle_block_request_timeouts();
2306
2307 assert_eq!(new_sync.requests.read().len(), (locator_height - ledger_height) as usize);
2309 }
2310
2311 #[test]
2312 fn test_timed_out_block_request() {
2313 let sync = sample_sync_at_height(0);
2314 let peer_ip = sample_peer_ip(1);
2315 let locators = sample_block_locators(10);
2316 let block_hash = locators.get_hash(1);
2317
2318 sync.update_peer_locators(peer_ip, &locators).unwrap();
2319
2320 let timestamp = Instant::now() - BLOCK_REQUEST_TIMEOUT - Duration::from_secs(1);
2321
2322 sync.requests.write().insert(1, OutstandingRequest {
2324 request: (block_hash, None, [peer_ip].into()),
2325 timestamp,
2326 response: None,
2327 });
2328
2329 assert_eq!(sync.requests.read().len(), 1);
2330 assert_eq!(sync.locators.read().len(), 1);
2331
2332 sync.handle_block_request_timeouts();
2334
2335 assert!(sync.requests.read().is_empty());
2340 assert!(sync.locators.read().is_empty());
2341 }
2342
2343 #[test]
2344 fn test_reissue_timed_out_block_request() {
2345 let sync = sample_sync_at_height(0);
2346 let peer_ip1 = sample_peer_ip(1);
2347 let peer_ip2 = sample_peer_ip(2);
2348 let peer_ip3 = sample_peer_ip(3);
2349
2350 let locators = sample_block_locators(10);
2351 let block_hash1 = locators.get_hash(1);
2352 let block_hash2 = locators.get_hash(2);
2353
2354 sync.update_peer_locators(peer_ip1, &locators).unwrap();
2355 sync.update_peer_locators(peer_ip2, &locators).unwrap();
2356 sync.update_peer_locators(peer_ip3, &locators).unwrap();
2357
2358 assert_eq!(sync.locators.read().len(), 3);
2359
2360 let timestamp = Instant::now() - BLOCK_REQUEST_TIMEOUT - Duration::from_secs(1);
2361
2362 sync.requests.write().insert(1, OutstandingRequest {
2364 request: (block_hash1, None, [peer_ip1].into()),
2365 timestamp,
2366 response: None,
2367 });
2368
2369 sync.requests.write().insert(2, OutstandingRequest {
2371 request: (block_hash2, None, [peer_ip2].into()),
2372 timestamp: Instant::now(),
2373 response: None,
2374 });
2375
2376 assert_eq!(sync.requests.read().len(), 2);
2377
2378 sync.handle_block_request_timeouts();
2380
2381 assert_eq!(sync.requests.read().len(), 1);
2386 assert_eq!(sync.locators.read().len(), 2);
2387
2388 let failed_requests = sync.failed_requests.lock();
2389 assert_eq!(failed_requests.len(), 1);
2390
2391 let (height, (hash, _)) = failed_requests.iter().next().unwrap();
2392 assert_eq!(*height, 1);
2393 assert_eq!(*hash, block_hash1);
2394 }
2402}