1use anemo::{PeerId, Request, Response, Result, types::PeerEvent};
51use futures::{FutureExt, StreamExt, stream::FuturesOrdered};
52use rand::Rng;
53use std::{
54 collections::{HashMap, VecDeque},
55 sync::{Arc, RwLock},
56 time::{Duration, Instant},
57};
58use sui_config::p2p::StateSyncConfig;
59use sui_types::{
60 committee::Committee,
61 digests::CheckpointDigest,
62 messages_checkpoint::{
63 CertifiedCheckpointSummary as Checkpoint, CheckpointSequenceNumber, EndOfEpochData,
64 VerifiedCheckpoint, VerifiedCheckpointContents, VersionedFullCheckpointContents,
65 },
66 storage::WriteStore,
67};
68use tap::Pipe;
69use tokio::sync::oneshot;
70use tokio::{
71 sync::{broadcast, mpsc, watch},
72 task::{AbortHandle, JoinSet},
73};
74use tracing::{debug, info, instrument, trace, warn};
75
76#[allow(clippy::result_large_err)]
77mod generated {
78 include!(concat!(env!("OUT_DIR"), "/sui.StateSync.rs"));
79}
80mod builder;
81mod metrics;
82mod server;
83#[cfg(test)]
84mod tests;
85mod worker;
86
87use self::{metrics::Metrics, server::CheckpointContentsDownloadLimitLayer};
88use crate::state_sync::worker::process_archive_checkpoint;
89pub use builder::{Builder, UnstartedStateSync};
90pub use generated::{
91 state_sync_client::StateSyncClient,
92 state_sync_server::{StateSync, StateSyncServer},
93};
94pub use server::GetCheckpointAvailabilityResponse;
95pub use server::GetCheckpointSummaryRequest;
96use sui_config::node::ArchiveReaderConfig;
97use sui_storage::object_store::util::{build_object_store, fetch_checkpoint};
98use sui_storage::verify_checkpoint;
99
100#[derive(Clone, Debug)]
105pub struct Handle {
106 sender: mpsc::Sender<StateSyncMessage>,
107 checkpoint_event_sender: broadcast::Sender<VerifiedCheckpoint>,
108 metrics: Metrics,
109}
110
111impl Handle {
112 pub async fn send_checkpoint(&self, checkpoint: VerifiedCheckpoint) {
121 self.sender
122 .send(StateSyncMessage::VerifiedCheckpoint(Box::new(checkpoint)))
123 .await
124 .unwrap()
125 }
126
127 pub fn subscribe_to_synced_checkpoints(&self) -> broadcast::Receiver<VerifiedCheckpoint> {
129 self.checkpoint_event_sender.subscribe()
130 }
131
132 pub fn get_peers_reported_for_failure(&self) -> u64 {
134 self.metrics.get_peers_reported_for_failure()
135 }
136}
137
138pub(super) fn compute_adaptive_timeout(
139 tx_count: u64,
140 min_timeout: Duration,
141 max_timeout: Duration,
142) -> Duration {
143 const MAX_TRANSACTIONS_PER_CHECKPOINT: u64 = 10_000;
144 const JITTER_FRACTION: f64 = 0.1;
145
146 let max_timeout = max_timeout.max(min_timeout);
147 let ratio = (tx_count as f64 / MAX_TRANSACTIONS_PER_CHECKPOINT as f64).min(1.0);
148 let extra = Duration::from_secs_f64((max_timeout - min_timeout).as_secs_f64() * ratio);
149 let base = min_timeout + extra;
150
151 let jitter_range = base.as_secs_f64() * JITTER_FRACTION;
152 let jitter = rand::thread_rng().gen_range(-jitter_range..jitter_range);
153 Duration::from_secs_f64((base.as_secs_f64() + jitter).max(min_timeout.as_secs_f64()))
154}
155
156#[cfg_attr(test, derive(Debug))]
157pub(super) struct PeerScore {
158 successes: VecDeque<(Instant, u64 , Duration)>,
159 failures: VecDeque<Instant>,
160 window: Duration,
161 failure_rate: f64,
162 failing_since: Option<Instant>,
163}
164
165impl PeerScore {
166 const MAX_SAMPLES: usize = 20;
167 const MIN_SAMPLES_FOR_FAILURE: usize = 10;
168
169 pub(super) fn new(window: Duration, failure_rate: f64) -> Self {
170 Self {
171 successes: VecDeque::new(),
172 failures: VecDeque::new(),
173 window,
174 failure_rate,
175 failing_since: None,
176 }
177 }
178
179 pub(super) fn record_success(&mut self, size: u64, response_time: Duration) {
180 let now = Instant::now();
181 self.successes.push_back((now, size, response_time));
182 while self.successes.len() > Self::MAX_SAMPLES {
183 self.successes.pop_front();
184 }
185 }
191
192 pub(super) fn record_failure(&mut self) {
193 let now = Instant::now();
194 self.failures.push_back(now);
195 while self.failures.len() > Self::MAX_SAMPLES {
196 self.failures.pop_front();
197 }
198 }
199
200 pub(super) fn is_failing(&self) -> bool {
201 let now = Instant::now();
202 let recent_failures = self
203 .failures
204 .iter()
205 .filter(|ts| now.duration_since(**ts) < self.window)
206 .count();
207 let recent_successes = self
208 .successes
209 .iter()
210 .filter(|(ts, _, _)| now.duration_since(*ts) < self.window)
211 .count();
212
213 let total = recent_failures + recent_successes;
214 if total < Self::MIN_SAMPLES_FOR_FAILURE {
215 return false;
216 }
217
218 let rate = recent_failures as f64 / total as f64;
219 rate >= self.failure_rate
220 }
221
222 pub(super) fn update_failing_state(&mut self) {
223 if self.is_failing() {
224 if self.failing_since.is_none() {
227 self.failing_since = Some(Instant::now());
228 }
229 } else {
230 self.failing_since = None;
233 }
234 }
235
236 pub(super) fn reset_failing_since(&mut self) {
237 self.failing_since = None;
238 }
239
240 pub(super) fn consistently_failing(&self, threshold: Duration) -> bool {
241 match self.failing_since {
242 Some(since) => since.elapsed() >= threshold,
243 None => false,
244 }
245 }
246
247 pub(super) fn effective_throughput(&self) -> Option<f64> {
248 let now = Instant::now();
249 let (total_size, total_time) = self
250 .successes
251 .iter()
252 .filter(|(ts, _, _)| now.duration_since(*ts) < self.window)
253 .fold((0u64, Duration::ZERO), |(size, time), (_, s, d)| {
254 (size + s, time + *d)
255 });
256
257 if total_size == 0 {
258 return None;
259 }
260
261 if total_time.is_zero() {
262 return None;
263 }
264
265 Some(total_size as f64 / total_time.as_secs_f64())
266 }
267}
268
269struct PeerHeights {
270 peers: HashMap<PeerId, PeerStateSyncInfo>,
272 unprocessed_checkpoints: HashMap<CheckpointDigest, Checkpoint>,
273 sequence_number_to_digest: HashMap<CheckpointSequenceNumber, CheckpointDigest>,
274 scores: HashMap<PeerId, PeerScore>,
275
276 wait_interval_when_no_peer_to_sync_content: Duration,
277 peer_scoring_window: Duration,
278 peer_failure_rate: f64,
279 checkpoint_content_timeout_min: Duration,
280 checkpoint_content_timeout_max: Duration,
281 exploration_probability: f64,
282}
283
284#[derive(Copy, Clone, Debug, PartialEq, Eq)]
285struct PeerStateSyncInfo {
286 genesis_checkpoint_digest: CheckpointDigest,
288 on_same_chain_as_us: bool,
290 height: CheckpointSequenceNumber,
292 lowest: CheckpointSequenceNumber,
295}
296
297impl PeerHeights {
298 pub fn highest_known_checkpoint_sequence_number(&self) -> Option<CheckpointSequenceNumber> {
299 self.peers
300 .values()
301 .filter_map(|info| info.on_same_chain_as_us.then_some(info.height))
302 .max()
303 }
304
305 pub fn peers_on_same_chain(&self) -> impl Iterator<Item = (&PeerId, &PeerStateSyncInfo)> {
306 self.peers
307 .iter()
308 .filter(|(_peer_id, info)| info.on_same_chain_as_us)
309 }
310
311 #[instrument(level = "debug", skip_all, fields(peer_id=?peer_id, checkpoint=?checkpoint.sequence_number()))]
316 pub fn update_peer_info(
317 &mut self,
318 peer_id: PeerId,
319 checkpoint: Checkpoint,
320 low_watermark: Option<CheckpointSequenceNumber>,
321 ) -> bool {
322 debug!("Update peer info");
323
324 let info = match self.peers.get_mut(&peer_id) {
325 Some(info) if info.on_same_chain_as_us => info,
326 _ => return false,
327 };
328
329 info.height = std::cmp::max(*checkpoint.sequence_number(), info.height);
330 if let Some(low_watermark) = low_watermark {
331 info.lowest = low_watermark;
332 }
333 self.insert_checkpoint(checkpoint);
334
335 true
336 }
337
338 #[instrument(level = "debug", skip_all, fields(peer_id=?peer_id, lowest = ?info.lowest, height = ?info.height))]
339 pub fn insert_peer_info(&mut self, peer_id: PeerId, info: PeerStateSyncInfo) {
340 use std::collections::hash_map::Entry;
341 debug!("Insert peer info");
342
343 match self.peers.entry(peer_id) {
344 Entry::Occupied(mut entry) => {
345 let entry = entry.get_mut();
348 if entry.genesis_checkpoint_digest == info.genesis_checkpoint_digest {
349 entry.height = std::cmp::max(entry.height, info.height);
350 } else {
351 *entry = info;
352 }
353 }
354 Entry::Vacant(entry) => {
355 entry.insert(info);
356 }
357 }
358 }
359
360 pub fn mark_peer_as_not_on_same_chain(&mut self, peer_id: PeerId) {
361 if let Some(info) = self.peers.get_mut(&peer_id) {
362 info.on_same_chain_as_us = false;
363 }
364 self.scores.remove(&peer_id);
365 }
366
367 pub fn update_peer_height(
370 &mut self,
371 peer_id: PeerId,
372 height: CheckpointSequenceNumber,
373 low_watermark: Option<CheckpointSequenceNumber>,
374 ) -> bool {
375 let info = match self.peers.get_mut(&peer_id) {
376 Some(info) if info.on_same_chain_as_us => info,
377 _ => return false,
378 };
379
380 info.height = std::cmp::max(height, info.height);
381 if let Some(low_watermark) = low_watermark {
382 info.lowest = low_watermark;
383 }
384
385 true
386 }
387
388 pub fn cleanup_old_checkpoints(&mut self, sequence_number: CheckpointSequenceNumber) {
389 self.unprocessed_checkpoints
390 .retain(|_digest, checkpoint| *checkpoint.sequence_number() > sequence_number);
391 self.sequence_number_to_digest
392 .retain(|&s, _digest| s > sequence_number);
393 }
394
395 pub fn insert_checkpoint(&mut self, checkpoint: Checkpoint) {
400 let digest = *checkpoint.digest();
401 let sequence_number = *checkpoint.sequence_number();
402
403 if let Some(existing_digest) = self.sequence_number_to_digest.get(&sequence_number) {
405 if *existing_digest == digest {
406 return;
408 }
409 tracing::info!(
410 ?sequence_number,
411 ?existing_digest,
412 ?digest,
413 "received checkpoint with same sequence number but different digest, dropping new checkpoint"
414 );
415 return;
416 }
417
418 self.unprocessed_checkpoints.insert(digest, checkpoint);
419 self.sequence_number_to_digest
420 .insert(sequence_number, digest);
421 }
422
423 pub fn remove_checkpoint(&mut self, digest: &CheckpointDigest) {
424 if let Some(checkpoint) = self.unprocessed_checkpoints.remove(digest) {
425 self.sequence_number_to_digest
426 .remove(checkpoint.sequence_number());
427 }
428 }
429
430 pub fn get_checkpoint_by_sequence_number(
431 &self,
432 sequence_number: CheckpointSequenceNumber,
433 ) -> Option<&Checkpoint> {
434 self.sequence_number_to_digest
435 .get(&sequence_number)
436 .and_then(|digest| self.get_checkpoint_by_digest(digest))
437 }
438
439 pub fn get_checkpoint_by_digest(&self, digest: &CheckpointDigest) -> Option<&Checkpoint> {
440 self.unprocessed_checkpoints.get(digest)
441 }
442
443 #[cfg(test)]
444 pub fn set_wait_interval_when_no_peer_to_sync_content(&mut self, duration: Duration) {
445 self.wait_interval_when_no_peer_to_sync_content = duration;
446 }
447
448 pub fn wait_interval_when_no_peer_to_sync_content(&self) -> Duration {
449 self.wait_interval_when_no_peer_to_sync_content
450 }
451
452 pub fn record_success(&mut self, peer_id: PeerId, size: u64, response_time: Duration) {
453 if !self.peers.contains_key(&peer_id) {
454 return;
455 }
456 self.scores
457 .entry(peer_id)
458 .or_insert_with(|| PeerScore::new(self.peer_scoring_window, self.peer_failure_rate))
459 .record_success(size, response_time);
460 }
461
462 pub fn record_failure(&mut self, peer_id: PeerId) {
463 if !self.peers.contains_key(&peer_id) {
464 return;
465 }
466 self.scores
467 .entry(peer_id)
468 .or_insert_with(|| PeerScore::new(self.peer_scoring_window, self.peer_failure_rate))
469 .record_failure();
470 }
471
472 pub fn get_throughput(&self, peer_id: &PeerId) -> Option<f64> {
473 self.scores
474 .get(peer_id)
475 .and_then(|s| s.effective_throughput())
476 }
477
478 pub fn is_failing(&self, peer_id: &PeerId) -> bool {
479 self.scores
480 .get(peer_id)
481 .map(|s| s.is_failing())
482 .unwrap_or(false)
483 }
484
485 pub fn update_failing_states(&mut self) {
486 for score in self.scores.values_mut() {
487 score.update_failing_state();
488 }
489 }
490
491 pub fn find_peer_to_report_for_failure(&self, threshold: Duration) -> Option<PeerId> {
492 self.scores
493 .iter()
494 .filter(|(peer_id, score)| {
495 score.consistently_failing(threshold)
496 && self
497 .peers
498 .get(peer_id)
499 .is_some_and(|info| info.on_same_chain_as_us)
500 })
501 .max_by_key(|(_, score)| {
502 score
503 .failing_since
504 .map(|since| since.elapsed())
505 .unwrap_or(Duration::ZERO)
506 })
507 .map(|(peer_id, _)| *peer_id)
508 }
509}
510
511#[derive(Clone)]
516struct PeerBalancer {
517 known_peers: Vec<(anemo::Peer, PeerStateSyncInfo, f64)>,
518 unknown_peers: Vec<(anemo::Peer, PeerStateSyncInfo, f64)>,
519 failing_peers: Vec<(anemo::Peer, PeerStateSyncInfo)>,
520 requested_checkpoint: Option<CheckpointSequenceNumber>,
521 request_type: PeerCheckpointRequestType,
522 exploration_probability: f64,
523}
524
525#[derive(Clone)]
526enum PeerCheckpointRequestType {
527 Summary,
528 Content,
529}
530
531impl PeerBalancer {
532 pub fn new(
533 network: &anemo::Network,
534 peer_heights: Arc<RwLock<PeerHeights>>,
535 request_type: PeerCheckpointRequestType,
536 ) -> Self {
537 let peer_heights_guard = peer_heights.read().unwrap();
538
539 let mut known_peers = Vec::new();
540 let mut unknown_peers = Vec::new();
541 let mut failing_peers = Vec::new();
542 let exploration_probability = peer_heights_guard.exploration_probability;
543
544 for (peer_id, info) in peer_heights_guard.peers_on_same_chain() {
545 let Some(peer) = network.peer(*peer_id) else {
546 continue;
547 };
548 let rtt_secs = peer.connection_rtt().as_secs_f64();
549
550 if peer_heights_guard.is_failing(peer_id) {
551 failing_peers.push((peer, *info));
552 } else if let Some(throughput) = peer_heights_guard.get_throughput(peer_id) {
553 known_peers.push((peer, *info, throughput));
554 } else {
555 unknown_peers.push((peer, *info, rtt_secs));
556 }
557 }
558 drop(peer_heights_guard);
559
560 unknown_peers.sort_by(|(_, _, rtt_a), (_, _, rtt_b)| {
561 rtt_a
562 .partial_cmp(rtt_b)
563 .unwrap_or(std::cmp::Ordering::Equal)
564 });
565
566 Self {
567 known_peers,
568 unknown_peers,
569 failing_peers,
570 requested_checkpoint: None,
571 request_type,
572 exploration_probability,
573 }
574 }
575
576 pub fn with_checkpoint(mut self, checkpoint: CheckpointSequenceNumber) -> Self {
577 self.requested_checkpoint = Some(checkpoint);
578 self
579 }
580
581 fn is_eligible(&self, info: &PeerStateSyncInfo) -> bool {
582 let requested = self.requested_checkpoint.unwrap_or(0);
583 match &self.request_type {
584 PeerCheckpointRequestType::Summary => info.height >= requested,
585 PeerCheckpointRequestType::Content => {
586 info.height >= requested && info.lowest <= requested
587 }
588 }
589 }
590
591 fn select_by_throughput(&mut self) -> Option<(anemo::Peer, PeerStateSyncInfo)> {
592 let eligible: Vec<_> = self
593 .known_peers
594 .iter()
595 .enumerate()
596 .filter(|(_, (_, info, _))| self.is_eligible(info))
597 .map(|(i, (_, _, t))| (i, *t))
598 .collect();
599
600 if eligible.is_empty() {
601 return None;
602 }
603
604 let total: f64 = eligible.iter().map(|(_, t)| t).sum();
605 let mut pick = rand::thread_rng().gen_range(0.0..total);
606
607 for (idx, throughput) in &eligible {
608 pick -= throughput;
609 if pick <= 0.0 {
610 let (peer, info, _) = self.known_peers.remove(*idx);
611 return Some((peer, info));
612 }
613 }
614
615 let (idx, _) = eligible.last().unwrap();
616 let (peer, info, _) = self.known_peers.remove(*idx);
617 Some((peer, info))
618 }
619
620 fn select_by_rtt(&mut self) -> Option<(anemo::Peer, PeerStateSyncInfo)> {
621 let pos = self
622 .unknown_peers
623 .iter()
624 .position(|(_, info, _)| self.is_eligible(info))?;
625 let (peer, info, _) = self.unknown_peers.remove(pos);
626 Some((peer, info))
627 }
628
629 fn select_failing(&mut self) -> Option<(anemo::Peer, PeerStateSyncInfo)> {
630 let pos = self
631 .failing_peers
632 .iter()
633 .position(|(_, info)| self.is_eligible(info))?;
634 Some(self.failing_peers.remove(pos))
635 }
636}
637
638impl Iterator for PeerBalancer {
639 type Item = StateSyncClient<anemo::Peer>;
640
641 fn next(&mut self) -> Option<Self::Item> {
642 let has_eligible_known = self
643 .known_peers
644 .iter()
645 .any(|(_, info, _)| self.is_eligible(info));
646 let has_eligible_unknown = self
647 .unknown_peers
648 .iter()
649 .any(|(_, info, _)| self.is_eligible(info));
650
651 let explore = has_eligible_unknown
652 && (!has_eligible_known || rand::thread_rng().gen_bool(self.exploration_probability));
653
654 if explore && let Some((peer, _)) = self.select_by_rtt() {
655 return Some(StateSyncClient::new(peer));
656 }
657
658 if has_eligible_known && let Some((peer, _)) = self.select_by_throughput() {
659 return Some(StateSyncClient::new(peer));
660 }
661
662 if let Some((peer, _)) = self.select_failing() {
663 return Some(StateSyncClient::new(peer));
664 }
665
666 None
667 }
668}
669
670#[derive(Clone, Debug)]
671enum StateSyncMessage {
672 StartSyncJob,
673 VerifiedCheckpoint(Box<VerifiedCheckpoint>),
676 SyncedCheckpoint(Box<VerifiedCheckpoint>),
680}
681
682struct StateSyncEventLoop<S> {
683 config: StateSyncConfig,
684
685 mailbox: mpsc::Receiver<StateSyncMessage>,
686 weak_sender: mpsc::WeakSender<StateSyncMessage>,
688
689 tasks: JoinSet<()>,
690 sync_checkpoint_summaries_task: Option<AbortHandle>,
691 sync_checkpoint_contents_task: Option<AbortHandle>,
692 download_limit_layer: Option<CheckpointContentsDownloadLimitLayer>,
693
694 store: S,
695 peer_heights: Arc<RwLock<PeerHeights>>,
696 checkpoint_event_sender: broadcast::Sender<VerifiedCheckpoint>,
697 network: anemo::Network,
698 metrics: Metrics,
699
700 sync_checkpoint_from_archive_task: Option<AbortHandle>,
701 archive_config: Option<ArchiveReaderConfig>,
702 discovery_sender: Option<crate::discovery::Sender>,
703}
704
705impl<S> StateSyncEventLoop<S>
706where
707 S: WriteStore + Clone + Send + Sync + 'static,
708{
709 pub async fn start(mut self) {
714 info!("State-Synchronizer started");
715
716 self.config.pinned_checkpoints.sort();
717
718 let mut interval = tokio::time::interval(self.config.interval_period());
719 let mut peer_events = {
720 let (subscriber, peers) = self.network.subscribe().unwrap();
721 for peer_id in peers {
722 self.spawn_get_latest_from_peer(peer_id);
723 }
724 subscriber
725 };
726 let (
727 target_checkpoint_contents_sequence_sender,
728 target_checkpoint_contents_sequence_receiver,
729 ) = watch::channel(0);
730
731 let (_sender, receiver) = oneshot::channel();
733 tokio::spawn(update_checkpoint_watermark_metrics(
734 receiver,
735 self.store.clone(),
736 self.metrics.clone(),
737 ));
738
739 let task = sync_checkpoint_contents(
741 self.network.clone(),
742 self.store.clone(),
743 self.peer_heights.clone(),
744 self.weak_sender.clone(),
745 self.checkpoint_event_sender.clone(),
746 self.config.checkpoint_content_download_concurrency(),
747 self.config.checkpoint_content_download_tx_concurrency(),
748 self.config.use_get_checkpoint_contents_v2(),
749 target_checkpoint_contents_sequence_receiver,
750 );
751 let task_handle = self.tasks.spawn(task);
752 self.sync_checkpoint_contents_task = Some(task_handle);
753
754 let task = sync_checkpoint_contents_from_archive(
762 self.network.clone(),
763 self.archive_config.clone(),
764 self.store.clone(),
765 self.peer_heights.clone(),
766 self.metrics.clone(),
767 );
768 let task_handle = self.tasks.spawn(task);
769 self.sync_checkpoint_from_archive_task = Some(task_handle);
770
771 loop {
773 tokio::select! {
774 now = interval.tick() => {
775 self.handle_tick(now.into_std());
776 },
777 maybe_message = self.mailbox.recv() => {
778 if let Some(message) = maybe_message {
781 self.handle_message(message);
782 } else {
783 break;
784 }
785 },
786 peer_event = peer_events.recv() => {
787 self.handle_peer_event(peer_event);
788 },
789 Some(task_result) = self.tasks.join_next() => {
790 match task_result {
791 Ok(()) => {},
792 Err(e) => {
793 if e.is_cancelled() {
794 } else if e.is_panic() {
796 std::panic::resume_unwind(e.into_panic());
798 } else {
799 panic!("task failed: {e}");
800 }
801 },
802 };
803
804 if matches!(&self.sync_checkpoint_contents_task, Some(t) if t.is_finished()) {
805 panic!("sync_checkpoint_contents task unexpectedly terminated")
806 }
807
808 if matches!(&self.sync_checkpoint_summaries_task, Some(t) if t.is_finished()) {
809 self.sync_checkpoint_summaries_task = None;
810 }
811
812 if matches!(&self.sync_checkpoint_from_archive_task, Some(t) if t.is_finished()) {
813 panic!("sync_checkpoint_from_archive task unexpectedly terminated")
814 }
815 },
816 }
817
818 self.maybe_start_checkpoint_summary_sync_task();
819 self.maybe_trigger_checkpoint_contents_sync_task(
820 &target_checkpoint_contents_sequence_sender,
821 );
822 }
823
824 info!("State-Synchronizer ended");
825 }
826
827 fn handle_message(&mut self, message: StateSyncMessage) {
828 debug!("Received message: {:?}", message);
829 match message {
830 StateSyncMessage::StartSyncJob => self.maybe_start_checkpoint_summary_sync_task(),
831 StateSyncMessage::VerifiedCheckpoint(checkpoint) => {
832 self.handle_checkpoint_from_consensus(checkpoint)
833 }
834 StateSyncMessage::SyncedCheckpoint(checkpoint) => {
836 self.spawn_notify_peers_of_checkpoint(*checkpoint)
837 }
838 }
839 }
840
841 #[instrument(level = "debug", skip_all)]
843 fn handle_checkpoint_from_consensus(&mut self, checkpoint: Box<VerifiedCheckpoint>) {
844 let prev_digest = *self.store.get_checkpoint_by_sequence_number(checkpoint.sequence_number() - 1)
847 .unwrap_or_else(|| panic!("Got checkpoint {} from consensus but cannot find checkpoint {} in certified_checkpoints", checkpoint.sequence_number(), checkpoint.sequence_number() - 1))
848 .digest();
849 if checkpoint.previous_digest != Some(prev_digest) {
850 panic!(
851 "Checkpoint {} from consensus has mismatched previous_digest, expected: {:?}, actual: {:?}",
852 checkpoint.sequence_number(),
853 Some(prev_digest),
854 checkpoint.previous_digest
855 );
856 }
857
858 let latest_checkpoint = self
859 .store
860 .get_highest_verified_checkpoint()
861 .expect("store operation should not fail");
862
863 if latest_checkpoint.sequence_number() >= checkpoint.sequence_number() {
865 return;
866 }
867
868 let checkpoint = *checkpoint;
869 let next_sequence_number = latest_checkpoint.sequence_number().checked_add(1).unwrap();
870 if *checkpoint.sequence_number() > next_sequence_number {
871 debug!(
872 "consensus sent too new of a checkpoint, expecting: {}, got: {}",
873 next_sequence_number,
874 checkpoint.sequence_number()
875 );
876 }
877
878 #[cfg(debug_assertions)]
881 {
882 let _ = (next_sequence_number..=*checkpoint.sequence_number())
883 .map(|n| {
884 let checkpoint = self
885 .store
886 .get_checkpoint_by_sequence_number(n)
887 .unwrap_or_else(|| panic!("store should contain checkpoint {n}"));
888 self.store
889 .get_full_checkpoint_contents(Some(n), &checkpoint.content_digest)
890 .unwrap_or_else(|| {
891 panic!(
892 "store should contain checkpoint contents for {:?}",
893 checkpoint.content_digest
894 )
895 });
896 })
897 .collect::<Vec<_>>();
898 }
899
900 if let Some(EndOfEpochData {
907 next_epoch_committee,
908 ..
909 }) = checkpoint.end_of_epoch_data.as_ref()
910 {
911 let next_committee = next_epoch_committee.iter().cloned().collect();
912 let committee =
913 Committee::new(checkpoint.epoch().checked_add(1).unwrap(), next_committee);
914 self.store
915 .insert_committee(committee)
916 .expect("store operation should not fail");
917 }
918
919 self.store
920 .update_highest_verified_checkpoint(&checkpoint)
921 .expect("store operation should not fail");
922 self.store
923 .update_highest_synced_checkpoint(&checkpoint)
924 .expect("store operation should not fail");
925
926 let _ = self.checkpoint_event_sender.send(checkpoint.clone());
928
929 self.spawn_notify_peers_of_checkpoint(checkpoint);
930 }
931
932 fn handle_peer_event(
933 &mut self,
934 peer_event: Result<PeerEvent, tokio::sync::broadcast::error::RecvError>,
935 ) {
936 use tokio::sync::broadcast::error::RecvError;
937
938 match peer_event {
939 Ok(PeerEvent::NewPeer(peer_id)) => {
940 self.spawn_get_latest_from_peer(peer_id);
941 }
942 Ok(PeerEvent::LostPeer(peer_id, _)) => {
943 let mut heights = self.peer_heights.write().unwrap();
944 heights.peers.remove(&peer_id);
945 heights.scores.remove(&peer_id);
946 }
947
948 Err(RecvError::Closed) => {
949 panic!("PeerEvent channel shouldn't be able to be closed");
950 }
951
952 Err(RecvError::Lagged(_)) => {
953 trace!("State-Sync fell behind processing PeerEvents");
954 }
955 }
956 }
957
958 fn spawn_get_latest_from_peer(&mut self, peer_id: PeerId) {
959 if let Some(peer) = self.network.peer(peer_id) {
960 let genesis_checkpoint_digest = *self
961 .store
962 .get_checkpoint_by_sequence_number(0)
963 .expect("store should contain genesis checkpoint")
964 .digest();
965 let task = get_latest_from_peer(
966 genesis_checkpoint_digest,
967 peer,
968 self.peer_heights.clone(),
969 self.config.timeout(),
970 );
971 self.tasks.spawn(task);
972 }
973 }
974
975 fn handle_tick(&mut self, _now: std::time::Instant) {
976 let task = query_peers_for_their_latest_checkpoint(
977 self.network.clone(),
978 self.peer_heights.clone(),
979 self.weak_sender.clone(),
980 self.config.timeout(),
981 );
982 self.tasks.spawn(task);
983
984 if let Some(layer) = self.download_limit_layer.as_ref() {
985 layer.maybe_prune_map();
986 }
987
988 self.maybe_report_failing_peer();
989 }
990
991 fn maybe_report_failing_peer(&mut self) {
992 let threshold = self.config.peer_disconnect_threshold();
993 if threshold.is_zero() {
994 return;
995 }
996
997 let mut peer_heights = self.peer_heights.write().unwrap();
998 peer_heights.update_failing_states();
999
1000 let Some(peer_id) = peer_heights.find_peer_to_report_for_failure(threshold) else {
1001 return;
1002 };
1003 if let Some(score) = peer_heights.scores.get_mut(&peer_id) {
1009 score.reset_failing_since();
1010 }
1011 drop(peer_heights);
1012
1013 info!("reporting peer {peer_id} for consistent state sync failures");
1014 if let Some(sender) = &self.discovery_sender {
1015 sender.report_peer_failure(peer_id);
1016 } else {
1017 let _ = self.network.disconnect(peer_id);
1018 }
1019 self.metrics.inc_peers_reported_for_failure();
1020 }
1021
1022 fn maybe_start_checkpoint_summary_sync_task(&mut self) {
1023 if self.sync_checkpoint_summaries_task.is_some() {
1025 return;
1026 }
1027
1028 let highest_processed_checkpoint = self
1029 .store
1030 .get_highest_verified_checkpoint()
1031 .expect("store operation should not fail");
1032
1033 let highest_known_sequence_number = self
1034 .peer_heights
1035 .read()
1036 .unwrap()
1037 .highest_known_checkpoint_sequence_number();
1038
1039 if let Some(target_seq) = highest_known_sequence_number
1040 && *highest_processed_checkpoint.sequence_number() < target_seq
1041 {
1042 let max_batch_size = self.config.max_checkpoint_sync_batch_size();
1044 let limited_target = std::cmp::min(
1045 target_seq,
1046 highest_processed_checkpoint
1047 .sequence_number()
1048 .saturating_add(max_batch_size),
1049 );
1050 let was_limited = limited_target < target_seq;
1051
1052 let weak_sender = self.weak_sender.clone();
1054 let task = sync_to_checkpoint(
1055 self.network.clone(),
1056 self.store.clone(),
1057 self.peer_heights.clone(),
1058 self.metrics.clone(),
1059 self.config.pinned_checkpoints.clone(),
1060 self.config.checkpoint_header_download_concurrency(),
1061 self.config.timeout(),
1062 limited_target,
1063 )
1064 .map(move |result| match result {
1065 Ok(()) => {
1066 if was_limited && let Some(sender) = weak_sender.upgrade() {
1069 let _ = sender.try_send(StateSyncMessage::StartSyncJob);
1070 }
1071 }
1072 Err(e) => {
1073 debug!("error syncing checkpoint {e}");
1074 }
1075 });
1076 let task_handle = self.tasks.spawn(task);
1077 self.sync_checkpoint_summaries_task = Some(task_handle);
1078 }
1079 }
1080
1081 fn maybe_trigger_checkpoint_contents_sync_task(
1082 &mut self,
1083 target_sequence_channel: &watch::Sender<CheckpointSequenceNumber>,
1084 ) {
1085 let highest_verified_checkpoint = self
1086 .store
1087 .get_highest_verified_checkpoint()
1088 .expect("store operation should not fail");
1089 let highest_synced_checkpoint = self
1090 .store
1091 .get_highest_synced_checkpoint()
1092 .expect("store operation should not fail");
1093
1094 if highest_verified_checkpoint.sequence_number()
1095 > highest_synced_checkpoint.sequence_number()
1096 && self
1098 .peer_heights
1099 .read()
1100 .unwrap()
1101 .highest_known_checkpoint_sequence_number()
1102 > Some(*highest_synced_checkpoint.sequence_number())
1103 {
1104 let _ = target_sequence_channel.send_if_modified(|num| {
1105 let new_num = *highest_verified_checkpoint.sequence_number();
1106 if *num == new_num {
1107 return false;
1108 }
1109 *num = new_num;
1110 true
1111 });
1112 }
1113 }
1114
1115 fn spawn_notify_peers_of_checkpoint(&mut self, checkpoint: VerifiedCheckpoint) {
1116 let task = notify_peers_of_checkpoint(
1117 self.network.clone(),
1118 self.peer_heights.clone(),
1119 checkpoint,
1120 self.config.timeout(),
1121 );
1122 self.tasks.spawn(task);
1123 }
1124}
1125
1126async fn notify_peers_of_checkpoint(
1127 network: anemo::Network,
1128 peer_heights: Arc<RwLock<PeerHeights>>,
1129 checkpoint: VerifiedCheckpoint,
1130 timeout: Duration,
1131) {
1132 let futs = peer_heights
1133 .read()
1134 .unwrap()
1135 .peers_on_same_chain()
1136 .filter_map(|(peer_id, info)| {
1138 (*checkpoint.sequence_number() > info.height).then_some(peer_id)
1139 })
1140 .flat_map(|peer_id| network.peer(*peer_id))
1142 .map(StateSyncClient::new)
1143 .map(|mut client| {
1144 let request = Request::new(checkpoint.inner().clone()).with_timeout(timeout);
1145 async move { client.push_checkpoint_summary(request).await }
1146 })
1147 .collect::<Vec<_>>();
1148 futures::future::join_all(futs).await;
1149}
1150
1151async fn get_latest_from_peer(
1152 our_genesis_checkpoint_digest: CheckpointDigest,
1153 peer: anemo::Peer,
1154 peer_heights: Arc<RwLock<PeerHeights>>,
1155 timeout: Duration,
1156) {
1157 let peer_id = peer.peer_id();
1158 let mut client = StateSyncClient::new(peer);
1159
1160 let info = {
1161 let maybe_info = peer_heights.read().unwrap().peers.get(&peer_id).copied();
1162
1163 if let Some(info) = maybe_info {
1164 info
1165 } else {
1166 let request = Request::new(GetCheckpointSummaryRequest::BySequenceNumber(0))
1171 .with_timeout(timeout);
1172 let response = client
1173 .get_checkpoint_summary(request)
1174 .await
1175 .map(Response::into_inner);
1176
1177 let info = match response {
1178 Ok(Some(checkpoint)) => {
1179 let digest = *checkpoint.digest();
1180 PeerStateSyncInfo {
1181 genesis_checkpoint_digest: digest,
1182 on_same_chain_as_us: our_genesis_checkpoint_digest == digest,
1183 height: *checkpoint.sequence_number(),
1184 lowest: CheckpointSequenceNumber::default(),
1185 }
1186 }
1187 Ok(None) => PeerStateSyncInfo {
1188 genesis_checkpoint_digest: CheckpointDigest::default(),
1189 on_same_chain_as_us: false,
1190 height: CheckpointSequenceNumber::default(),
1191 lowest: CheckpointSequenceNumber::default(),
1192 },
1193 Err(status) => {
1194 trace!("get_latest_checkpoint_summary request failed: {status:?}");
1195 return;
1196 }
1197 };
1198 peer_heights
1199 .write()
1200 .unwrap()
1201 .insert_peer_info(peer_id, info);
1202 info
1203 }
1204 };
1205
1206 if !info.on_same_chain_as_us {
1208 trace!(?info, "Peer {peer_id} not on same chain as us");
1209 return;
1210 }
1211 let Ok(Some((highest_checkpoint, low_watermark))) =
1212 query_peer_for_latest_info(&mut client, timeout).await
1213 else {
1214 return;
1215 };
1216 peer_heights
1217 .write()
1218 .unwrap()
1219 .update_peer_info(peer_id, highest_checkpoint, low_watermark);
1220}
1221
1222async fn query_peer_for_latest_info(
1231 client: &mut StateSyncClient<anemo::Peer>,
1232 timeout: Duration,
1233) -> Result<Option<(Checkpoint, Option<CheckpointSequenceNumber>)>, ()> {
1234 let request = Request::new(()).with_timeout(timeout);
1235 let response = client
1236 .get_checkpoint_availability(request)
1237 .await
1238 .map(Response::into_inner);
1239 match response {
1240 Ok(GetCheckpointAvailabilityResponse {
1241 highest_synced_checkpoint,
1242 lowest_available_checkpoint,
1243 }) => {
1244 return Ok(Some((
1245 highest_synced_checkpoint,
1246 Some(lowest_available_checkpoint),
1247 )));
1248 }
1249 Err(status) => {
1250 if status.status() != anemo::types::response::StatusCode::NotFound {
1252 trace!("get_checkpoint_availability request failed: {status:?}");
1253 return Err(());
1254 }
1255 }
1256 };
1257
1258 let request = Request::new(GetCheckpointSummaryRequest::Latest).with_timeout(timeout);
1261 let response = client
1262 .get_checkpoint_summary(request)
1263 .await
1264 .map(Response::into_inner);
1265 match response {
1266 Ok(Some(checkpoint)) => Ok(Some((checkpoint, None))),
1267 Ok(None) => Ok(None),
1268 Err(status) => {
1269 trace!("get_checkpoint_summary (latest) request failed: {status:?}");
1270 Err(())
1271 }
1272 }
1273}
1274
1275#[instrument(level = "debug", skip_all)]
1276async fn query_peers_for_their_latest_checkpoint(
1277 network: anemo::Network,
1278 peer_heights: Arc<RwLock<PeerHeights>>,
1279 sender: mpsc::WeakSender<StateSyncMessage>,
1280 timeout: Duration,
1281) {
1282 let peer_heights = &peer_heights;
1283 let futs = peer_heights
1284 .read()
1285 .unwrap()
1286 .peers_on_same_chain()
1287 .flat_map(|(peer_id, _info)| network.peer(*peer_id))
1289 .map(|peer| {
1290 let peer_id = peer.peer_id();
1291 let mut client = StateSyncClient::new(peer);
1292
1293 async move {
1294 let start = Instant::now();
1295 let response = query_peer_for_latest_info(&mut client, timeout).await;
1296 let elapsed = start.elapsed();
1297 match response {
1298 Ok(Some((highest_checkpoint, low_watermark))) => {
1299 let size = bcs::serialized_size(&highest_checkpoint).unwrap_or(0) as u64;
1302 let mut peer_heights = peer_heights.write().unwrap();
1303 peer_heights.record_success(peer_id, size, elapsed);
1304 peer_heights
1305 .update_peer_info(peer_id, highest_checkpoint.clone(), low_watermark)
1306 .then_some(highest_checkpoint)
1307 }
1308 Ok(None) => None,
1310 Err(()) => {
1317 peer_heights.write().unwrap().record_failure(peer_id);
1318 None
1319 }
1320 }
1321 }
1322 })
1323 .collect::<Vec<_>>();
1324
1325 debug!("Query {} peers for latest checkpoint", futs.len());
1326
1327 let checkpoints = futures::future::join_all(futs).await.into_iter().flatten();
1328
1329 let highest_checkpoint_seq = checkpoints
1330 .map(|checkpoint| *checkpoint.sequence_number())
1331 .max();
1332
1333 let our_highest_seq = peer_heights
1334 .read()
1335 .unwrap()
1336 .highest_known_checkpoint_sequence_number();
1337
1338 debug!(
1339 "Our highest checkpoint {our_highest_seq:?}, peers' highest checkpoint {highest_checkpoint_seq:?}"
1340 );
1341
1342 let _new_checkpoint = match (highest_checkpoint_seq, our_highest_seq) {
1343 (Some(theirs), None) => theirs,
1344 (Some(theirs), Some(ours)) if theirs > ours => theirs,
1345 _ => return,
1346 };
1347
1348 if let Some(sender) = sender.upgrade() {
1349 let _ = sender.send(StateSyncMessage::StartSyncJob).await;
1350 }
1351}
1352
1353async fn sync_to_checkpoint<S>(
1354 network: anemo::Network,
1355 store: S,
1356 peer_heights: Arc<RwLock<PeerHeights>>,
1357 metrics: Metrics,
1358 pinned_checkpoints: Vec<(CheckpointSequenceNumber, CheckpointDigest)>,
1359 checkpoint_header_download_concurrency: usize,
1360 timeout: Duration,
1361 target_sequence_number: CheckpointSequenceNumber,
1362) -> Result<()>
1363where
1364 S: WriteStore,
1365{
1366 metrics.set_highest_known_checkpoint(target_sequence_number);
1367
1368 let mut current = store
1369 .get_highest_verified_checkpoint()
1370 .expect("store operation should not fail");
1371 if *current.sequence_number() >= target_sequence_number {
1372 return Err(anyhow::anyhow!(
1373 "target checkpoint {} is older than highest verified checkpoint {}",
1374 target_sequence_number,
1375 current.sequence_number(),
1376 ));
1377 }
1378
1379 let peer_balancer = PeerBalancer::new(
1380 &network,
1381 peer_heights.clone(),
1382 PeerCheckpointRequestType::Summary,
1383 );
1384 let mut request_stream = (current.sequence_number().checked_add(1).unwrap()
1386 ..=target_sequence_number)
1387 .map(|next| {
1388 let peers = peer_balancer.clone().with_checkpoint(next);
1389 let peer_heights = peer_heights.clone();
1390 let pinned_checkpoints = &pinned_checkpoints;
1391 async move {
1392 if let Some(checkpoint) = peer_heights
1393 .read()
1394 .unwrap()
1395 .get_checkpoint_by_sequence_number(next)
1396 {
1397 return (Some(checkpoint.to_owned()), next, None);
1398 }
1399
1400 for mut peer in peers {
1403 let peer_id = peer.inner().peer_id();
1404 let request = Request::new(GetCheckpointSummaryRequest::BySequenceNumber(next))
1405 .with_timeout(timeout);
1406 let start = Instant::now();
1407 let result = peer.get_checkpoint_summary(request).await;
1408 let elapsed = start.elapsed();
1409
1410 let checkpoint = match result {
1411 Ok(response) => match response.into_inner() {
1412 Some(cp) => Some(cp),
1413 None => {
1414 trace!("peer unable to help sync");
1415 peer_heights.write().unwrap().record_failure(peer_id);
1416 None
1417 }
1418 },
1419 Err(e) => {
1420 trace!("{e:?}");
1421 peer_heights.write().unwrap().record_failure(peer_id);
1422 None
1423 }
1424 };
1425
1426 let Some(checkpoint) = checkpoint else {
1427 continue;
1428 };
1429
1430 let size = bcs::serialized_size(&checkpoint).expect("serialization should not fail") as u64;
1431 peer_heights.write().unwrap().record_success(peer_id, size, elapsed);
1432
1433 if *checkpoint.sequence_number() != next {
1435 tracing::debug!(
1436 "peer returned checkpoint with wrong sequence number: expected {next}, got {}",
1437 checkpoint.sequence_number()
1438 );
1439 peer_heights
1440 .write()
1441 .unwrap()
1442 .mark_peer_as_not_on_same_chain(peer_id);
1443 continue;
1444 }
1445
1446 let checkpoint_digest = checkpoint.digest();
1448 if let Ok(pinned_digest_index) = pinned_checkpoints.binary_search_by_key(
1449 checkpoint.sequence_number(),
1450 |(seq_num, _digest)| *seq_num
1451 )
1452 && pinned_checkpoints[pinned_digest_index].1 != *checkpoint_digest
1453 {
1454 tracing::debug!(
1455 "peer returned checkpoint with digest that does not match pinned digest: expected {:?}, got {:?}",
1456 pinned_checkpoints[pinned_digest_index].1,
1457 checkpoint_digest
1458 );
1459 peer_heights
1460 .write()
1461 .unwrap()
1462 .mark_peer_as_not_on_same_chain(peer.inner().peer_id());
1463 continue;
1464 }
1465
1466 peer_heights
1468 .write()
1469 .unwrap()
1470 .insert_checkpoint(checkpoint.clone());
1471 return (Some(checkpoint), next, Some(peer_id));
1472 }
1473 (None, next, None)
1474 }
1475 })
1476 .pipe(futures::stream::iter)
1477 .buffered(checkpoint_header_download_concurrency);
1478
1479 while let Some((maybe_checkpoint, next, maybe_peer_id)) = request_stream.next().await {
1480 assert_eq!(
1481 current
1482 .sequence_number()
1483 .checked_add(1)
1484 .expect("exhausted u64"),
1485 next
1486 );
1487
1488 let checkpoint = 'cp: {
1490 let checkpoint = maybe_checkpoint.ok_or_else(|| {
1491 anyhow::anyhow!("no peers were able to help sync checkpoint {next}")
1492 })?;
1493 if pinned_checkpoints
1495 .binary_search_by_key(checkpoint.sequence_number(), |(seq_num, _digest)| *seq_num)
1496 .is_ok()
1497 {
1498 break 'cp VerifiedCheckpoint::new_unchecked(checkpoint);
1499 }
1500 match verify_checkpoint(¤t, &store, checkpoint) {
1501 Ok(verified_checkpoint) => verified_checkpoint,
1502 Err(checkpoint) => {
1503 let mut peer_heights = peer_heights.write().unwrap();
1504 peer_heights.remove_checkpoint(checkpoint.digest());
1507
1508 if let Some(peer_id) = maybe_peer_id {
1510 peer_heights.mark_peer_as_not_on_same_chain(peer_id);
1511 }
1512
1513 return Err(anyhow::anyhow!(
1514 "unable to verify checkpoint {checkpoint:?}"
1515 ));
1516 }
1517 }
1518 };
1519
1520 debug!(checkpoint_seq = ?checkpoint.sequence_number(), "verified checkpoint summary");
1521 if let Some((checkpoint_summary_age_metric, checkpoint_summary_age_metric_deprecated)) =
1522 metrics.checkpoint_summary_age_metrics()
1523 {
1524 checkpoint.report_checkpoint_age(
1525 checkpoint_summary_age_metric,
1526 checkpoint_summary_age_metric_deprecated,
1527 );
1528 }
1529
1530 store
1533 .insert_checkpoint(&checkpoint)
1534 .expect("store operation should not fail");
1535
1536 current = checkpoint;
1537 }
1538
1539 peer_heights
1540 .write()
1541 .unwrap()
1542 .cleanup_old_checkpoints(*current.sequence_number());
1543
1544 Ok(())
1545}
1546
1547async fn sync_checkpoint_contents_from_archive<S>(
1548 network: anemo::Network,
1549 archive_config: Option<ArchiveReaderConfig>,
1550 store: S,
1551 peer_heights: Arc<RwLock<PeerHeights>>,
1552 metrics: Metrics,
1553) where
1554 S: WriteStore + Clone + Send + Sync + 'static,
1555{
1556 loop {
1557 sync_checkpoint_contents_from_archive_iteration(
1558 &network,
1559 &archive_config,
1560 store.clone(),
1561 peer_heights.clone(),
1562 metrics.clone(),
1563 )
1564 .await;
1565 tokio::time::sleep(Duration::from_secs(5)).await;
1566 }
1567}
1568
1569async fn sync_checkpoint_contents_from_archive_iteration<S>(
1570 network: &anemo::Network,
1571 archive_config: &Option<ArchiveReaderConfig>,
1572 store: S,
1573 peer_heights: Arc<RwLock<PeerHeights>>,
1574 metrics: Metrics,
1575) where
1576 S: WriteStore + Clone + Send + Sync + 'static,
1577{
1578 let peers: Vec<_> = peer_heights
1579 .read()
1580 .unwrap()
1581 .peers_on_same_chain()
1582 .filter_map(|(peer_id, info)| network.peer(*peer_id).map(|peer| (peer, *info)))
1584 .collect();
1585 let lowest_checkpoint_on_peers = peers
1586 .iter()
1587 .map(|(_p, state_sync_info)| state_sync_info.lowest)
1588 .min();
1589 let highest_synced = store
1590 .get_highest_synced_checkpoint()
1591 .expect("store operation should not fail")
1592 .sequence_number;
1593 let sync_from_archive = if let Some(lowest_checkpoint_on_peers) = lowest_checkpoint_on_peers {
1594 highest_synced < lowest_checkpoint_on_peers
1595 } else {
1596 false
1597 };
1598 debug!(
1599 "Syncing checkpoint contents from archive: {sync_from_archive}, highest_synced: {highest_synced}, lowest_checkpoint_on_peers: {}",
1600 lowest_checkpoint_on_peers.map_or_else(|| "None".to_string(), |l| l.to_string())
1601 );
1602 if sync_from_archive {
1603 let start = highest_synced
1604 .checked_add(1)
1605 .expect("Checkpoint seq num overflow");
1606 let end = lowest_checkpoint_on_peers.unwrap();
1607
1608 let Some(archive_config) = archive_config else {
1609 warn!("Failed to find an archive reader to complete the state sync request");
1610 return;
1611 };
1612 let Some(ingestion_url) = &archive_config.ingestion_url else {
1613 warn!("Archival ingestion url for state sync is not configured");
1614 return;
1615 };
1616 if ingestion_url.contains("checkpoints.mainnet.sui.io") {
1617 warn!("{} can't be used as an archival fallback", ingestion_url);
1618 return;
1619 }
1620 let obj_store = build_object_store(
1621 ingestion_url,
1622 archive_config.remote_store_options.clone(),
1623 archive_config.remote_store_headers.clone(),
1624 );
1625 let mut checkpoint_stream = futures::stream::iter(start..=end)
1626 .map(|seq| {
1627 let obj_store = obj_store.clone();
1628 async move { (seq, fetch_checkpoint(&obj_store, seq).await) }
1629 })
1630 .buffered(archive_config.download_concurrency.get());
1631
1632 while let Some((seq, result)) = checkpoint_stream.next().await {
1633 let checkpoint = match result {
1634 Ok(checkpoint) => checkpoint,
1635 Err(err) => {
1636 warn!("State sync from archive failed fetching checkpoint {seq}: {err}");
1637 return;
1638 }
1639 };
1640 if let Err(err) = process_archive_checkpoint(&store, &checkpoint, &metrics) {
1641 warn!("State sync from archive failed processing checkpoint {seq}: {err}");
1642 return;
1643 }
1644 }
1645 }
1646}
1647
1648async fn sync_checkpoint_contents<S>(
1649 network: anemo::Network,
1650 store: S,
1651 peer_heights: Arc<RwLock<PeerHeights>>,
1652 sender: mpsc::WeakSender<StateSyncMessage>,
1653 checkpoint_event_sender: broadcast::Sender<VerifiedCheckpoint>,
1654 checkpoint_content_download_concurrency: usize,
1655 checkpoint_content_download_tx_concurrency: u64,
1656 use_get_checkpoint_contents_v2: bool,
1657 mut target_sequence_channel: watch::Receiver<CheckpointSequenceNumber>,
1658) where
1659 S: WriteStore + Clone,
1660{
1661 let mut highest_synced = store
1662 .get_highest_synced_checkpoint()
1663 .expect("store operation should not fail");
1664
1665 let mut current_sequence = highest_synced.sequence_number().checked_add(1).unwrap();
1666 let mut target_sequence_cursor = 0;
1667 let mut highest_started_network_total_transactions = highest_synced.network_total_transactions;
1668 let mut checkpoint_contents_tasks = FuturesOrdered::new();
1669
1670 let mut tx_concurrency_remaining = checkpoint_content_download_tx_concurrency;
1671
1672 loop {
1673 tokio::select! {
1674 result = target_sequence_channel.changed() => {
1675 match result {
1676 Ok(()) => {
1677 target_sequence_cursor = (*target_sequence_channel.borrow_and_update()).checked_add(1).unwrap();
1678 }
1679 Err(_) => {
1680 return
1682 }
1683 }
1684 },
1685 Some(maybe_checkpoint) = checkpoint_contents_tasks.next() => {
1686 match maybe_checkpoint {
1687 Ok(checkpoint) => {
1688 let _: &VerifiedCheckpoint = &checkpoint; store
1691 .update_highest_synced_checkpoint(&checkpoint)
1692 .expect("store operation should not fail");
1693 let _ = checkpoint_event_sender.send(checkpoint.clone());
1695 tx_concurrency_remaining += checkpoint.network_total_transactions - highest_synced.network_total_transactions;
1696 highest_synced = checkpoint;
1697
1698 }
1699 Err(checkpoint) => {
1700 let _: &VerifiedCheckpoint = &checkpoint; if let Some(lowest_peer_checkpoint) =
1702 peer_heights.read().ok().and_then(|x| x.peers.values().map(|state_sync_info| state_sync_info.lowest).min()) {
1703 if checkpoint.sequence_number() >= &lowest_peer_checkpoint {
1704 info!("unable to sync contents of checkpoint through state sync {} with lowest peer checkpoint: {}", checkpoint.sequence_number(), lowest_peer_checkpoint);
1705 }
1706 } else {
1707 info!("unable to sync contents of checkpoint through state sync {}", checkpoint.sequence_number());
1708
1709 }
1710 let retry_tx_count = if *checkpoint.sequence_number() == 0 {
1712 checkpoint.network_total_transactions
1713 } else {
1714 let prev = store
1715 .get_checkpoint_by_sequence_number(checkpoint.sequence_number() - 1)
1716 .expect("previous checkpoint must exist")
1717 .network_total_transactions;
1718 checkpoint.network_total_transactions - prev
1719 };
1720 checkpoint_contents_tasks.push_front(sync_one_checkpoint_contents(
1722 network.clone(),
1723 &store,
1724 peer_heights.clone(),
1725 use_get_checkpoint_contents_v2,
1726 retry_tx_count,
1727 checkpoint,
1728 ));
1729 }
1730 }
1731 },
1732 }
1733
1734 while current_sequence < target_sequence_cursor
1736 && checkpoint_contents_tasks.len() < checkpoint_content_download_concurrency
1737 {
1738 let next_checkpoint = store
1739 .get_checkpoint_by_sequence_number(current_sequence)
1740 .unwrap_or_else(|| panic!(
1741 "BUG: store should have all checkpoints older than highest_verified_checkpoint (checkpoint {})",
1742 current_sequence
1743 ));
1744
1745 let tx_count = next_checkpoint.network_total_transactions
1747 - highest_started_network_total_transactions;
1748 if tx_count > tx_concurrency_remaining {
1749 break;
1750 }
1751 tx_concurrency_remaining -= tx_count;
1752
1753 highest_started_network_total_transactions = next_checkpoint.network_total_transactions;
1754 current_sequence += 1;
1755 checkpoint_contents_tasks.push_back(sync_one_checkpoint_contents(
1756 network.clone(),
1757 &store,
1758 peer_heights.clone(),
1759 use_get_checkpoint_contents_v2,
1760 tx_count,
1761 next_checkpoint,
1762 ));
1763 }
1764
1765 if highest_synced
1766 .sequence_number()
1767 .is_multiple_of(checkpoint_content_download_concurrency as u64)
1768 || checkpoint_contents_tasks.is_empty()
1769 {
1770 if let Some(sender) = sender.upgrade() {
1772 let message = StateSyncMessage::SyncedCheckpoint(Box::new(highest_synced.clone()));
1773 let _ = sender.send(message).await;
1774 }
1775 }
1776 }
1777}
1778
1779#[allow(clippy::result_large_err)]
1782#[instrument(level = "debug", skip_all, fields(sequence_number = ?checkpoint.sequence_number()))]
1783async fn sync_one_checkpoint_contents<S>(
1784 network: anemo::Network,
1785 store: S,
1786 peer_heights: Arc<RwLock<PeerHeights>>,
1787 use_get_checkpoint_contents_v2: bool,
1788 tx_count: u64,
1789 checkpoint: VerifiedCheckpoint,
1790) -> Result<VerifiedCheckpoint, VerifiedCheckpoint>
1791where
1792 S: WriteStore + Clone,
1793{
1794 let (timeout_min, timeout_max) = {
1795 let ph = peer_heights.read().unwrap();
1796 (
1797 ph.checkpoint_content_timeout_min,
1798 ph.checkpoint_content_timeout_max,
1799 )
1800 };
1801 let timeout = compute_adaptive_timeout(tx_count, timeout_min, timeout_max);
1802 debug!(
1803 "syncing checkpoint contents with adaptive timeout {:?} for {} txns",
1804 timeout, tx_count
1805 );
1806
1807 if store
1810 .get_highest_synced_checkpoint()
1811 .expect("store operation should not fail")
1812 .sequence_number()
1813 >= checkpoint.sequence_number()
1814 {
1815 debug!("checkpoint was already created via consensus output");
1816 return Ok(checkpoint);
1817 }
1818
1819 let peers = PeerBalancer::new(
1821 &network,
1822 peer_heights.clone(),
1823 PeerCheckpointRequestType::Content,
1824 )
1825 .with_checkpoint(*checkpoint.sequence_number());
1826 let now = tokio::time::Instant::now();
1827 let Some(_contents) = get_full_checkpoint_contents(
1828 peers,
1829 &store,
1830 peer_heights.clone(),
1831 &checkpoint,
1832 use_get_checkpoint_contents_v2,
1833 timeout,
1834 )
1835 .await
1836 else {
1837 let duration = peer_heights
1839 .read()
1840 .unwrap()
1841 .wait_interval_when_no_peer_to_sync_content();
1842 if now.elapsed() < duration {
1843 let duration = duration - now.elapsed();
1844 info!("retrying checkpoint sync after {:?}", duration);
1845 tokio::time::sleep(duration).await;
1846 }
1847 return Err(checkpoint);
1848 };
1849 debug!("completed checkpoint contents sync");
1850 Ok(checkpoint)
1851}
1852
1853#[instrument(level = "debug", skip_all)]
1854async fn get_full_checkpoint_contents<S>(
1855 peers: PeerBalancer,
1856 store: S,
1857 peer_heights: Arc<RwLock<PeerHeights>>,
1858 checkpoint: &VerifiedCheckpoint,
1859 use_get_checkpoint_contents_v2: bool,
1860 timeout: Duration,
1861) -> Option<VersionedFullCheckpointContents>
1862where
1863 S: WriteStore,
1864{
1865 let sequence_number = checkpoint.sequence_number;
1866 let digest = checkpoint.content_digest;
1867 if let Some(contents) = store.get_full_checkpoint_contents(Some(sequence_number), &digest) {
1868 debug!("store already contains checkpoint contents");
1869 return Some(contents);
1870 }
1871
1872 for mut peer in peers {
1875 let peer_id = peer.inner().peer_id();
1876 debug!(?timeout, "requesting checkpoint contents from {}", peer_id);
1877 let request = Request::new(digest).with_timeout(timeout);
1878 let start = Instant::now();
1879 let result = if use_get_checkpoint_contents_v2 {
1880 peer.get_checkpoint_contents_v2(request).await
1881 } else {
1882 peer.get_checkpoint_contents(request)
1883 .await
1884 .map(|r| r.map(|c| c.map(VersionedFullCheckpointContents::V1)))
1885 };
1886 let elapsed = start.elapsed();
1887
1888 let contents = match result {
1889 Ok(response) => match response.into_inner() {
1890 Some(c) => Some(c),
1891 None => {
1892 trace!("peer unable to help sync");
1893 peer_heights.write().unwrap().record_failure(peer_id);
1894 None
1895 }
1896 },
1897 Err(e) => {
1898 trace!("{e:?}");
1899 peer_heights.write().unwrap().record_failure(peer_id);
1900 None
1901 }
1902 };
1903
1904 let Some(contents) = contents else {
1905 continue;
1906 };
1907
1908 if contents.verify_digests(digest).is_ok() {
1909 let size =
1910 bcs::serialized_size(&contents).expect("serialization should not fail") as u64;
1911 peer_heights
1912 .write()
1913 .unwrap()
1914 .record_success(peer_id, size, elapsed);
1915 let verified_contents = VerifiedCheckpointContents::new_unchecked(contents.clone());
1916 store
1917 .insert_checkpoint_contents(checkpoint, verified_contents)
1918 .expect("store operation should not fail");
1919 return Some(contents);
1920 }
1921 }
1922 debug!("no peers had checkpoint contents");
1923 None
1924}
1925
1926async fn update_checkpoint_watermark_metrics<S>(
1927 mut recv: oneshot::Receiver<()>,
1928 store: S,
1929 metrics: Metrics,
1930) -> Result<()>
1931where
1932 S: WriteStore + Clone + Send + Sync,
1933{
1934 let mut interval = tokio::time::interval(Duration::from_secs(5));
1935 loop {
1936 tokio::select! {
1937 _now = interval.tick() => {
1938 let highest_verified_checkpoint = store.get_highest_verified_checkpoint()
1939 .expect("store operation should not fail");
1940 metrics.set_highest_verified_checkpoint(highest_verified_checkpoint.sequence_number);
1941 let highest_synced_checkpoint = store.get_highest_synced_checkpoint()
1942 .expect("store operation should not fail");
1943 metrics.set_highest_synced_checkpoint(highest_synced_checkpoint.sequence_number);
1944 },
1945 _ = &mut recv => break,
1946 }
1947 }
1948 Ok(())
1949}