1use anyhow::{Context, Result};
6use backoff::ExponentialBackoff;
7use fastcrypto::traits::ToFromBytes;
8use futures::future::AbortHandle;
9use futures::future::join_all;
10use itertools::Itertools;
11use std::collections::BTreeMap;
12use std::fmt::Write;
13use std::net::{IpAddr, Ipv4Addr, SocketAddr};
14use std::num::NonZeroUsize;
15use std::path::{Path, PathBuf};
16use std::sync::Arc;
17use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
18use std::time::Duration;
19use std::{fs, io};
20use sui_config::{NodeConfig, genesis::Genesis};
21use sui_core::authority_client::{AuthorityAPI, NetworkAuthorityClient};
22use sui_core::execution_cache::build_execution_cache_from_env;
23use sui_network::default_mysten_network_config;
24use sui_protocol_config::Chain;
25use sui_rpc_api::Client;
26use sui_storage::object_store::http::HttpDownloaderBuilder;
27use sui_storage::object_store::util::MANIFEST_FILENAME;
28use sui_storage::object_store::util::Manifest;
29use sui_storage::object_store::util::{build_object_store, end_of_epoch_data, fetch_checkpoint};
30use sui_types::committee::QUORUM_THRESHOLD;
31use sui_types::crypto::AuthorityPublicKeyBytes;
32use sui_types::digests::ChainIdentifier;
33use sui_types::global_state_hash::GlobalStateHash;
34use sui_types::messages_grpc::LayoutGenerationOption;
35use sui_types::multiaddr::Multiaddr;
36use sui_types::{base_types::*, object::Owner};
37use tokio::sync::{mpsc, oneshot};
38use tokio::task::JoinHandle;
39use tokio::time::Instant;
40
41use anyhow::anyhow;
42use clap::ValueEnum;
43use eyre::ContextCompat;
44use fastcrypto::hash::MultisetHash;
45use futures::{StreamExt, TryStreamExt};
46use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
47use prometheus::Registry;
48use serde::{Deserialize, Serialize};
49use sui_config::object_storage_config::{ObjectStoreConfig, ObjectStoreType};
50use sui_core::authority::AuthorityStore;
51use sui_core::authority::authority_store_tables::AuthorityPerpetualTables;
52use sui_core::checkpoints::CheckpointStore;
53use sui_core::epoch::committee_store::CommitteeStore;
54use sui_core::storage::RocksDbStore;
55use sui_snapshot::reader::{StateAccumulatorSender, StateSnapshotReaderV1};
56use sui_snapshot::setup_db_state;
57use sui_storage::object_store::ObjectStoreGetExt;
58use sui_storage::object_store::util::{exists, get_path};
59use sui_types::full_checkpoint_content::CheckpointData;
60use sui_types::messages_checkpoint::{CheckpointCommitment, ECMHLiveObjectSetDigest};
61use sui_types::messages_grpc::{
62 ObjectInfoRequest, ObjectInfoRequestKind, ObjectInfoResponse, TransactionInfoRequest,
63 TransactionStatus,
64};
65
66use crate::formal_snapshot_util::read_summaries_for_list_no_verify;
67use sui_core::authority::authority_store_pruner::PrunerWatermarks;
68use sui_types::storage::ReadStore;
69use tracing::info;
70use typed_store::DBMetrics;
71
72pub mod commands;
73pub mod db_shell;
74pub mod db_tool;
75mod formal_snapshot_util;
76#[cfg(all(feature = "tideconsole", not(windows)))]
77pub mod tideconsole_cmd;
78
79#[derive(
80 Clone, Serialize, Deserialize, Debug, PartialEq, Copy, PartialOrd, Ord, Eq, ValueEnum, Default,
81)]
82pub enum SnapshotVerifyMode {
83 None,
88 #[default]
91 Normal,
92 Strict,
95}
96
97async fn make_clients(
99 sui_client: &Client,
100) -> Result<BTreeMap<AuthorityName, (Multiaddr, NetworkAuthorityClient)>> {
101 let mut net_config = default_mysten_network_config();
102 net_config.connect_timeout = Some(Duration::from_secs(5));
103 let mut authority_clients = BTreeMap::new();
104
105 let active_validators = sui_client
106 .get_system_state_summary(None)
107 .await?
108 .active_validators;
109
110 for validator in active_validators {
111 let net_addr = Multiaddr::try_from(validator.net_address)
112 .unwrap()
113 .rewrite_http_to_https();
114 let tls_config = sui_tls::create_rustls_client_config(
115 sui_types::crypto::NetworkPublicKey::from_bytes(&validator.network_pubkey_bytes)?,
116 sui_tls::SUI_VALIDATOR_SERVER_NAME.to_string(),
117 None,
118 );
119 let channel = net_config
120 .connect_lazy(&net_addr, tls_config)
121 .map_err(|err| anyhow!(err.to_string()))?;
122 let client = NetworkAuthorityClient::new(channel);
123 let public_key_bytes =
124 AuthorityPublicKeyBytes::from_bytes(&validator.protocol_pubkey_bytes)?;
125 authority_clients.insert(public_key_bytes, (net_addr.clone(), client));
126 }
127
128 Ok(authority_clients)
129}
130
131type ObjectVersionResponses = (Option<SequenceNumber>, Result<ObjectInfoResponse>, f64);
132pub struct ObjectData {
133 requested_id: ObjectID,
134 responses: Vec<(AuthorityName, Multiaddr, ObjectVersionResponses)>,
135}
136
137trait OptionDebug<T> {
138 fn opt_debug(&self, def_str: &str) -> String;
139}
140
141impl<T> OptionDebug<T> for Option<T>
142where
143 T: std::fmt::Debug,
144{
145 fn opt_debug(&self, def_str: &str) -> String {
146 match self {
147 None => def_str.to_string(),
148 Some(t) => format!("{:?}", t),
149 }
150 }
151}
152
153#[allow(clippy::type_complexity)]
154pub struct GroupedObjectOutput {
155 pub grouped_results: BTreeMap<
156 Option<(
157 Option<SequenceNumber>,
158 ObjectDigest,
159 TransactionDigest,
160 Owner,
161 Option<TransactionDigest>,
162 )>,
163 Vec<AuthorityName>,
164 >,
165 pub voting_power: Vec<(
166 Option<(
167 Option<SequenceNumber>,
168 ObjectDigest,
169 TransactionDigest,
170 Owner,
171 Option<TransactionDigest>,
172 )>,
173 u64,
174 )>,
175 pub available_voting_power: u64,
176 pub fully_locked: bool,
177}
178
179impl GroupedObjectOutput {
180 pub fn new(
181 object_data: ObjectData,
182 committee: Arc<BTreeMap<AuthorityPublicKeyBytes, u64>>,
183 ) -> Self {
184 let mut grouped_results = BTreeMap::new();
185 let mut voting_power = BTreeMap::new();
186 let mut available_voting_power = 0;
187 for (name, _, (version, resp, _elapsed)) in &object_data.responses {
188 let stake = committee.get(name).unwrap();
189 let key = match resp {
190 Ok(r) => {
191 let obj_digest = r.object.compute_object_reference().2;
192 let parent_tx_digest = r.object.previous_transaction;
193 let owner = r.object.owner.clone();
194 let lock = r.lock_for_debugging.as_ref().map(|lock| *lock.digest());
195 if lock.is_none() {
196 available_voting_power += stake;
197 }
198 Some((*version, obj_digest, parent_tx_digest, owner, lock))
199 }
200 Err(_) => None,
201 };
202 let entry = grouped_results.entry(key.clone()).or_insert_with(Vec::new);
203 entry.push(*name);
204 let entry: &mut u64 = voting_power.entry(key).or_default();
205 *entry += stake;
206 }
207 let voting_power = voting_power
208 .into_iter()
209 .sorted_by(|(_, v1), (_, v2)| Ord::cmp(v2, v1))
210 .collect::<Vec<_>>();
211 let mut fully_locked = false;
212 if !voting_power.is_empty()
213 && voting_power.first().unwrap().1 + available_voting_power < QUORUM_THRESHOLD
214 {
215 fully_locked = true;
216 }
217 Self {
218 grouped_results,
219 voting_power,
220 available_voting_power,
221 fully_locked,
222 }
223 }
224}
225
226#[allow(clippy::format_in_format_args)]
227impl std::fmt::Display for GroupedObjectOutput {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 writeln!(f, "available stake: {}", self.available_voting_power)?;
230 writeln!(f, "fully locked: {}", self.fully_locked)?;
231 writeln!(f, "{:<100}\n", "-".repeat(100))?;
232 for (key, stake) in &self.voting_power {
233 let val = self.grouped_results.get(key).unwrap();
234 writeln!(f, "total stake: {stake}")?;
235 match key {
236 Some((_version, obj_digest, parent_tx_digest, owner, lock)) => {
237 let lock = lock.opt_debug("no-known-lock");
238 writeln!(f, "obj ref: {obj_digest}")?;
239 writeln!(f, "parent tx: {parent_tx_digest}")?;
240 writeln!(f, "owner: {owner}")?;
241 writeln!(f, "lock: {lock}")?;
242 for (i, name) in val.iter().enumerate() {
243 writeln!(f, " {:<4} {:<20}", i, name.concise(),)?;
244 }
245 }
246 None => {
247 writeln!(f, "ERROR")?;
248 for (i, name) in val.iter().enumerate() {
249 writeln!(f, " {:<4} {:<20}", i, name.concise(),)?;
250 }
251 }
252 };
253 writeln!(f, "{:<100}\n", "-".repeat(100))?;
254 }
255 Ok(())
256 }
257}
258
259struct ConciseObjectOutput(ObjectData);
260
261impl ConciseObjectOutput {
262 fn header() -> String {
263 format!(
264 "{:<20} {:<8} {:<66} {:<45} {}",
265 "validator", "version", "digest", "parent_cert", "owner"
266 )
267 }
268}
269
270impl std::fmt::Display for ConciseObjectOutput {
271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272 for (name, _multi_addr, (version, resp, _time_elapsed)) in &self.0.responses {
273 write!(
274 f,
275 "{:<20} {:<8}",
276 format!("{:?}", name.concise()),
277 version.map(|s| s.value()).opt_debug("-")
278 )?;
279 match resp {
280 Err(_) => writeln!(
281 f,
282 "{:<66} {:<45} {:<51}",
283 "object-fetch-failed", "no-cert-available", "no-owner-available"
284 )?,
285 Ok(resp) => {
286 let obj_digest = resp.object.compute_object_reference().2;
287 let parent = resp.object.previous_transaction;
288 let owner = resp.object.owner.clone();
289 write!(f, " {:<66} {:<45} {:<51}", obj_digest, parent, owner)?;
290 }
291 }
292 writeln!(f)?;
293 }
294 Ok(())
295 }
296}
297
298struct VerboseObjectOutput(ObjectData);
299
300impl std::fmt::Display for VerboseObjectOutput {
301 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302 writeln!(f, "Object: {}", self.0.requested_id)?;
303
304 for (name, multiaddr, (version, resp, timespent)) in &self.0.responses {
305 writeln!(f, "validator: {:?}, addr: {:?}", name.concise(), multiaddr)?;
306 writeln!(
307 f,
308 "-- version: {} ({:.3}s)",
309 version.opt_debug("<version not available>"),
310 timespent,
311 )?;
312
313 match resp {
314 Err(e) => writeln!(f, "Error fetching object: {}", e)?,
315 Ok(resp) => {
316 writeln!(
317 f,
318 " -- object digest: {}",
319 resp.object.compute_object_reference().2
320 )?;
321 if resp.object.is_package() {
322 writeln!(f, " -- object: <Move Package>")?;
323 } else if let Some(layout) = &resp.layout {
324 writeln!(
325 f,
326 " -- object: Move Object: {}",
327 resp.object
328 .data
329 .try_as_move()
330 .unwrap()
331 .to_move_struct(layout)
332 .unwrap()
333 )?;
334 }
335 writeln!(f, " -- owner: {}", resp.object.owner)?;
336 writeln!(
337 f,
338 " -- locked by: {}",
339 resp.lock_for_debugging.opt_debug("<not locked>")
340 )?;
341 }
342 }
343 }
344 Ok(())
345 }
346}
347
348pub async fn get_object(
349 obj_id: ObjectID,
350 version: Option<u64>,
351 validator: Option<AuthorityName>,
352 clients: Arc<BTreeMap<AuthorityName, (Multiaddr, NetworkAuthorityClient)>>,
353) -> Result<ObjectData> {
354 let responses = join_all(
355 clients
356 .iter()
357 .filter(|(name, _)| {
358 if let Some(v) = validator {
359 v == **name
360 } else {
361 true
362 }
363 })
364 .map(|(name, (address, client))| async {
365 let object_version = get_object_impl(client, obj_id, version).await;
366 (*name, address.clone(), object_version)
367 }),
368 )
369 .await;
370
371 Ok(ObjectData {
372 requested_id: obj_id,
373 responses,
374 })
375}
376
377pub async fn get_transaction_block(
378 tx_digest: TransactionDigest,
379 show_input_tx: bool,
380 fullnode_rpc: String,
381) -> Result<String> {
382 let sui_client = Client::new(fullnode_rpc)?;
383 let clients = make_clients(&sui_client).await?;
384 let timer = Instant::now();
385 let responses = join_all(clients.iter().map(|(name, (address, client))| async {
386 let result = client
387 .handle_transaction_info_request(TransactionInfoRequest {
388 transaction_digest: tx_digest,
389 })
390 .await;
391 (
392 *name,
393 address.clone(),
394 result,
395 timer.elapsed().as_secs_f64(),
396 )
397 }))
398 .await;
399
400 let validator_aware_of_tx = responses.iter().find(|r| r.2.is_ok());
402
403 let responses = responses
404 .iter()
405 .map(|r| {
406 let key =
407 r.2.as_ref()
408 .map(|ok_result| match &ok_result.status {
409 TransactionStatus::Signed(_) => None,
410 TransactionStatus::Executed(_, effects, _) => Some(effects.digest()),
411 })
412 .ok();
413 let err = r.2.as_ref().err();
414 (key, err, r)
415 })
416 .sorted_by(|(k1, err1, _), (k2, err2, _)| {
417 Ord::cmp(k1, k2).then_with(|| Ord::cmp(err1, err2))
418 })
419 .chunk_by(|(_, _err, r)| {
420 r.2.as_ref().map(|ok_result| match &ok_result.status {
421 TransactionStatus::Signed(_) => None,
422 TransactionStatus::Executed(_, effects, _) => Some((
423 ok_result.transaction.transaction_data(),
424 effects.data(),
425 effects.digest(),
426 )),
427 })
428 });
429 let mut s = String::new();
430 for (i, (key, group)) in responses.into_iter().enumerate() {
431 match key {
432 Ok(Some((tx, effects, effects_digest))) => {
433 writeln!(
434 &mut s,
435 "#{:<2} tx_digest: {:<68?} effects_digest: {:?}",
436 i, tx_digest, effects_digest,
437 )?;
438 writeln!(&mut s, "{:#?}", effects)?;
439 if show_input_tx {
440 writeln!(&mut s, "{:#?}", tx)?;
441 }
442 }
443 Ok(None) => {
444 writeln!(
445 &mut s,
446 "#{:<2} tx_digest: {:<68?} Signed but not executed",
447 i, tx_digest
448 )?;
449 if show_input_tx {
450 let validator_aware_of_tx = validator_aware_of_tx.unwrap();
452 let client = &clients.get(&validator_aware_of_tx.0).unwrap().1;
453 let tx_info = client.handle_transaction_info_request(TransactionInfoRequest {
454 transaction_digest: tx_digest,
455 }).await.unwrap_or_else(|e| panic!("Validator {:?} should have known about tx_digest: {:?}, got error: {:?}", validator_aware_of_tx.0, tx_digest, e));
456 writeln!(&mut s, "{:#?}", tx_info)?;
457 }
458 }
459 other => {
460 writeln!(&mut s, "#{:<2} {:#?}", i, other)?;
461 }
462 }
463 for (j, (_, _, res)) in group.enumerate() {
464 writeln!(
465 &mut s,
466 " {:<4} {:<20} {:<56} ({:.3}s)",
467 j,
468 res.0.concise(),
469 format!("{}", res.1),
470 res.3
471 )?;
472 }
473 writeln!(&mut s, "{:<100}\n", "-".repeat(100))?;
474 }
475 Ok(s)
476}
477
478async fn get_object_impl(
479 client: &NetworkAuthorityClient,
480 id: ObjectID,
481 version: Option<u64>,
482) -> (Option<SequenceNumber>, Result<ObjectInfoResponse>, f64) {
483 let start = Instant::now();
484 let resp = client
485 .handle_object_info_request(ObjectInfoRequest {
486 object_id: id,
487 generate_layout: LayoutGenerationOption::Generate,
488 request_kind: match version {
489 None => ObjectInfoRequestKind::LatestObjectInfo,
490 Some(v) => ObjectInfoRequestKind::PastObjectInfoDebug(SequenceNumber::from_u64(v)),
491 },
492 })
493 .await
494 .map_err(anyhow::Error::from);
495 let elapsed = start.elapsed().as_secs_f64();
496
497 let resp_version = resp.as_ref().ok().map(|r| r.object.version().value());
498 (resp_version.map(SequenceNumber::from), resp, elapsed)
499}
500
501pub(crate) fn make_anemo_config() -> anemo_cli::Config {
502 use sui_network::discovery::*;
503 use sui_network::state_sync::*;
504
505 anemo_cli::Config::new()
507 .add_service(
509 "Discovery",
510 anemo_cli::ServiceInfo::new().add_method(
511 "GetKnownPeersV2",
512 anemo_cli::ron_method!(DiscoveryClient, get_known_peers_v2, ()),
513 ),
514 )
515 .add_service(
517 "StateSync",
518 anemo_cli::ServiceInfo::new()
519 .add_method(
520 "PushCheckpointSummary",
521 anemo_cli::ron_method!(
522 StateSyncClient,
523 push_checkpoint_summary,
524 sui_types::messages_checkpoint::CertifiedCheckpointSummary
525 ),
526 )
527 .add_method(
528 "GetCheckpointSummary",
529 anemo_cli::ron_method!(
530 StateSyncClient,
531 get_checkpoint_summary,
532 GetCheckpointSummaryRequest
533 ),
534 )
535 .add_method(
536 "GetCheckpointContents",
537 anemo_cli::ron_method!(
538 StateSyncClient,
539 get_checkpoint_contents,
540 sui_types::messages_checkpoint::CheckpointContentsDigest
541 ),
542 )
543 .add_method(
544 "GetCheckpointAvailability",
545 anemo_cli::ron_method!(StateSyncClient, get_checkpoint_availability, ()),
546 ),
547 )
548}
549
550fn copy_dir_all(
551 src: impl AsRef<Path>,
552 dst: impl AsRef<Path>,
553 skip: Vec<PathBuf>,
554) -> io::Result<()> {
555 fs::create_dir_all(&dst)?;
556 for entry in fs::read_dir(src)? {
557 let entry = entry?;
558 let ty = entry.file_type()?;
559 if skip.contains(&entry.path()) {
560 continue;
561 }
562 if ty.is_dir() {
563 copy_dir_all(
564 entry.path(),
565 dst.as_ref().join(entry.file_name()),
566 skip.clone(),
567 )?;
568 } else {
569 fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
570 }
571 }
572 Ok(())
573}
574
575pub async fn restore_from_db_checkpoint(
576 config: &NodeConfig,
577 db_checkpoint_path: &Path,
578) -> Result<(), anyhow::Error> {
579 copy_dir_all(db_checkpoint_path, config.db_path(), vec![])?;
580 Ok(())
581}
582
583fn start_summary_sync(
584 perpetual_db: Arc<AuthorityPerpetualTables>,
585 committee_store: Arc<CommitteeStore>,
586 checkpoint_store: Arc<CheckpointStore>,
587 m: MultiProgress,
588 genesis: Genesis,
589 ingestion_url: String,
590 num_parallel_downloads: usize,
591 verify: bool,
592 end_of_epoch_checkpoint_seq_nums: Vec<u64>,
593) -> JoinHandle<Result<(), anyhow::Error>> {
594 tokio::spawn(async move {
595 let store = AuthorityStore::open_no_genesis(perpetual_db, false, &Registry::default())?;
596 let cache_traits = build_execution_cache_from_env(&Registry::default(), &store);
597 let state_sync_store =
598 RocksDbStore::new(cache_traits, committee_store, checkpoint_store.clone());
599 if checkpoint_store
601 .get_checkpoint_by_digest(genesis.checkpoint().digest())
602 .unwrap()
603 .is_none()
604 {
605 checkpoint_store.insert_checkpoint_contents(genesis.checkpoint_contents().clone())?;
606 checkpoint_store.insert_verified_checkpoint(&genesis.checkpoint())?;
607 checkpoint_store.update_highest_synced_checkpoint(&genesis.checkpoint())?;
608 }
609
610 let last_checkpoint = end_of_epoch_checkpoint_seq_nums
611 .last()
612 .expect("Expected at least one checkpoint");
613
614 let num_to_sync = end_of_epoch_checkpoint_seq_nums.len() as u64;
615 let sync_progress_bar = m.add(
616 ProgressBar::new(num_to_sync).with_style(
617 ProgressStyle::with_template("[{elapsed_precise}] {wide_bar} {pos}/{len} ({msg})")
618 .unwrap(),
619 ),
620 );
621
622 let cloned_progress_bar = sync_progress_bar.clone();
623 let sync_checkpoint_counter = Arc::new(AtomicU64::new(0));
624 let s_instant = Instant::now();
625
626 let cloned_counter = sync_checkpoint_counter.clone();
627 let latest_synced = checkpoint_store
628 .get_highest_synced_checkpoint()?
629 .map(|c| c.sequence_number)
630 .unwrap_or(0);
631 let s_start = latest_synced
632 .checked_add(1)
633 .wrap_err("Checkpoint overflow")
634 .map_err(|_| anyhow!("Failed to increment checkpoint"))?;
635 tokio::spawn(async move {
636 loop {
637 if cloned_progress_bar.is_finished() {
638 break;
639 }
640 let num_summaries = cloned_counter.load(Ordering::Relaxed);
641 let total_checkpoints_per_sec =
642 num_summaries as f64 / s_instant.elapsed().as_secs_f64();
643 cloned_progress_bar.set_position(s_start + num_summaries);
644 cloned_progress_bar.set_message(format!(
645 "checkpoints synced per sec: {}",
646 total_checkpoints_per_sec
647 ));
648 tokio::time::sleep(Duration::from_secs(1)).await;
649 }
650 });
651
652 read_summaries_for_list_no_verify(
653 ingestion_url,
654 num_parallel_downloads,
655 state_sync_store.clone(),
656 end_of_epoch_checkpoint_seq_nums.clone(),
657 sync_checkpoint_counter,
658 )
659 .await?;
660 sync_progress_bar.finish_with_message("Checkpoint summary sync is complete");
661 info!("Checkpoint summary sync is complete");
662
663 let checkpoint = checkpoint_store
664 .get_checkpoint_by_sequence_number(*last_checkpoint)?
665 .ok_or(anyhow!("Failed to read last checkpoint"))?;
666 if verify {
667 let verify_progress_bar = m.add(
668 ProgressBar::new(num_to_sync).with_style(
669 ProgressStyle::with_template(
670 "[{elapsed_precise}] {wide_bar} {pos}/{len} ({msg})",
671 )
672 .unwrap(),
673 ),
674 );
675 let cloned_verify_progress_bar = verify_progress_bar.clone();
676 let verify_checkpoint_counter = Arc::new(AtomicU64::new(0));
677 let cloned_verify_counter = verify_checkpoint_counter.clone();
678 let v_instant = Instant::now();
679
680 tokio::spawn(async move {
681 loop {
682 if cloned_verify_progress_bar.is_finished() {
683 break;
684 }
685 let num_summaries = cloned_verify_counter.load(Ordering::Relaxed);
686 let total_checkpoints_per_sec =
687 num_summaries as f64 / v_instant.elapsed().as_secs_f64();
688 cloned_verify_progress_bar.set_position(num_summaries);
689 cloned_verify_progress_bar.set_message(format!(
690 "checkpoints verified per sec: {}",
691 total_checkpoints_per_sec
692 ));
693 tokio::time::sleep(Duration::from_secs(1)).await;
694 }
695 });
696
697 for (cp_epoch, epoch_last_cp_seq_num) in
698 end_of_epoch_checkpoint_seq_nums.iter().enumerate()
699 {
700 let epoch_last_checkpoint = checkpoint_store
701 .get_checkpoint_by_sequence_number(*epoch_last_cp_seq_num)?
702 .ok_or(anyhow!("Failed to read checkpoint"))?;
703 let committee = state_sync_store.get_committee(cp_epoch as u64).expect(
704 "Expected committee to exist after syncing all end of epoch checkpoints",
705 );
706 epoch_last_checkpoint
707 .verify_authority_signatures(&committee)
708 .expect("Failed to verify checkpoint");
709 verify_checkpoint_counter.fetch_add(1, Ordering::Relaxed);
710 }
711
712 verify_progress_bar.finish_with_message("Checkpoint summary verification is complete");
713 }
714
715 checkpoint_store.update_highest_verified_checkpoint(&checkpoint)?;
716 checkpoint_store.update_highest_synced_checkpoint(&checkpoint)?;
717 checkpoint_store.update_highest_executed_checkpoint(&checkpoint)?;
718 checkpoint_store.update_highest_pruned_checkpoint(&checkpoint)?;
719 Ok::<(), anyhow::Error>(())
720 })
721}
722
723pub async fn get_latest_available_epoch(
724 snapshot_store_config: &ObjectStoreConfig,
725) -> Result<u64, anyhow::Error> {
726 let remote_object_store = if snapshot_store_config.no_sign_request {
727 snapshot_store_config.make_http()?
728 } else {
729 snapshot_store_config.make().map(Arc::new)?
730 };
731 let manifest_contents = remote_object_store
732 .get_bytes(&get_path(MANIFEST_FILENAME))
733 .await?;
734 let root_manifest: Manifest = serde_json::from_slice(&manifest_contents)
735 .map_err(|err| anyhow!("Error parsing MANIFEST from bytes: {}", err))?;
736 let epoch = root_manifest
737 .available_epochs
738 .iter()
739 .max()
740 .ok_or(anyhow!("No snapshot found in manifest"))?;
741 Ok(*epoch)
742}
743
744pub async fn check_completed_snapshot(
745 snapshot_store_config: &ObjectStoreConfig,
746 epoch: EpochId,
747) -> Result<(), anyhow::Error> {
748 let success_marker = format!("epoch_{}/_SUCCESS", epoch);
749 let archive_success_marker = format!("archive/epoch_{}/_SUCCESS", epoch);
750 let remote_object_store = if snapshot_store_config.no_sign_request {
751 snapshot_store_config.make_http()?
752 } else {
753 snapshot_store_config.make().map(Arc::new)?
754 };
755
756 if exists(&remote_object_store, &get_path(success_marker.as_str())).await
758 || exists(
759 &remote_object_store,
760 &get_path(archive_success_marker.as_str()),
761 )
762 .await
763 {
764 Ok(())
765 } else {
766 Err(anyhow!(
767 "missing success marker at {}/{} or {}/{}",
768 snapshot_store_config.bucket.as_ref().unwrap_or(
769 &snapshot_store_config
770 .clone()
771 .aws_endpoint
772 .unwrap_or("unknown_bucket".to_string())
773 ),
774 success_marker,
775 snapshot_store_config.bucket.as_ref().unwrap_or(
776 &snapshot_store_config
777 .clone()
778 .aws_endpoint
779 .unwrap_or("unknown_bucket".to_string())
780 ),
781 archive_success_marker
782 ))
783 }
784}
785
786pub async fn download_formal_snapshot(
787 path: &Path,
788 epoch: EpochId,
789 genesis: &Path,
790 snapshot_store_config: ObjectStoreConfig,
791 ingestion_url: &str,
792 num_parallel_downloads: usize,
793 num_parallel_chunks: usize,
794 network: Chain,
795 verify: SnapshotVerifyMode,
796 max_retries: usize,
797 metrics_port: u16,
798) -> Result<(), anyhow::Error> {
799 let m = MultiProgress::new();
800 let msg = format!(
801 "Beginning formal snapshot restore to end of epoch {}, network: {:?}, verification mode: {:?}",
802 epoch, network, verify
803 );
804 m.println(&msg).unwrap();
805 info!("{}", msg);
806
807 let path = path.join("staging").to_path_buf();
808 if path.exists() {
809 fs::remove_dir_all(path.clone())?;
810 }
811
812 let metrics_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), metrics_port);
814 let registry_service = mysten_metrics::start_prometheus_server(metrics_addr);
815 let prometheus_registry = registry_service.default_registry();
816 DBMetrics::init(registry_service.clone());
817 mysten_metrics::init_metrics(&prometheus_registry);
818
819 let perpetual_db = Arc::new(AuthorityPerpetualTables::open(
820 &path.join("store"),
821 None,
822 None,
823 ));
824 let genesis = Genesis::load(genesis)?;
825 let genesis_chain = ChainIdentifier::from(*genesis.checkpoint().digest()).chain();
826 if genesis_chain != network {
827 return Err(anyhow!(
828 "Genesis file is for chain {}, but formal snapshot download is configured for --network {}. \
829 Use the matching genesis.blob or pass the correct --network flag.",
830 genesis_chain.as_str(),
831 network.as_str(),
832 ));
833 }
834 let genesis_committee = genesis.committee();
835 let committee_store = Arc::new(CommitteeStore::new(
836 path.join("epochs"),
837 &genesis_committee,
838 None,
839 ));
840 let checkpoint_store = CheckpointStore::new(
841 &path.join("checkpoints"),
842 Arc::new(PrunerWatermarks::default()),
843 );
844
845 let end_of_epoch_checkpoint_seq_nums: Vec<_> = end_of_epoch_data(ingestion_url, vec![])
846 .await?
847 .into_iter()
848 .take((epoch + 1) as usize)
849 .collect();
850
851 let summaries_handle = start_summary_sync(
852 perpetual_db.clone(),
853 committee_store.clone(),
854 checkpoint_store.clone(),
855 m.clone(),
856 genesis.clone(),
857 ingestion_url.to_string(),
858 num_parallel_downloads,
859 verify != SnapshotVerifyMode::None,
860 end_of_epoch_checkpoint_seq_nums.clone(),
861 );
862
863 let backfill_handle = {
865 let perpetual_db = perpetual_db.clone();
866 let ingestion_url = ingestion_url.to_string();
867 let m = m.clone();
868 let end_of_epoch_checkpoint_seq_nums = end_of_epoch_checkpoint_seq_nums.clone();
869 tokio::spawn(async move {
870 backfill_epoch_transaction_digests(
871 perpetual_db,
872 epoch,
873 ingestion_url,
874 num_parallel_downloads,
875 max_retries,
876 m,
877 end_of_epoch_checkpoint_seq_nums,
878 )
879 .await
880 })
881 };
882
883 let (_abort_handle, abort_registration) = AbortHandle::new_pair();
884 let perpetual_db_clone = perpetual_db.clone();
885 let snapshot_dir = path.parent().unwrap().join("snapshot");
886 if snapshot_dir.exists() {
887 fs::remove_dir_all(snapshot_dir.clone())?;
888 }
889 let snapshot_dir_clone = snapshot_dir.clone();
890
891 let (sender, mut receiver) = mpsc::channel(num_parallel_downloads);
894 let (accumulation_done_sender, accumulation_done_receiver) = oneshot::channel();
895 let m_clone = m.clone();
896
897 let snapshot_handle = tokio::spawn(async move {
898 let local_store_config = ObjectStoreConfig {
899 object_store: Some(ObjectStoreType::File),
900 directory: Some(snapshot_dir_clone.to_path_buf()),
901 ..Default::default()
902 };
903 let mut reader = StateSnapshotReaderV1::new(
904 epoch,
905 &snapshot_store_config,
906 &local_store_config,
907 NonZeroUsize::new(num_parallel_downloads).unwrap(),
908 m_clone,
909 false, max_retries,
911 num_parallel_chunks,
912 )
913 .await
914 .context("Failed to create snapshot reader")?;
915 reader
916 .read(
917 perpetual_db_clone.clone(),
918 abort_registration,
919 Some(StateAccumulatorSender {
920 partials: sender,
921 completion: accumulation_done_sender,
922 }),
923 )
924 .await
925 .context("Failed to read snapshot")?;
926 info!("Snapshot download complete");
927 Ok::<(), anyhow::Error>(())
928 });
929 tokio::pin!(summaries_handle);
930 tokio::pin!(snapshot_handle);
931 tokio::pin!(backfill_handle);
932
933 let mut root_global_state_hash = GlobalStateHash::default();
934 let mut num_live_objects = 0;
935 while let Some((partial_hash, num_objects)) = receiver.recv().await {
936 num_live_objects += num_objects;
937 root_global_state_hash.union(&partial_hash);
938 }
939 if accumulation_done_receiver.await.is_err() {
940 (&mut snapshot_handle)
941 .await
942 .map_err(|error| anyhow!("Snapshot task failed: {error}"))??;
943 return Err(anyhow!("Snapshot accumulation did not complete"));
944 }
945
946 let mut summaries_done = false;
947 let mut snapshot_done = false;
948 let mut backfill_done = false;
949
950 while !summaries_done {
951 tokio::select! {
952 result = &mut summaries_handle, if !summaries_done => {
953 summaries_done = true;
954 result.map_err(|error| anyhow!("Summaries task failed: {error}"))??;
955 }
956 result = &mut backfill_handle, if !backfill_done => {
957 backfill_done = true;
958 result.map_err(|error| anyhow!("Backfill task failed: {error}"))??;
959 }
960 result = &mut snapshot_handle, if !snapshot_done => {
961 snapshot_done = true;
962 result.map_err(|error| anyhow!("Snapshot task failed: {error}"))??;
963 }
964 }
965 }
966
967 let last_checkpoint = checkpoint_store
968 .get_highest_verified_checkpoint()?
969 .expect("Expected nonempty checkpoint store");
970
971 if verify != SnapshotVerifyMode::None {
973 assert_eq!(
974 last_checkpoint.epoch(),
975 epoch,
976 "Expected highest verified checkpoint ({}) to be for epoch {} but was for epoch {}",
977 last_checkpoint.sequence_number,
978 epoch,
979 last_checkpoint.epoch()
980 );
981 let commitment = last_checkpoint
982 .end_of_epoch_data
983 .as_ref()
984 .expect("Expected highest verified checkpoint to have end of epoch data")
985 .epoch_commitments
986 .last()
987 .expect(
988 "End of epoch has no commitments. This likely means that the epoch \
989 you are attempting to restore from does not support end of epoch state \
990 digest commitment. If restoring from mainnet, `--epoch` must be > 20, \
991 and for testnet, `--epoch` must be > 12.",
992 );
993 match commitment {
994 CheckpointCommitment::ECMHLiveObjectSetDigest(consensus_digest) => {
995 let local_digest: ECMHLiveObjectSetDigest = root_global_state_hash.digest().into();
996 assert_eq!(
997 *consensus_digest, local_digest,
998 "End of epoch {} root state digest {} does not match \
999 local root state hash {} computed from snapshot data",
1000 epoch, consensus_digest.digest, local_digest.digest,
1001 );
1002 let progress_bar = m.add(
1003 ProgressBar::new(1).with_style(
1004 ProgressStyle::with_template(
1005 "[{elapsed_precise}] {wide_bar} Verifying snapshot contents against root state hash ({msg})",
1006 )
1007 .unwrap(),
1008 ),
1009 );
1010 progress_bar.finish_with_message("Verification complete");
1011 }
1012 _ => return Err(anyhow!("Expected ECMHLiveObjectSetDigest")),
1013 };
1014 } else {
1015 m.println(
1016 "WARNING: Skipping snapshot verification! \
1017 This is highly discouraged unless you fully trust the source of this snapshot and its contents.
1018 If this was unintentional, rerun with `--verify` set to `normal` or `strict`.",
1019 )?;
1020 }
1021
1022 while !snapshot_done || !backfill_done {
1023 tokio::select! {
1024 result = &mut backfill_handle, if !backfill_done => {
1025 backfill_done = true;
1026 result.map_err(|error| anyhow!("Backfill task failed: {error}"))??;
1027 }
1028 result = &mut snapshot_handle, if !snapshot_done => {
1029 snapshot_done = true;
1030 result.map_err(|error| anyhow!("Snapshot task failed: {error}"))??;
1031 }
1032 }
1033 }
1034
1035 checkpoint_store.insert_epoch_last_checkpoint(epoch, &last_checkpoint)?;
1039
1040 setup_db_state(
1041 epoch,
1042 root_global_state_hash.clone(),
1043 perpetual_db.clone(),
1044 checkpoint_store.clone(),
1045 committee_store,
1046 network,
1047 verify == SnapshotVerifyMode::Strict,
1048 num_live_objects,
1049 m.clone(),
1050 )
1051 .await?;
1052
1053 #[cfg(tidehunter)]
1056 {
1057 perpetual_db
1058 .force_rebuild_control_region()
1059 .expect("Failed to rebuild tidehunter control region after snapshot restore");
1060 println!(
1065 "Waiting for tidehunter background threads to finish before renaming staging to live"
1066 );
1067 perpetual_db.wait_for_tidehunter_background_threads();
1068 println!("Tidehunter background threads finished, proceeding with rename");
1069 }
1070
1071 let new_path = path.parent().unwrap().join("live");
1072 if new_path.exists() {
1073 fs::remove_dir_all(new_path.clone())?;
1074 }
1075 fs::rename(&path, &new_path)?;
1076 fs::remove_dir_all(snapshot_dir.clone())?;
1077 println!(
1078 "Successfully restored state from snapshot at end of epoch {}",
1079 epoch
1080 );
1081
1082 Ok(())
1083}
1084
1085async fn backfill_epoch_transaction_digests(
1086 perpetual_db: Arc<AuthorityPerpetualTables>,
1087 epoch: EpochId,
1088 ingestion_url: String,
1089 concurrency: usize,
1090 max_retries: usize,
1091 m: MultiProgress,
1092 end_of_epoch_checkpoint_seq_nums: Vec<u64>,
1093) -> Result<()> {
1094 if epoch == 0 {
1095 return Ok(());
1096 }
1097
1098 let epoch_last_cp_seq = end_of_epoch_checkpoint_seq_nums
1104 .get(epoch as usize)
1105 .ok_or_else(|| anyhow!("No checkpoint sequence found for epoch {}", epoch))?;
1106
1107 let epoch_start_cp = if epoch == 0 {
1108 0
1109 } else {
1110 end_of_epoch_checkpoint_seq_nums
1111 .get(epoch as usize - 1)
1112 .map(|cp| cp + 1)
1113 .unwrap_or(0)
1114 };
1115 let msg = format!(
1116 "Beginning transaction digest backfill for epoch: {:?}, backfilling from: {:?}..{:?}",
1117 epoch, epoch_start_cp, epoch_last_cp_seq
1118 );
1119 m.println(&msg).ok();
1120 info!("{}", msg);
1121
1122 let checkpoints_to_fetch: Vec<_> = (epoch_start_cp..=*epoch_last_cp_seq).collect();
1123 let num_checkpoints = checkpoints_to_fetch.len();
1124
1125 let progress_bar = m.add(
1126 ProgressBar::new(num_checkpoints as u64).with_style(
1127 ProgressStyle::with_template(
1128 "[{elapsed_precise}] {wide_bar} {pos}/{len} transactions backfilled ({msg})",
1129 )
1130 .unwrap(),
1131 ),
1132 );
1133
1134 let client = build_object_store(&ingestion_url, vec![], vec![]);
1135 let checkpoint_counter = Arc::new(AtomicU64::new(0));
1136 let tx_counter = Arc::new(AtomicU64::new(0));
1137 let cloned_checkpoint_counter = checkpoint_counter.clone();
1138 let cloned_progress_bar = progress_bar.clone();
1139 let start_instant = Instant::now();
1140
1141 tokio::spawn(async move {
1142 loop {
1143 if cloned_progress_bar.is_finished() {
1144 break;
1145 }
1146 let num_checkpoints_processed = cloned_checkpoint_counter.load(Ordering::Relaxed);
1147 let elapsed = start_instant.elapsed().as_secs_f64();
1148 let chkpts_per_sec = if elapsed > 0.0 {
1149 num_checkpoints_processed as f64 / elapsed
1150 } else {
1151 0.0
1152 };
1153 cloned_progress_bar.set_position(num_checkpoints_processed);
1154 cloned_progress_bar.set_message(format!("{:.1} chkpts/sec", chkpts_per_sec));
1155 tokio::time::sleep(Duration::from_millis(100)).await;
1156 }
1157 });
1158
1159 futures::stream::iter(checkpoints_to_fetch)
1160 .map(|sq| {
1161 let client = client.clone();
1162 async move {
1163 let attempts = AtomicUsize::new(0);
1170 backoff::future::retry_notify(
1171 ExponentialBackoff {
1172 max_interval: Duration::from_secs(30),
1173 max_elapsed_time: None,
1174 ..Default::default()
1175 },
1176 || async {
1177 fetch_checkpoint(&client, sq).await.map_err(|e| {
1178 if attempts.fetch_add(1, Ordering::Relaxed) + 1 >= max_retries {
1179 backoff::Error::permanent(e)
1180 } else {
1181 backoff::Error::transient(e)
1182 }
1183 })
1184 },
1185 |e, delay: Duration| {
1186 tracing::warn!(
1187 "backfill: checkpoint {} fetch failed (attempt {}/{}): {}; retrying in {:?}",
1188 sq,
1189 attempts.load(Ordering::Relaxed),
1190 max_retries,
1191 e,
1192 delay,
1193 );
1194 },
1195 )
1196 .await
1197 .map(|c| Arc::new(CheckpointData::from(c)))
1198 }
1199 })
1200 .buffer_unordered(concurrency)
1201 .try_for_each(|checkpoint| {
1202 let perpetual_db = perpetual_db.clone();
1203 let tx_counter = tx_counter.clone();
1204 let checkpoint_counter = checkpoint_counter.clone();
1205 let checkpoint_data = checkpoint;
1206
1207 async move {
1208 let tx_digests: Vec<_> = checkpoint_data
1209 .transactions
1210 .iter()
1211 .map(|tx_data| *tx_data.transaction.digest())
1212 .collect();
1213 let num_txs = tx_digests.len();
1214 perpetual_db
1215 .insert_executed_transaction_digests_batch(epoch, tx_digests.into_iter())?;
1216 tx_counter.fetch_add(num_txs as u64, Ordering::Relaxed);
1217 checkpoint_counter.fetch_add(1, Ordering::Relaxed);
1218 Ok::<(), anyhow::Error>(())
1219 }
1220 })
1221 .await?;
1222
1223 let tx_count = tx_counter.load(Ordering::Relaxed);
1224 progress_bar.finish_with_message(format!(
1225 "Backfill complete: {} transactions from {} checkpoints",
1226 tx_count, num_checkpoints
1227 ));
1228 info!(
1229 "Backfill complete: {} transactions from {} checkpoints",
1230 tx_counter.load(Ordering::Relaxed),
1231 checkpoint_counter.load(Ordering::Relaxed)
1232 );
1233
1234 Ok(())
1235}