1131 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			1131 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use clap::{
 | |
|     crate_description, crate_name, value_t, value_t_or_exit, values_t_or_exit, App, Arg,
 | |
|     ArgMatches, SubCommand,
 | |
| };
 | |
| use histogram;
 | |
| use serde_json::json;
 | |
| use solana_ledger::{
 | |
|     bank_forks::{BankForks, SnapshotConfig},
 | |
|     bank_forks_utils,
 | |
|     blockstore::Blockstore,
 | |
|     blockstore_db::{self, Column, Database},
 | |
|     blockstore_processor::{BankForksInfo, ProcessOptions},
 | |
|     rooted_slot_iterator::RootedSlotIterator,
 | |
|     snapshot_utils,
 | |
| };
 | |
| use solana_sdk::{
 | |
|     clock::Slot, genesis_config::GenesisConfig, native_token::lamports_to_sol,
 | |
|     program_utils::limited_deserialize, pubkey::Pubkey, shred_version::compute_shred_version,
 | |
| };
 | |
| use solana_vote_program::vote_state::VoteState;
 | |
| use std::{
 | |
|     collections::{BTreeMap, HashMap, HashSet},
 | |
|     ffi::OsStr,
 | |
|     fs::{self, File},
 | |
|     io::{self, stdout, Write},
 | |
|     path::{Path, PathBuf},
 | |
|     process::{exit, Command, Stdio},
 | |
|     str::FromStr,
 | |
| };
 | |
| 
 | |
| #[derive(PartialEq)]
 | |
| enum LedgerOutputMethod {
 | |
|     Print,
 | |
|     Json,
 | |
| }
 | |
| 
 | |
| fn output_slot(blockstore: &Blockstore, slot: Slot, method: &LedgerOutputMethod) {
 | |
|     println!("Slot Meta {:?}", blockstore.meta(slot));
 | |
|     let entries = blockstore
 | |
|         .get_slot_entries(slot, 0, None)
 | |
|         .unwrap_or_else(|err| {
 | |
|             eprintln!("Failed to load entries for slot {}: {:?}", slot, err);
 | |
|             exit(1);
 | |
|         });
 | |
| 
 | |
|     for (entry_index, entry) in entries.iter().enumerate() {
 | |
|         match method {
 | |
|             LedgerOutputMethod::Print => {
 | |
|                 println!(
 | |
|                     "  Entry {} - num_hashes: {}, hashes: {}, transactions: {}",
 | |
|                     entry_index,
 | |
|                     entry.num_hashes,
 | |
|                     entry.hash,
 | |
|                     entry.transactions.len()
 | |
|                 );
 | |
|                 for (transactions_index, transaction) in entry.transactions.iter().enumerate() {
 | |
|                     let message = &transaction.message;
 | |
|                     println!("    Transaction {}", transactions_index);
 | |
|                     println!("      Recent Blockhash: {:?}", message.recent_blockhash);
 | |
|                     for (signature_index, signature) in transaction.signatures.iter().enumerate() {
 | |
|                         println!("      Signature {}: {:?}", signature_index, signature);
 | |
|                     }
 | |
|                     println!("      Header: {:?}", message.header);
 | |
|                     for (account_index, account) in message.account_keys.iter().enumerate() {
 | |
|                         println!("      Account {}: {:?}", account_index, account);
 | |
|                     }
 | |
|                     for (instruction_index, instruction) in message.instructions.iter().enumerate()
 | |
|                     {
 | |
|                         let program_pubkey =
 | |
|                             message.account_keys[instruction.program_id_index as usize];
 | |
|                         println!("      Instruction {}", instruction_index);
 | |
|                         println!(
 | |
|                             "        Program: {} ({})",
 | |
|                             program_pubkey, instruction.program_id_index
 | |
|                         );
 | |
|                         for (account_index, account) in instruction.accounts.iter().enumerate() {
 | |
|                             let account_pubkey = message.account_keys[*account as usize];
 | |
|                             println!(
 | |
|                                 "        Account {}: {} ({})",
 | |
|                                 account_index, account_pubkey, account
 | |
|                             );
 | |
|                         }
 | |
| 
 | |
|                         let mut raw = true;
 | |
|                         if program_pubkey == solana_vote_program::id() {
 | |
|                             if let Ok(vote_instruction) =
 | |
|                                 limited_deserialize::<
 | |
|                                     solana_vote_program::vote_instruction::VoteInstruction,
 | |
|                                 >(&instruction.data)
 | |
|                             {
 | |
|                                 println!("        {:?}", vote_instruction);
 | |
|                                 raw = false;
 | |
|                             }
 | |
|                         } else if program_pubkey == solana_sdk::system_program::id() {
 | |
|                             if let Ok(system_instruction) =
 | |
|                                 limited_deserialize::<
 | |
|                                     solana_sdk::system_instruction::SystemInstruction,
 | |
|                                 >(&instruction.data)
 | |
|                             {
 | |
|                                 println!("        {:?}", system_instruction);
 | |
|                                 raw = false;
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                         if raw {
 | |
|                             println!("        Data: {:?}", instruction.data);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             LedgerOutputMethod::Json => {
 | |
|                 serde_json::to_writer(stdout(), &entry).expect("serialize entry");
 | |
|                 stdout().write_all(b",\n").expect("newline");
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn output_ledger(blockstore: Blockstore, starting_slot: Slot, method: LedgerOutputMethod) {
 | |
|     let rooted_slot_iterator =
 | |
|         RootedSlotIterator::new(starting_slot, &blockstore).unwrap_or_else(|err| {
 | |
|             eprintln!(
 | |
|                 "Failed to load entries starting from slot {}: {:?}",
 | |
|                 starting_slot, err
 | |
|             );
 | |
|             exit(1);
 | |
|         });
 | |
| 
 | |
|     if method == LedgerOutputMethod::Json {
 | |
|         stdout().write_all(b"{\"ledger\":[\n").expect("open array");
 | |
|     }
 | |
| 
 | |
|     for (slot, slot_meta) in rooted_slot_iterator {
 | |
|         match method {
 | |
|             LedgerOutputMethod::Print => println!("Slot {}", slot),
 | |
|             LedgerOutputMethod::Json => {
 | |
|                 serde_json::to_writer(stdout(), &slot_meta).expect("serialize slot_meta");
 | |
|                 stdout().write_all(b",\n").expect("newline");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         output_slot(&blockstore, slot, &method);
 | |
|     }
 | |
| 
 | |
|     if method == LedgerOutputMethod::Json {
 | |
|         stdout().write_all(b"\n]}\n").expect("close array");
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn render_dot(dot: String, output_file: &str, output_format: &str) -> io::Result<()> {
 | |
|     let mut child = Command::new("dot")
 | |
|         .arg(format!("-T{}", output_format))
 | |
|         .arg(format!("-o{}", output_file))
 | |
|         .stdin(Stdio::piped())
 | |
|         .spawn()
 | |
|         .map_err(|err| {
 | |
|             eprintln!("Failed to spawn dot: {:?}", err);
 | |
|             err
 | |
|         })?;
 | |
| 
 | |
|     let stdin = child.stdin.as_mut().unwrap();
 | |
|     stdin.write_all(&dot.into_bytes())?;
 | |
| 
 | |
|     let status = child.wait_with_output()?.status;
 | |
|     if !status.success() {
 | |
|         return Err(io::Error::new(
 | |
|             io::ErrorKind::Other,
 | |
|             format!("dot failed with error {}", status.code().unwrap_or(-1)),
 | |
|         ));
 | |
|     }
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| #[allow(clippy::cognitive_complexity)]
 | |
| fn graph_forks(
 | |
|     bank_forks: &BankForks,
 | |
|     bank_forks_info: &[BankForksInfo],
 | |
|     include_all_votes: bool,
 | |
| ) -> String {
 | |
|     // Search all forks and collect the last vote made by each validator
 | |
|     let mut last_votes = HashMap::new();
 | |
|     for bfi in bank_forks_info {
 | |
|         let bank = bank_forks.banks.get(&bfi.bank_slot).unwrap();
 | |
| 
 | |
|         let total_stake = bank
 | |
|             .vote_accounts()
 | |
|             .iter()
 | |
|             .fold(0, |acc, (_, (stake, _))| acc + stake);
 | |
|         for (_, (stake, vote_account)) in bank.vote_accounts() {
 | |
|             let vote_state = VoteState::from(&vote_account).unwrap_or_default();
 | |
|             if let Some(last_vote) = vote_state.votes.iter().last() {
 | |
|                 let entry = last_votes.entry(vote_state.node_pubkey).or_insert((
 | |
|                     last_vote.slot,
 | |
|                     vote_state.clone(),
 | |
|                     stake,
 | |
|                     total_stake,
 | |
|                 ));
 | |
|                 if entry.0 < last_vote.slot {
 | |
|                     *entry = (last_vote.slot, vote_state, stake, total_stake);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Figure the stake distribution at all the nodes containing the last vote from each
 | |
|     // validator
 | |
|     let mut slot_stake_and_vote_count = HashMap::new();
 | |
|     for (last_vote_slot, _, stake, total_stake) in last_votes.values() {
 | |
|         let entry = slot_stake_and_vote_count
 | |
|             .entry(last_vote_slot)
 | |
|             .or_insert((0, 0, *total_stake));
 | |
|         entry.0 += 1;
 | |
|         entry.1 += stake;
 | |
|         assert_eq!(entry.2, *total_stake)
 | |
|     }
 | |
| 
 | |
|     let mut dot = vec!["digraph {".to_string()];
 | |
| 
 | |
|     // Build a subgraph consisting of all banks and links to their parent banks
 | |
|     dot.push("  subgraph cluster_banks {".to_string());
 | |
|     dot.push("    style=invis".to_string());
 | |
|     let mut styled_slots = HashSet::new();
 | |
|     let mut all_votes: HashMap<Pubkey, HashMap<Slot, VoteState>> = HashMap::new();
 | |
|     for bfi in bank_forks_info {
 | |
|         let bank = bank_forks.banks.get(&bfi.bank_slot).unwrap();
 | |
|         let mut bank = bank.clone();
 | |
| 
 | |
|         let mut first = true;
 | |
|         loop {
 | |
|             for (_, (_, vote_account)) in bank.vote_accounts() {
 | |
|                 let vote_state = VoteState::from(&vote_account).unwrap_or_default();
 | |
|                 if let Some(last_vote) = vote_state.votes.iter().last() {
 | |
|                     let validator_votes = all_votes.entry(vote_state.node_pubkey).or_default();
 | |
|                     validator_votes
 | |
|                         .entry(last_vote.slot)
 | |
|                         .or_insert_with(|| vote_state.clone());
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if !styled_slots.contains(&bank.slot()) {
 | |
|                 dot.push(format!(
 | |
|                     r#"    "{}"[label="{} (epoch {})\nleader: {}{}{}",style="{}{}"];"#,
 | |
|                     bank.slot(),
 | |
|                     bank.slot(),
 | |
|                     bank.epoch(),
 | |
|                     bank.collector_id(),
 | |
|                     if let Some(parent) = bank.parent() {
 | |
|                         format!(
 | |
|                             "\ntransactions: {}",
 | |
|                             bank.transaction_count() - parent.transaction_count(),
 | |
|                         )
 | |
|                     } else {
 | |
|                         "".to_string()
 | |
|                     },
 | |
|                     if let Some((votes, stake, total_stake)) =
 | |
|                         slot_stake_and_vote_count.get(&bank.slot())
 | |
|                     {
 | |
|                         format!(
 | |
|                             "\nvotes: {}, stake: {:.1} SOL ({:.1}%)",
 | |
|                             votes,
 | |
|                             lamports_to_sol(*stake),
 | |
|                             *stake as f64 / *total_stake as f64 * 100.,
 | |
|                         )
 | |
|                     } else {
 | |
|                         "".to_string()
 | |
|                     },
 | |
|                     if first { "filled," } else { "" },
 | |
|                     ""
 | |
|                 ));
 | |
|                 styled_slots.insert(bank.slot());
 | |
|             }
 | |
|             first = false;
 | |
| 
 | |
|             match bank.parent() {
 | |
|                 None => {
 | |
|                     if bank.slot() > 0 {
 | |
|                         dot.push(format!(r#"    "{}" -> "..." [dir=back]"#, bank.slot(),));
 | |
|                     }
 | |
|                     break;
 | |
|                 }
 | |
|                 Some(parent) => {
 | |
|                     let slot_distance = bank.slot() - parent.slot();
 | |
|                     let penwidth = if bank.epoch() > parent.epoch() {
 | |
|                         "5"
 | |
|                     } else {
 | |
|                         "1"
 | |
|                     };
 | |
|                     let link_label = if slot_distance > 1 {
 | |
|                         format!("label=\"{} slots\",color=red", slot_distance)
 | |
|                     } else {
 | |
|                         "color=blue".to_string()
 | |
|                     };
 | |
|                     dot.push(format!(
 | |
|                         r#"    "{}" -> "{}"[{},dir=back,penwidth={}];"#,
 | |
|                         bank.slot(),
 | |
|                         parent.slot(),
 | |
|                         link_label,
 | |
|                         penwidth
 | |
|                     ));
 | |
| 
 | |
|                     bank = parent.clone();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     dot.push("  }".to_string());
 | |
| 
 | |
|     // Strafe the banks with links from validators to the bank they last voted on,
 | |
|     // while collecting information about the absent votes and stakes
 | |
|     let mut absent_stake = 0;
 | |
|     let mut absent_votes = 0;
 | |
|     let mut lowest_last_vote_slot = std::u64::MAX;
 | |
|     let mut lowest_total_stake = 0;
 | |
|     for (node_pubkey, (last_vote_slot, vote_state, stake, total_stake)) in &last_votes {
 | |
|         all_votes.entry(*node_pubkey).and_modify(|validator_votes| {
 | |
|             validator_votes.remove(&last_vote_slot);
 | |
|         });
 | |
| 
 | |
|         dot.push(format!(
 | |
|             r#"  "last vote {}"[shape=box,label="Latest validator vote: {}\nstake: {} SOL\nroot slot: {}\nvote history:\n{}"];"#,
 | |
|             node_pubkey,
 | |
|             node_pubkey,
 | |
|             lamports_to_sol(*stake),
 | |
|             vote_state.root_slot.unwrap_or(0),
 | |
|             vote_state
 | |
|                 .votes
 | |
|                 .iter()
 | |
|                 .map(|vote| format!("slot {} (conf={})", vote.slot, vote.confirmation_count))
 | |
|                 .collect::<Vec<_>>()
 | |
|                 .join("\n")
 | |
|         ));
 | |
| 
 | |
|         dot.push(format!(
 | |
|             r#"  "last vote {}" -> "{}" [style=dashed,label="latest vote"];"#,
 | |
|             node_pubkey,
 | |
|             if styled_slots.contains(&last_vote_slot) {
 | |
|                 last_vote_slot.to_string()
 | |
|             } else {
 | |
|                 if *last_vote_slot < lowest_last_vote_slot {
 | |
|                     lowest_last_vote_slot = *last_vote_slot;
 | |
|                     lowest_total_stake = *total_stake;
 | |
|                 }
 | |
|                 absent_votes += 1;
 | |
|                 absent_stake += stake;
 | |
| 
 | |
|                 "...".to_string()
 | |
|             },
 | |
|         ));
 | |
|     }
 | |
| 
 | |
|     // Annotate the final "..." node with absent vote and stake information
 | |
|     if absent_votes > 0 {
 | |
|         dot.push(format!(
 | |
|             r#"    "..."[label="...\nvotes: {}, stake: {:.1} SOL {:.1}%"];"#,
 | |
|             absent_votes,
 | |
|             lamports_to_sol(absent_stake),
 | |
|             absent_stake as f64 / lowest_total_stake as f64 * 100.,
 | |
|         ));
 | |
|     }
 | |
| 
 | |
|     // Add for vote information from all banks.
 | |
|     if include_all_votes {
 | |
|         for (node_pubkey, validator_votes) in &all_votes {
 | |
|             for (vote_slot, vote_state) in validator_votes {
 | |
|                 dot.push(format!(
 | |
|                     r#"  "{} vote {}"[shape=box,style=dotted,label="validator vote: {}\nroot slot: {}\nvote history:\n{}"];"#,
 | |
|                     node_pubkey,
 | |
|                     vote_slot,
 | |
|                     node_pubkey,
 | |
|                     vote_state.root_slot.unwrap_or(0),
 | |
|                     vote_state
 | |
|                         .votes
 | |
|                         .iter()
 | |
|                         .map(|vote| format!("slot {} (conf={})", vote.slot, vote.confirmation_count))
 | |
|                         .collect::<Vec<_>>()
 | |
|                         .join("\n")
 | |
|                 ));
 | |
| 
 | |
|                 dot.push(format!(
 | |
|                     r#"  "{} vote {}" -> "{}" [style=dotted,label="vote"];"#,
 | |
|                     node_pubkey,
 | |
|                     vote_slot,
 | |
|                     if styled_slots.contains(&vote_slot) {
 | |
|                         vote_slot.to_string()
 | |
|                     } else {
 | |
|                         "...".to_string()
 | |
|                     },
 | |
|                 ));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     dot.push("}".to_string());
 | |
|     dot.join("\n")
 | |
| }
 | |
| 
 | |
| fn analyze_column<
 | |
|     T: solana_ledger::blockstore_db::Column + solana_ledger::blockstore_db::ColumnName,
 | |
| >(
 | |
|     db: &Database,
 | |
|     name: &str,
 | |
|     key_size: usize,
 | |
| ) -> Result<(), String> {
 | |
|     let mut key_tot: u64 = 0;
 | |
|     let mut val_hist = histogram::Histogram::new();
 | |
|     let mut val_tot: u64 = 0;
 | |
|     let mut row_hist = histogram::Histogram::new();
 | |
|     let a = key_size as u64;
 | |
|     for (_x, y) in db.iter::<T>(blockstore_db::IteratorMode::Start).unwrap() {
 | |
|         let b = y.len() as u64;
 | |
|         key_tot += a;
 | |
|         val_hist.increment(b).unwrap();
 | |
|         val_tot += b;
 | |
|         row_hist.increment(a + b).unwrap();
 | |
|     }
 | |
| 
 | |
|     let json_result = if val_hist.entries() > 0 {
 | |
|         json!({
 | |
|             "column":name,
 | |
|             "entries":val_hist.entries(),
 | |
|             "key_stats":{
 | |
|                 "max":a,
 | |
|                 "total_bytes":key_tot,
 | |
|             },
 | |
|             "val_stats":{
 | |
|                 "p50":val_hist.percentile(50.0).unwrap(),
 | |
|                 "p90":val_hist.percentile(90.0).unwrap(),
 | |
|                 "p99":val_hist.percentile(99.0).unwrap(),
 | |
|                 "p999":val_hist.percentile(99.9).unwrap(),
 | |
|                 "min":val_hist.minimum().unwrap(),
 | |
|                 "max":val_hist.maximum().unwrap(),
 | |
|                 "stddev":val_hist.stddev().unwrap(),
 | |
|                 "total_bytes":val_tot,
 | |
|             },
 | |
|             "row_stats":{
 | |
|                 "p50":row_hist.percentile(50.0).unwrap(),
 | |
|                 "p90":row_hist.percentile(90.0).unwrap(),
 | |
|                 "p99":row_hist.percentile(99.0).unwrap(),
 | |
|                 "p999":row_hist.percentile(99.9).unwrap(),
 | |
|                 "min":row_hist.minimum().unwrap(),
 | |
|                 "max":row_hist.maximum().unwrap(),
 | |
|                 "stddev":row_hist.stddev().unwrap(),
 | |
|                 "total_bytes":key_tot + val_tot,
 | |
|             },
 | |
|         })
 | |
|     } else {
 | |
|         json!({
 | |
|         "column":name,
 | |
|         "entries":val_hist.entries(),
 | |
|         "key_stats":{
 | |
|             "max":a,
 | |
|             "total_bytes":0,
 | |
|         },
 | |
|         "val_stats":{
 | |
|             "total_bytes":0,
 | |
|         },
 | |
|         "row_stats":{
 | |
|             "total_bytes":0,
 | |
|         },
 | |
|         })
 | |
|     };
 | |
| 
 | |
|     println!("{}", serde_json::to_string_pretty(&json_result).unwrap());
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| fn analyze_storage(database: &Database) -> Result<(), String> {
 | |
|     use blockstore_db::columns::*;
 | |
|     analyze_column::<SlotMeta>(database, "SlotMeta", SlotMeta::key_size())?;
 | |
|     analyze_column::<Orphans>(database, "Orphans", Orphans::key_size())?;
 | |
|     analyze_column::<DeadSlots>(database, "DeadSlots", DeadSlots::key_size())?;
 | |
|     analyze_column::<ErasureMeta>(database, "ErasureMeta", ErasureMeta::key_size())?;
 | |
|     analyze_column::<Root>(database, "Root", Root::key_size())?;
 | |
|     analyze_column::<Index>(database, "Index", Index::key_size())?;
 | |
|     analyze_column::<ShredData>(database, "ShredData", ShredData::key_size())?;
 | |
|     analyze_column::<ShredCode>(database, "ShredCode", ShredCode::key_size())?;
 | |
|     analyze_column::<TransactionStatus>(
 | |
|         database,
 | |
|         "TransactionStatus",
 | |
|         TransactionStatus::key_size(),
 | |
|     )?;
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| fn open_genesis_config(ledger_path: &Path) -> GenesisConfig {
 | |
|     GenesisConfig::load(&ledger_path).unwrap_or_else(|err| {
 | |
|         eprintln!(
 | |
|             "Failed to open ledger genesis_config at {:?}: {}",
 | |
|             ledger_path, err
 | |
|         );
 | |
|         exit(1);
 | |
|     })
 | |
| }
 | |
| 
 | |
| fn open_blockstore(ledger_path: &Path) -> Blockstore {
 | |
|     match Blockstore::open(ledger_path) {
 | |
|         Ok(blockstore) => blockstore,
 | |
|         Err(err) => {
 | |
|             eprintln!("Failed to open ledger at {:?}: {:?}", ledger_path, err);
 | |
|             exit(1);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn open_database(ledger_path: &Path) -> Database {
 | |
|     match Database::open(&ledger_path.join("rocksdb")) {
 | |
|         Ok(database) => database,
 | |
|         Err(err) => {
 | |
|             eprintln!("Unable to read the Ledger rocksdb: {:?}", err);
 | |
|             exit(1);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| // This function is duplicated in validator/src/main.rs...
 | |
| fn hardforks_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Slot>> {
 | |
|     if matches.is_present(name) {
 | |
|         Some(values_t_or_exit!(matches, name, Slot))
 | |
|     } else {
 | |
|         None
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn load_bank_forks(
 | |
|     arg_matches: &ArgMatches,
 | |
|     ledger_path: &PathBuf,
 | |
|     genesis_config: &GenesisConfig,
 | |
|     process_options: ProcessOptions,
 | |
| ) -> bank_forks_utils::LoadResult {
 | |
|     let snapshot_config = if arg_matches.is_present("no_snapshot") {
 | |
|         None
 | |
|     } else {
 | |
|         Some(SnapshotConfig {
 | |
|             snapshot_interval_slots: 0, // Value doesn't matter
 | |
|             snapshot_package_output_path: ledger_path.clone(),
 | |
|             snapshot_path: ledger_path.clone().join("snapshot"),
 | |
|             trusted_validators: None,
 | |
|         })
 | |
|     };
 | |
|     let account_paths = if let Some(account_paths) = arg_matches.value_of("account_paths") {
 | |
|         account_paths.split(',').map(PathBuf::from).collect()
 | |
|     } else {
 | |
|         vec![ledger_path.join("accounts")]
 | |
|     };
 | |
| 
 | |
|     bank_forks_utils::load(
 | |
|         &genesis_config,
 | |
|         &open_blockstore(&ledger_path),
 | |
|         account_paths,
 | |
|         snapshot_config.as_ref(),
 | |
|         process_options,
 | |
|     )
 | |
| }
 | |
| 
 | |
| #[allow(clippy::cognitive_complexity)]
 | |
| fn main() {
 | |
|     const DEFAULT_ROOT_COUNT: &str = "1";
 | |
|     solana_logger::setup_with_default("solana=info");
 | |
| 
 | |
|     let starting_slot_arg = Arg::with_name("starting_slot")
 | |
|         .long("starting-slot")
 | |
|         .value_name("NUM")
 | |
|         .takes_value(true)
 | |
|         .default_value("0")
 | |
|         .help("Start at this slot");
 | |
|     let no_snapshot_arg = Arg::with_name("no_snapshot")
 | |
|         .long("no-snapshot")
 | |
|         .takes_value(false)
 | |
|         .help("Do not start from a local snapshot if present");
 | |
|     let account_paths_arg = Arg::with_name("account_paths")
 | |
|         .long("accounts")
 | |
|         .value_name("PATHS")
 | |
|         .takes_value(true)
 | |
|         .help("Comma separated persistent accounts location");
 | |
|     let halt_at_slot_arg = Arg::with_name("halt_at_slot")
 | |
|         .long("halt-at-slot")
 | |
|         .value_name("SLOT")
 | |
|         .takes_value(true)
 | |
|         .help("Halt processing at the given slot");
 | |
|     let hard_forks_arg = Arg::with_name("hard_forks")
 | |
|         .long("hard-fork")
 | |
|         .value_name("SLOT")
 | |
|         .multiple(true)
 | |
|         .takes_value(true)
 | |
|         .help("Add a hard fork at this slot");
 | |
| 
 | |
|     let matches = App::new(crate_name!())
 | |
|         .about(crate_description!())
 | |
|         .version(solana_clap_utils::version!())
 | |
|         .arg(
 | |
|             Arg::with_name("ledger_path")
 | |
|                 .short("l")
 | |
|                 .long("ledger")
 | |
|                 .value_name("DIR")
 | |
|                 .takes_value(true)
 | |
|                 .global(true)
 | |
|                 .help("Use DIR for ledger location"),
 | |
|         )
 | |
|         .subcommand(
 | |
|             SubCommand::with_name("print")
 | |
|             .about("Print the ledger")
 | |
|             .arg(&starting_slot_arg)
 | |
|         )
 | |
|         .subcommand(
 | |
|             SubCommand::with_name("slot")
 | |
|             .about("Print the contents of one or more slots")
 | |
|             .arg(
 | |
|                 Arg::with_name("slots")
 | |
|                     .index(1)
 | |
|                     .value_name("SLOTS")
 | |
|                     .takes_value(true)
 | |
|                     .multiple(true)
 | |
|                     .required(true)
 | |
|                     .help("List of slots to print"),
 | |
|             )
 | |
|         )
 | |
|         .subcommand(
 | |
|             SubCommand::with_name("genesis")
 | |
|             .about("Prints the ledger's genesis config")
 | |
|         )
 | |
|         .subcommand(
 | |
|             SubCommand::with_name("genesis-hash")
 | |
|             .about("Prints the ledger's genesis hash")
 | |
|         )
 | |
|         .subcommand(
 | |
|             SubCommand::with_name("shred-version")
 | |
|             .about("Prints the ledger's shred hash")
 | |
|             .arg(&hard_forks_arg)
 | |
|         )
 | |
|         .subcommand(
 | |
|             SubCommand::with_name("bounds")
 | |
|             .about("Print lowest and highest non-empty slots. Note that there may be empty slots within the bounds")
 | |
|             .arg(
 | |
|                 Arg::with_name("all")
 | |
|                     .long("all")
 | |
|                     .takes_value(false)
 | |
|                     .required(false)
 | |
|                     .help("Additionally print all the non-empty slots within the bounds"),
 | |
|             )
 | |
|         ).subcommand(
 | |
|             SubCommand::with_name("json")
 | |
|             .about("Print the ledger in JSON format")
 | |
|             .arg(&starting_slot_arg)
 | |
|         )
 | |
|         .subcommand(
 | |
|             SubCommand::with_name("verify")
 | |
|             .about("Verify the ledger")
 | |
|             .arg(&no_snapshot_arg)
 | |
|             .arg(&account_paths_arg)
 | |
|             .arg(&halt_at_slot_arg)
 | |
|             .arg(&hard_forks_arg)
 | |
|             .arg(
 | |
|                 Arg::with_name("skip_poh_verify")
 | |
|                     .long("skip-poh-verify")
 | |
|                     .takes_value(false)
 | |
|                     .help("Skip ledger PoH verification"),
 | |
|             )
 | |
|         ).subcommand(
 | |
|             SubCommand::with_name("graph")
 | |
|             .about("Create a Graphviz rendering of the ledger")
 | |
|             .arg(&no_snapshot_arg)
 | |
|             .arg(&account_paths_arg)
 | |
|             .arg(&halt_at_slot_arg)
 | |
|             .arg(&hard_forks_arg)
 | |
|             .arg(
 | |
|                 Arg::with_name("include_all_votes")
 | |
|                     .long("include-all-votes")
 | |
|                     .help("Include all votes in the graph"),
 | |
|             )
 | |
|             .arg(
 | |
|                 Arg::with_name("graph_filename")
 | |
|                     .index(1)
 | |
|                     .value_name("FILENAME")
 | |
|                     .takes_value(true)
 | |
|                     .help("Output file"),
 | |
|             )
 | |
|         ).subcommand(
 | |
|             SubCommand::with_name("create-snapshot")
 | |
|             .about("Create a new ledger snapshot")
 | |
|             .arg(&no_snapshot_arg)
 | |
|             .arg(&account_paths_arg)
 | |
|             .arg(&hard_forks_arg)
 | |
|             .arg(
 | |
|                 Arg::with_name("snapshot_slot")
 | |
|                     .index(1)
 | |
|                     .value_name("SLOT")
 | |
|                     .takes_value(true)
 | |
|                     .help("Slot at which to create the snapshot"),
 | |
|             )
 | |
|             .arg(
 | |
|                 Arg::with_name("output_directory")
 | |
|                     .index(2)
 | |
|                     .value_name("DIR")
 | |
|                     .takes_value(true)
 | |
|                     .help("Output directory for the snapshot"),
 | |
|             )
 | |
|         ).subcommand(
 | |
|             SubCommand::with_name("accounts")
 | |
|             .about("Print account contents after processing in the ledger")
 | |
|             .arg(&no_snapshot_arg)
 | |
|             .arg(&account_paths_arg)
 | |
|             .arg(&halt_at_slot_arg)
 | |
|             .arg(&hard_forks_arg)
 | |
|         ).subcommand(
 | |
|             SubCommand::with_name("prune")
 | |
|             .about("Prune the ledger at the block height")
 | |
|             .arg(
 | |
|                 Arg::with_name("slot_list")
 | |
|                     .long("slot-list")
 | |
|                     .value_name("FILENAME")
 | |
|                     .takes_value(true)
 | |
|                     .required(true)
 | |
|                     .help("The location of the YAML file with a list of rollback slot heights and hashes"),
 | |
|             )
 | |
|         )
 | |
|         .subcommand(
 | |
|             SubCommand::with_name("list-roots")
 | |
|             .about("Output upto last <num-roots> root hashes and their heights starting at the given block height")
 | |
|             .arg(
 | |
|                 Arg::with_name("max_height")
 | |
|                     .long("max-height")
 | |
|                     .value_name("NUM")
 | |
|                     .takes_value(true)
 | |
|                     .required(true)
 | |
|                     .help("Maximum block height")
 | |
|             )
 | |
|             .arg(
 | |
|                 Arg::with_name("slot_list")
 | |
|                     .long("slot-list")
 | |
|                     .value_name("FILENAME")
 | |
|                     .required(false)
 | |
|                     .takes_value(true)
 | |
|                     .help("The location of the output YAML file. A list of rollback slot heights and hashes will be written to the file.")
 | |
|             )
 | |
|             .arg(
 | |
|                 Arg::with_name("num_roots")
 | |
|                     .long("num-roots")
 | |
|                     .value_name("NUM")
 | |
|                     .takes_value(true)
 | |
|                     .default_value(DEFAULT_ROOT_COUNT)
 | |
|                     .required(false)
 | |
|                     .help("Number of roots in the output"),
 | |
|             )
 | |
|         )
 | |
|         .subcommand(
 | |
|             SubCommand::with_name("analyze-storage")
 | |
|                 .about("Output statistics in JSON format about all column families in the ledger rocksDB")
 | |
|         )
 | |
|         .get_matches();
 | |
| 
 | |
|     let ledger_path = PathBuf::from(value_t_or_exit!(matches, "ledger_path", String));
 | |
| 
 | |
|     // Canonicalize ledger path to avoid issues with symlink creation
 | |
|     let ledger_path = fs::canonicalize(&ledger_path).unwrap_or_else(|err| {
 | |
|         eprintln!("Unable to access ledger path: {:?}", err);
 | |
|         exit(1);
 | |
|     });
 | |
| 
 | |
|     match matches.subcommand() {
 | |
|         ("print", Some(arg_matches)) => {
 | |
|             let starting_slot = value_t_or_exit!(arg_matches, "starting_slot", Slot);
 | |
|             output_ledger(
 | |
|                 open_blockstore(&ledger_path),
 | |
|                 starting_slot,
 | |
|                 LedgerOutputMethod::Print,
 | |
|             );
 | |
|         }
 | |
|         ("genesis", Some(_arg_matches)) => {
 | |
|             println!("{}", open_genesis_config(&ledger_path));
 | |
|         }
 | |
|         ("genesis-hash", Some(_arg_matches)) => {
 | |
|             println!("{}", open_genesis_config(&ledger_path).hash());
 | |
|         }
 | |
|         ("shred-version", Some(arg_matches)) => {
 | |
|             let process_options = ProcessOptions {
 | |
|                 dev_halt_at_slot: Some(0),
 | |
|                 new_hard_forks: hardforks_of(arg_matches, "hard_forks"),
 | |
|                 poh_verify: false,
 | |
|                 ..ProcessOptions::default()
 | |
|             };
 | |
|             let genesis_config = open_genesis_config(&ledger_path);
 | |
|             match load_bank_forks(arg_matches, &ledger_path, &genesis_config, process_options) {
 | |
|                 Ok((bank_forks, bank_forks_info, _leader_schedule_cache, _snapshot_hash)) => {
 | |
|                     let bank_info = &bank_forks_info[0];
 | |
|                     let bank = bank_forks[bank_info.bank_slot].clone();
 | |
| 
 | |
|                     println!(
 | |
|                         "{}",
 | |
|                         compute_shred_version(
 | |
|                             &genesis_config.hash(),
 | |
|                             Some(&bank.hard_forks().read().unwrap())
 | |
|                         )
 | |
|                     );
 | |
|                 }
 | |
|                 Err(err) => {
 | |
|                     eprintln!("Failed to load ledger: {:?}", err);
 | |
|                     exit(1);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         ("slot", Some(arg_matches)) => {
 | |
|             let slots = values_t_or_exit!(arg_matches, "slots", Slot);
 | |
|             for slot in slots {
 | |
|                 println!("Slot {}", slot);
 | |
|                 output_slot(
 | |
|                     &open_blockstore(&ledger_path),
 | |
|                     slot,
 | |
|                     &LedgerOutputMethod::Print,
 | |
|                 );
 | |
|             }
 | |
|         }
 | |
|         ("json", Some(arg_matches)) => {
 | |
|             let starting_slot = value_t_or_exit!(arg_matches, "starting_slot", Slot);
 | |
|             output_ledger(
 | |
|                 open_blockstore(&ledger_path),
 | |
|                 starting_slot,
 | |
|                 LedgerOutputMethod::Json,
 | |
|             );
 | |
|         }
 | |
|         ("verify", Some(arg_matches)) => {
 | |
|             let process_options = ProcessOptions {
 | |
|                 dev_halt_at_slot: value_t!(arg_matches, "halt_at_slot", Slot).ok(),
 | |
|                 new_hard_forks: hardforks_of(arg_matches, "hard_forks"),
 | |
|                 poh_verify: !arg_matches.is_present("skip_poh_verify"),
 | |
|                 ..ProcessOptions::default()
 | |
|             };
 | |
|             println!("{}", open_genesis_config(&ledger_path).hash());
 | |
| 
 | |
|             load_bank_forks(
 | |
|                 arg_matches,
 | |
|                 &ledger_path,
 | |
|                 &open_genesis_config(&ledger_path),
 | |
|                 process_options,
 | |
|             )
 | |
|             .unwrap_or_else(|err| {
 | |
|                 eprintln!("Ledger verification failed: {:?}", err);
 | |
|                 exit(1);
 | |
|             });
 | |
|             println!("Ok");
 | |
|         }
 | |
|         ("graph", Some(arg_matches)) => {
 | |
|             let output_file = value_t_or_exit!(arg_matches, "graph_filename", String);
 | |
| 
 | |
|             let process_options = ProcessOptions {
 | |
|                 dev_halt_at_slot: value_t!(arg_matches, "halt_at_slot", Slot).ok(),
 | |
|                 new_hard_forks: hardforks_of(arg_matches, "hard_forks"),
 | |
|                 poh_verify: false,
 | |
|                 ..ProcessOptions::default()
 | |
|             };
 | |
| 
 | |
|             match load_bank_forks(
 | |
|                 arg_matches,
 | |
|                 &ledger_path,
 | |
|                 &open_genesis_config(&ledger_path),
 | |
|                 process_options,
 | |
|             ) {
 | |
|                 Ok((bank_forks, bank_forks_info, _leader_schedule_cache, _snapshot_hash)) => {
 | |
|                     let dot = graph_forks(
 | |
|                         &bank_forks,
 | |
|                         &bank_forks_info,
 | |
|                         arg_matches.is_present("include_all_votes"),
 | |
|                     );
 | |
| 
 | |
|                     let extension = Path::new(&output_file).extension();
 | |
|                     let result = if extension == Some(OsStr::new("pdf")) {
 | |
|                         render_dot(dot, &output_file, "pdf")
 | |
|                     } else if extension == Some(OsStr::new("png")) {
 | |
|                         render_dot(dot, &output_file, "png")
 | |
|                     } else {
 | |
|                         File::create(&output_file)
 | |
|                             .and_then(|mut file| file.write_all(&dot.into_bytes()))
 | |
|                     };
 | |
| 
 | |
|                     match result {
 | |
|                         Ok(_) => println!("Wrote {}", output_file),
 | |
|                         Err(err) => eprintln!("Unable to write {}: {}", output_file, err),
 | |
|                     }
 | |
|                 }
 | |
|                 Err(err) => {
 | |
|                     eprintln!("Failed to load ledger: {:?}", err);
 | |
|                     exit(1);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         ("create-snapshot", Some(arg_matches)) => {
 | |
|             let snapshot_slot = value_t_or_exit!(arg_matches, "snapshot_slot", Slot);
 | |
|             let output_directory = value_t_or_exit!(arg_matches, "output_directory", String);
 | |
| 
 | |
|             let process_options = ProcessOptions {
 | |
|                 dev_halt_at_slot: Some(snapshot_slot),
 | |
|                 new_hard_forks: hardforks_of(arg_matches, "hard_forks"),
 | |
|                 poh_verify: false,
 | |
|                 ..ProcessOptions::default()
 | |
|             };
 | |
|             let genesis_config = open_genesis_config(&ledger_path);
 | |
|             match load_bank_forks(arg_matches, &ledger_path, &genesis_config, process_options) {
 | |
|                 Ok((bank_forks, _bank_forks_info, _leader_schedule_cache, _snapshot_hash)) => {
 | |
|                     let bank = bank_forks.get(snapshot_slot).unwrap_or_else(|| {
 | |
|                         eprintln!("Error: Slot {} is not available", snapshot_slot);
 | |
|                         exit(1);
 | |
|                     });
 | |
| 
 | |
|                     println!("Creating a snapshot of slot {}", bank.slot());
 | |
|                     bank.squash();
 | |
| 
 | |
|                     let temp_dir = tempfile::TempDir::new().unwrap_or_else(|err| {
 | |
|                         eprintln!("Unable to create temporary directory: {}", err);
 | |
|                         exit(1);
 | |
|                     });
 | |
| 
 | |
|                     let storages: Vec<_> = bank.get_snapshot_storages();
 | |
|                     snapshot_utils::add_snapshot(&temp_dir, &bank, &storages)
 | |
|                         .and_then(|slot_snapshot_paths| {
 | |
|                             snapshot_utils::package_snapshot(
 | |
|                                 &bank,
 | |
|                                 &slot_snapshot_paths,
 | |
|                                 snapshot_utils::get_snapshot_archive_path(output_directory),
 | |
|                                 &temp_dir,
 | |
|                                 &bank.src.roots(),
 | |
|                                 storages,
 | |
|                             )
 | |
|                         })
 | |
|                         .and_then(|package| {
 | |
|                             snapshot_utils::archive_snapshot_package(&package).map(|ok| {
 | |
|                                 println!(
 | |
|                                     "Successfully created snapshot for slot {}: {:?}",
 | |
|                                     snapshot_slot, package.tar_output_file
 | |
|                                 );
 | |
|                                 println!(
 | |
|                                     "Shred version: {}",
 | |
|                                     compute_shred_version(
 | |
|                                         &genesis_config.hash(),
 | |
|                                         Some(&bank.hard_forks().read().unwrap())
 | |
|                                     )
 | |
|                                 );
 | |
|                                 ok
 | |
|                             })
 | |
|                         })
 | |
|                         .unwrap_or_else(|err| {
 | |
|                             eprintln!("Unable to create snapshot archive: {}", err);
 | |
|                             exit(1);
 | |
|                         });
 | |
|                 }
 | |
|                 Err(err) => {
 | |
|                     eprintln!("Failed to load ledger: {:?}", err);
 | |
|                     exit(1);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         ("accounts", Some(arg_matches)) => {
 | |
|             let dev_halt_at_slot = value_t!(arg_matches, "halt_at_slot", Slot).ok();
 | |
|             let process_options = ProcessOptions {
 | |
|                 dev_halt_at_slot,
 | |
|                 new_hard_forks: hardforks_of(arg_matches, "hard_forks"),
 | |
|                 poh_verify: false,
 | |
|                 ..ProcessOptions::default()
 | |
|             };
 | |
|             let genesis_config = open_genesis_config(&ledger_path);
 | |
|             match load_bank_forks(arg_matches, &ledger_path, &genesis_config, process_options) {
 | |
|                 Ok((bank_forks, bank_forks_info, _leader_schedule_cache, _snapshot_hash)) => {
 | |
|                     let slot = dev_halt_at_slot.unwrap_or_else(|| {
 | |
|                         if bank_forks_info.len() > 1 {
 | |
|                             eprintln!("Error: multiple forks present");
 | |
|                             exit(1);
 | |
|                         }
 | |
|                         bank_forks_info[0].bank_slot
 | |
|                     });
 | |
| 
 | |
|                     let bank = bank_forks.get(slot).unwrap_or_else(|| {
 | |
|                         eprintln!("Error: Slot {} is not available", slot);
 | |
|                         exit(1);
 | |
|                     });
 | |
| 
 | |
|                     let accounts: Vec<_> = bank
 | |
|                         .get_program_accounts(None)
 | |
|                         .into_iter()
 | |
|                         .filter(|(pubkey, _account)| !solana_sdk::sysvar::is_sysvar_id(pubkey))
 | |
|                         .collect();
 | |
| 
 | |
|                     println!("---");
 | |
|                     for (pubkey, account) in accounts.into_iter() {
 | |
|                         println!("{}:", pubkey);
 | |
|                         println!("  - lamports: {}", account.lamports);
 | |
|                         println!("  - owner: '{}'", account.owner);
 | |
|                         println!("  - executable: {}", account.executable);
 | |
|                         println!("  - data: '{}'", bs58::encode(account.data).into_string());
 | |
|                     }
 | |
|                 }
 | |
|                 Err(err) => {
 | |
|                     eprintln!("Failed to load ledger: {:?}", err);
 | |
|                     exit(1);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         ("prune", Some(arg_matches)) => {
 | |
|             if let Some(prune_file_path) = arg_matches.value_of("slot_list") {
 | |
|                 let blockstore = open_blockstore(&ledger_path);
 | |
|                 let prune_file = File::open(prune_file_path.to_string()).unwrap();
 | |
|                 let slot_hashes: BTreeMap<u64, String> =
 | |
|                     serde_yaml::from_reader(prune_file).unwrap();
 | |
| 
 | |
|                 let iter =
 | |
|                     RootedSlotIterator::new(0, &blockstore).expect("Failed to get rooted slot");
 | |
| 
 | |
|                 let potential_hashes: Vec<_> = iter
 | |
|                     .filter_map(|(slot, _meta)| {
 | |
|                         let blockhash = blockstore
 | |
|                             .get_slot_entries(slot, 0, None)
 | |
|                             .unwrap()
 | |
|                             .last()
 | |
|                             .unwrap()
 | |
|                             .hash
 | |
|                             .to_string();
 | |
| 
 | |
|                         slot_hashes.get(&slot).and_then(|hash| {
 | |
|                             if *hash == blockhash {
 | |
|                                 Some((slot, blockhash))
 | |
|                             } else {
 | |
|                                 None
 | |
|                             }
 | |
|                         })
 | |
|                     })
 | |
|                     .collect();
 | |
| 
 | |
|                 let (target_slot, target_hash) = potential_hashes
 | |
|                     .last()
 | |
|                     .expect("Failed to find a valid slot");
 | |
|                 println!("Prune at slot {:?} hash {:?}", target_slot, target_hash);
 | |
|                 blockstore.prune(*target_slot);
 | |
|             }
 | |
|         }
 | |
|         ("list-roots", Some(arg_matches)) => {
 | |
|             let blockstore = open_blockstore(&ledger_path);
 | |
|             let max_height = if let Some(height) = arg_matches.value_of("max_height") {
 | |
|                 usize::from_str(height).expect("Maximum height must be a number")
 | |
|             } else {
 | |
|                 panic!("Maximum height must be provided");
 | |
|             };
 | |
|             let num_roots = if let Some(roots) = arg_matches.value_of("num_roots") {
 | |
|                 usize::from_str(roots).expect("Number of roots must be a number")
 | |
|             } else {
 | |
|                 usize::from_str(DEFAULT_ROOT_COUNT).unwrap()
 | |
|             };
 | |
| 
 | |
|             let iter = RootedSlotIterator::new(0, &blockstore).expect("Failed to get rooted slot");
 | |
| 
 | |
|             let slot_hash: Vec<_> = iter
 | |
|                 .filter_map(|(slot, _meta)| {
 | |
|                     if slot <= max_height as u64 {
 | |
|                         let blockhash = blockstore
 | |
|                             .get_slot_entries(slot, 0, None)
 | |
|                             .unwrap()
 | |
|                             .last()
 | |
|                             .unwrap()
 | |
|                             .hash;
 | |
|                         Some((slot, blockhash))
 | |
|                     } else {
 | |
|                         None
 | |
|                     }
 | |
|                 })
 | |
|                 .collect();
 | |
| 
 | |
|             let mut output_file: Box<dyn Write> =
 | |
|                 if let Some(path) = arg_matches.value_of("slot_list") {
 | |
|                     match File::create(path) {
 | |
|                         Ok(file) => Box::new(file),
 | |
|                         _ => Box::new(stdout()),
 | |
|                     }
 | |
|                 } else {
 | |
|                     Box::new(stdout())
 | |
|                 };
 | |
| 
 | |
|             slot_hash
 | |
|                 .into_iter()
 | |
|                 .rev()
 | |
|                 .enumerate()
 | |
|                 .for_each(|(i, (slot, hash))| {
 | |
|                     if i < num_roots {
 | |
|                         output_file
 | |
|                             .write_all(format!("{:?}: {:?}\n", slot, hash).as_bytes())
 | |
|                             .expect("failed to write");
 | |
|                     }
 | |
|                 });
 | |
|         }
 | |
|         ("bounds", Some(arg_matches)) => {
 | |
|             match open_blockstore(&ledger_path).slot_meta_iterator(0) {
 | |
|                 Ok(metas) => {
 | |
|                     let all = arg_matches.is_present("all");
 | |
| 
 | |
|                     println!("Collecting Ledger information...");
 | |
|                     let slots: Vec<_> = metas.map(|(slot, _)| slot).collect();
 | |
|                     if slots.is_empty() {
 | |
|                         println!("Ledger is empty. No slots found.");
 | |
|                     } else {
 | |
|                         let first = slots.first().unwrap();
 | |
|                         let last = slots.last().unwrap_or_else(|| first);
 | |
|                         if first != last {
 | |
|                             println!("Ledger contains data from slots {:?} to {:?}", first, last);
 | |
|                             if all {
 | |
|                                 println!("Non-empty slots: {:?}", slots);
 | |
|                             }
 | |
|                         } else {
 | |
|                             println!("Ledger only contains some data for slot {:?}", first);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 Err(err) => {
 | |
|                     eprintln!("Unable to read the Ledger: {:?}", err);
 | |
|                     exit(1);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         ("analyze-storage", _) => match analyze_storage(&open_database(&ledger_path)) {
 | |
|             Ok(()) => {
 | |
|                 println!("Ok.");
 | |
|             }
 | |
|             Err(err) => {
 | |
|                 eprintln!("Unable to read the Ledger: {:?}", err);
 | |
|                 exit(1);
 | |
|             }
 | |
|         },
 | |
|         ("", _) => {
 | |
|             eprintln!("{}", matches.usage());
 | |
|             exit(1);
 | |
|         }
 | |
|         _ => unreachable!(),
 | |
|     };
 | |
| }
 |