mod build;
mod convert;
mod directory;
mod info;
mod read;
mod verify;
use std::{collections::BTreeMap, fs::File, io::BufReader};
use anyhow::Context;
use brandon::format::types::{
TYPE_ACCUMULATOR, TYPE_COMPRESSED_BEACON_STATE, TYPE_COMPRESSED_BODY, TYPE_COMPRESSED_HEADER,
TYPE_COMPRESSED_RECEIPTS, TYPE_COMPRESSED_SIGNED_BEACON_BLOCK, TYPE_SLOT_INDEX,
TYPE_TOTAL_DIFFICULTY, TYPE_VERSION,
};
use clap::{Parser, Subcommand};
use crate::cli::{convert::ConvertCommand, read::ReadArgs};
#[derive(Parser)]
#[command(name = "brandon", version, about)]
#[command(propagate_version = true)]
pub struct Args {
#[arg(short, long, global = true)]
pub json: bool,
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand)]
pub enum Command {
Info {
file: String,
},
Verify {
file: String,
#[arg(long)]
manifest: Option<String>,
},
Read(ReadArgs),
Build {
#[arg(long)]
blocks_dir: String,
#[arg(long)]
state: Option<String>,
#[arg(long, requires = "state")]
state_slot: Option<u64>,
#[arg(short, long)]
output: String,
},
Convert {
#[command(subcommand)]
command: ConvertCommand,
},
}
pub fn run() -> anyhow::Result<()> {
let args = Args::parse();
match args.command {
Command::Info { file } => info::run(&file, args.json),
Command::Verify { file, manifest } => verify::run(&file, manifest.as_deref(), args.json),
Command::Read(args) => read::run(args),
Command::Build {
blocks_dir,
state,
state_slot,
output,
} => build::run(&blocks_dir, state.as_deref(), state_slot, &output),
Command::Convert { command } => convert::run(command),
}
}
pub fn entry_type_name(typ: &[u8; 2]) -> &'static str {
match *typ {
TYPE_VERSION => "Version",
TYPE_COMPRESSED_SIGNED_BEACON_BLOCK => "CompressedSignedBeaconBlock",
TYPE_COMPRESSED_BEACON_STATE => "CompressedBeaconState",
TYPE_SLOT_INDEX => "SlotIndex",
TYPE_COMPRESSED_HEADER => "CompressedHeader",
TYPE_COMPRESSED_BODY => "BlockBody",
TYPE_COMPRESSED_RECEIPTS => "Receipts",
TYPE_TOTAL_DIFFICULTY => "TotalDifficulty",
TYPE_ACCUMULATOR => "BlockAccumulator",
_ => "Unknown",
}
}
pub fn human_size(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB", "TiB"];
if bytes == 0 {
return "0 B".into();
}
let mut size = bytes as f64;
let mut unit_idx = 0;
while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
size /= 1024.0;
unit_idx += 1;
}
if unit_idx == 0 {
format!("{bytes} B")
} else {
format!("{size:.2} {unit}", unit = UNITS[unit_idx])
}
}
pub fn count_entry_types(headers: &[brandon::format::e2store::Header]) -> BTreeMap<String, usize> {
let mut counts: BTreeMap<String, usize> = BTreeMap::new();
for h in headers {
if h.is_version() {
continue; }
let name = entry_type_name(&h.typ).to_string();
*counts.entry(name).or_insert(0) += 1;
}
counts
}
pub fn open_file(path: &str) -> anyhow::Result<BufReader<File>> {
let file = File::open(path).with_context(|| format!("cannot open {path}"))?;
Ok(BufReader::new(file))
}
pub fn output<T: serde::Serialize + std::fmt::Display>(value: &T, json: bool) {
if json {
println!("{}", serde_json::to_string_pretty(value).unwrap());
} else {
println!("{value}")
}
}