2019-08-09 15:57:31 -07:00
|
|
|
use clap::{crate_description, crate_name, crate_version, value_t_or_exit, App, Arg, SubCommand};
|
2019-10-08 14:58:49 -07:00
|
|
|
use solana_core::blocktree_processor::{process_blocktree, ProcessOptions};
|
2019-10-18 09:28:51 -07:00
|
|
|
use solana_core::rooted_slot_iterator::RootedSlotIterator;
|
|
|
|
use solana_ledger::blocktree::Blocktree;
|
2019-09-06 14:30:56 -07:00
|
|
|
use solana_sdk::clock::Slot;
|
2019-02-18 22:26:22 -08:00
|
|
|
use solana_sdk::genesis_block::GenesisBlock;
|
2019-07-12 16:58:13 -07:00
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use std::fs::File;
|
2018-08-04 14:31:12 -07:00
|
|
|
use std::io::{stdout, Write};
|
2019-07-30 15:53:41 -07:00
|
|
|
use std::path::PathBuf;
|
2018-08-06 16:03:08 -07:00
|
|
|
use std::process::exit;
|
2019-07-12 16:58:13 -07:00
|
|
|
use std::str::FromStr;
|
2018-08-04 14:31:12 -07:00
|
|
|
|
2019-07-11 20:33:36 -07:00
|
|
|
#[derive(PartialEq)]
|
|
|
|
enum LedgerOutputMethod {
|
|
|
|
Print,
|
|
|
|
Json,
|
|
|
|
}
|
2019-08-09 15:57:31 -07:00
|
|
|
|
|
|
|
fn output_slot(blocktree: &Blocktree, slot: u64, method: &LedgerOutputMethod) {
|
|
|
|
let entries = blocktree
|
|
|
|
.get_slot_entries(slot, 0, None)
|
|
|
|
.unwrap_or_else(|err| {
|
|
|
|
eprintln!("Failed to load entries for slot {}: {:?}", slot, err);
|
|
|
|
exit(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
for entry in entries {
|
|
|
|
match method {
|
|
|
|
LedgerOutputMethod::Print => println!("{:?}", entry),
|
|
|
|
LedgerOutputMethod::Json => {
|
|
|
|
serde_json::to_writer(stdout(), &entry).expect("serialize entry");
|
|
|
|
stdout().write_all(b",\n").expect("newline");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-11 20:33:36 -07:00
|
|
|
fn output_ledger(blocktree: Blocktree, starting_slot: u64, method: LedgerOutputMethod) {
|
2019-10-18 09:28:51 -07:00
|
|
|
let rooted_slot_iterator =
|
|
|
|
RootedSlotIterator::new(starting_slot, &blocktree).unwrap_or_else(|err| {
|
2019-07-11 20:33:36 -07:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-09 15:57:31 -07:00
|
|
|
output_slot(&blocktree, slot, &method);
|
2019-07-11 20:33:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if method == LedgerOutputMethod::Json {
|
|
|
|
stdout().write_all(b"\n]}\n").expect("close array");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-04 14:31:12 -07:00
|
|
|
fn main() {
|
2019-07-12 16:58:13 -07:00
|
|
|
const DEFAULT_ROOT_COUNT: &str = "1";
|
2018-12-14 12:36:50 -08:00
|
|
|
solana_logger::setup();
|
2019-08-09 15:57:31 -07:00
|
|
|
|
|
|
|
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");
|
|
|
|
|
2019-07-11 20:33:36 -07:00
|
|
|
let matches = App::new(crate_name!())
|
|
|
|
.about(crate_description!())
|
2018-08-06 20:51:12 -07:00
|
|
|
.version(crate_version!())
|
2018-08-04 14:31:12 -07:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("ledger")
|
|
|
|
.short("l")
|
|
|
|
.long("ledger")
|
|
|
|
.value_name("DIR")
|
|
|
|
.takes_value(true)
|
2019-08-09 15:57:31 -07:00
|
|
|
.global(true)
|
2018-09-14 15:32:57 -07:00
|
|
|
.help("Use directory for ledger location"),
|
2018-08-04 14:31:12 -07:00
|
|
|
)
|
2019-08-09 15:57:31 -07:00
|
|
|
.subcommand(SubCommand::with_name("print").about("Print the ledger").arg(&starting_slot_arg))
|
|
|
|
.subcommand(SubCommand::with_name("print-slot").about("Print the contents of one slot").arg(
|
|
|
|
Arg::with_name("slot")
|
|
|
|
.index(1)
|
|
|
|
.value_name("SLOT")
|
2018-12-07 20:44:59 -08:00
|
|
|
.takes_value(true)
|
2019-08-09 15:57:31 -07:00
|
|
|
.required(true)
|
|
|
|
.help("The slot to print"),
|
|
|
|
))
|
|
|
|
.subcommand(SubCommand::with_name("json").about("Print the ledger in JSON format").arg(&starting_slot_arg))
|
2018-08-09 22:14:04 -07:00
|
|
|
.subcommand(SubCommand::with_name("verify").about("Verify the ledger's PoH"))
|
2019-07-12 16:58:13 -07:00
|
|
|
.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)
|
2019-07-17 14:42:29 -07:00
|
|
|
.required(true)
|
2019-07-12 16:58:13 -07:00
|
|
|
.help("The location of the YAML file with a list of rollback slot heights and hashes"),
|
|
|
|
))
|
2019-08-09 15:57:31 -07:00
|
|
|
.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(
|
2019-07-12 16:58:13 -07:00
|
|
|
Arg::with_name("slot_list")
|
|
|
|
.long("slot-list")
|
|
|
|
.value_name("FILENAME")
|
|
|
|
.required(false)
|
|
|
|
.takes_value(true)
|
2019-08-09 15:57:31 -07:00
|
|
|
.help("The location of the output YAML file. A list of rollback slot heights and hashes will be written to the file.")).arg(
|
2019-07-12 16:58:13 -07:00
|
|
|
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"),
|
|
|
|
))
|
2018-08-04 14:31:12 -07:00
|
|
|
.get_matches();
|
|
|
|
|
2019-08-09 15:57:31 -07:00
|
|
|
let ledger_path = PathBuf::from(value_t_or_exit!(matches, "ledger", String));
|
2018-08-10 18:41:26 -07:00
|
|
|
|
2019-07-30 15:53:41 -07:00
|
|
|
let genesis_block = GenesisBlock::load(&ledger_path).unwrap_or_else(|err| {
|
2019-01-24 12:04:04 -08:00
|
|
|
eprintln!(
|
2019-07-30 15:53:41 -07:00
|
|
|
"Failed to open ledger genesis_block at {:?}: {}",
|
2019-01-24 12:04:04 -08:00
|
|
|
ledger_path, err
|
|
|
|
);
|
|
|
|
exit(1);
|
|
|
|
});
|
|
|
|
|
2019-07-30 15:53:41 -07:00
|
|
|
let blocktree = match Blocktree::open(&ledger_path) {
|
2019-02-07 20:52:39 -08:00
|
|
|
Ok(blocktree) => blocktree,
|
2019-01-03 21:29:21 -08:00
|
|
|
Err(err) => {
|
2019-07-30 15:53:41 -07:00
|
|
|
eprintln!("Failed to open ledger at {:?}: {}", ledger_path, err);
|
2018-08-10 18:41:26 -07:00
|
|
|
exit(1);
|
|
|
|
}
|
2019-01-03 21:29:21 -08:00
|
|
|
};
|
2018-09-26 18:37:24 -07:00
|
|
|
|
2018-08-06 16:03:08 -07:00
|
|
|
match matches.subcommand() {
|
2019-08-09 15:57:31 -07:00
|
|
|
("print", Some(args_matches)) => {
|
|
|
|
let starting_slot = value_t_or_exit!(args_matches, "starting_slot", Slot);
|
2019-07-11 20:33:36 -07:00
|
|
|
output_ledger(blocktree, starting_slot, LedgerOutputMethod::Print);
|
2018-08-09 22:14:04 -07:00
|
|
|
}
|
2019-08-09 15:57:31 -07:00
|
|
|
("print-slot", Some(args_matches)) => {
|
|
|
|
let slot = value_t_or_exit!(args_matches, "slot", Slot);
|
|
|
|
output_slot(&blocktree, slot, &LedgerOutputMethod::Print);
|
|
|
|
}
|
|
|
|
("json", Some(args_matches)) => {
|
|
|
|
let starting_slot = value_t_or_exit!(args_matches, "starting_slot", Slot);
|
2019-07-11 20:33:36 -07:00
|
|
|
output_ledger(blocktree, starting_slot, LedgerOutputMethod::Json);
|
|
|
|
}
|
|
|
|
("verify", _) => {
|
|
|
|
println!("Verifying ledger...");
|
2019-10-08 14:58:49 -07:00
|
|
|
let options = ProcessOptions {
|
|
|
|
verify_ledger: true,
|
|
|
|
..ProcessOptions::default()
|
|
|
|
};
|
|
|
|
match process_blocktree(&genesis_block, &blocktree, None, options) {
|
2019-07-11 20:33:36 -07:00
|
|
|
Ok((_bank_forks, bank_forks_info, _)) => {
|
|
|
|
println!("{:?}", bank_forks_info);
|
2018-08-09 22:14:04 -07:00
|
|
|
}
|
2019-07-11 20:33:36 -07:00
|
|
|
Err(err) => {
|
|
|
|
eprintln!("Ledger verification failed: {:?}", err);
|
|
|
|
exit(1);
|
2018-12-07 20:44:59 -08:00
|
|
|
}
|
2018-08-06 16:03:08 -07:00
|
|
|
}
|
|
|
|
}
|
2019-07-12 16:58:13 -07:00
|
|
|
("prune", Some(args_matches)) => {
|
|
|
|
if let Some(prune_file_path) = args_matches.value_of("slot_list") {
|
|
|
|
let prune_file = File::open(prune_file_path.to_string()).unwrap();
|
|
|
|
let slot_hashes: BTreeMap<u64, String> =
|
|
|
|
serde_yaml::from_reader(prune_file).unwrap();
|
|
|
|
|
2019-10-18 09:28:51 -07:00
|
|
|
let iter =
|
|
|
|
RootedSlotIterator::new(0, &blocktree).expect("Failed to get rooted slot");
|
2019-07-12 16:58:13 -07:00
|
|
|
|
|
|
|
let potential_hashes: Vec<_> = iter
|
|
|
|
.filter_map(|(slot, meta)| {
|
|
|
|
let blockhash = blocktree
|
|
|
|
.get_slot_entries(slot, meta.last_index, Some(1))
|
|
|
|
.unwrap()
|
|
|
|
.first()
|
|
|
|
.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);
|
2019-07-17 14:42:29 -07:00
|
|
|
blocktree.prune(*target_slot);
|
2019-07-12 16:58:13 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
("list-roots", Some(args_matches)) => {
|
|
|
|
let max_height = if let Some(height) = args_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) = args_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()
|
|
|
|
};
|
|
|
|
|
2019-10-18 09:28:51 -07:00
|
|
|
let iter = RootedSlotIterator::new(0, &blocktree).expect("Failed to get rooted slot");
|
2019-07-12 16:58:13 -07:00
|
|
|
|
|
|
|
let slot_hash: Vec<_> = iter
|
|
|
|
.filter_map(|(slot, meta)| {
|
|
|
|
if slot <= max_height as u64 {
|
|
|
|
let blockhash = blocktree
|
|
|
|
.get_slot_entries(slot, meta.last_index, Some(1))
|
|
|
|
.unwrap()
|
|
|
|
.first()
|
|
|
|
.unwrap()
|
|
|
|
.hash;
|
|
|
|
Some((slot, blockhash))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
2019-08-15 13:00:09 -07:00
|
|
|
let mut output_file: Box<dyn Write> =
|
|
|
|
if let Some(path) = args_matches.value_of("slot_list") {
|
|
|
|
match File::create(path) {
|
|
|
|
Ok(file) => Box::new(file),
|
|
|
|
_ => Box::new(stdout()),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Box::new(stdout())
|
|
|
|
};
|
2019-07-12 16:58:13 -07:00
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2018-08-06 16:03:08 -07:00
|
|
|
("", _) => {
|
2018-08-09 22:14:04 -07:00
|
|
|
eprintln!("{}", matches.usage());
|
|
|
|
exit(1);
|
2018-08-06 16:03:08 -07:00
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
2018-08-04 14:31:12 -07:00
|
|
|
}
|