use ckb_app_config::{CKBAppConfig, ExitCode};
use ckb_chain_spec::ChainSpec;
use ckb_resource::{AVAILABLE_SPECS, Resource};
use ckb_types::{H256, packed::CellOutput};
use ckb_util::LinkedHashMap;
use clap::ArgMatches;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::cli;
#[derive(Clone, Debug, Serialize, Deserialize)]
struct SystemCell {
pub path: String,
pub tx_hash: H256,
pub index: usize,
pub data_hash: H256,
pub type_hash: Option<H256>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct DepGroupCell {
pub included_cells: Vec<String>,
pub tx_hash: H256,
pub index: usize,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct SpecHashes {
pub spec_hash: H256,
pub genesis: H256,
pub cellbase: H256,
pub system_cells: Vec<SystemCell>,
pub dep_groups: Vec<DepGroupCell>,
}
impl TryFrom<ChainSpec> for SpecHashes {
type Error = ExitCode;
fn try_from(mut spec: ChainSpec) -> Result<Self, Self::Error> {
let hash_option = spec.genesis.hash.take();
let consensus = spec.build_consensus().map_err(to_config_error)?;
if let Some(hash) = hash_option {
let genesis_hash: H256 = consensus.genesis_hash().into();
if hash != genesis_hash {
eprintln!(
"Genesis hash unmatched in {} chainspec config file:\n\
in file {:#x},\n\
actual {:#x}",
spec.name, hash, genesis_hash
);
}
}
let block = consensus.genesis_block();
let cellbase = &block.transactions()[0];
let dep_group_tx = &block.transactions()[1];
let cells_hashes = spec
.genesis
.system_cells
.iter()
.map(|system_cell| &system_cell.file)
.zip(
cellbase
.outputs()
.into_iter()
.zip(cellbase.outputs_data())
.skip(1),
)
.enumerate()
.map(|(index_minus_one, (resource, (output, data)))| {
let data_hash: H256 = CellOutput::calc_data_hash(&data.raw_data()).into();
let type_hash: Option<H256> = output
.type_()
.to_opt()
.map(|script| script.calc_script_hash().into());
SystemCell {
path: resource.to_string(),
tx_hash: cellbase.hash().into(),
index: index_minus_one + 1,
data_hash,
type_hash,
}
})
.collect();
let dep_groups = spec
.genesis
.dep_groups
.iter()
.enumerate()
.map(|(index, dep_group)| DepGroupCell {
included_cells: dep_group
.files
.iter()
.map(|res| res.to_string())
.collect::<Vec<_>>(),
tx_hash: dep_group_tx.hash().into(),
index,
})
.collect::<Vec<_>>();
Ok(SpecHashes {
spec_hash: spec.hash.into(),
genesis: consensus.genesis_hash().into(),
cellbase: cellbase.hash().into(),
system_cells: cells_hashes,
dep_groups,
})
}
}
pub fn list_hashes(root_dir: PathBuf, matches: &ArgMatches) -> Result<(), ExitCode> {
let mut specs = Vec::new();
let output_format = matches.get_one::<String>(cli::ARG_FORMAT).unwrap().as_str();
if matches.get_flag(cli::ARG_BUNDLED) {
if output_format == "toml" {
println!("# Generated by: ckb list-hashes -b\n");
}
for env in AVAILABLE_SPECS {
let spec = ChainSpec::load_from(&Resource::bundled(format!("specs/{env}.toml")))
.map_err(to_config_error)?;
let spec_name = spec.name.clone();
let spec_hashes: SpecHashes = spec.try_into()?;
specs.push((spec_name, spec_hashes));
}
} else {
if output_format == "toml" {
println!("# Generated by: ckb list-hashes");
}
let mut resource = Resource::ckb_config(&root_dir);
if !resource.exists() {
resource = Resource::bundled_ckb_config();
}
let mut config = CKBAppConfig::load_from_slice(&resource.get()?)?;
config.chain.spec.absolutize(&root_dir);
let chain_spec = ChainSpec::load_from(&config.chain.spec).map_err(to_config_error)?;
let spec_name = chain_spec.name.clone();
let spec_hashes: SpecHashes = chain_spec.try_into()?;
specs.push((spec_name, spec_hashes));
}
let mut map = LinkedHashMap::new();
for (spec_name, spec_hashes) in specs {
map.insert(spec_name, spec_hashes);
}
match output_format {
"json" => {
println!("{}", serde_json::to_string_pretty(&map).unwrap());
}
_ => {
println!("{}", toml::to_string(&map).unwrap());
}
}
Ok(())
}
fn to_config_error(err: Box<dyn std::error::Error>) -> ExitCode {
eprintln!("ERROR: {err}");
ExitCode::Config
}