use clap::Parser as ClapParser;
#[cfg(feature = "chain-spec-pruning")]
use serde_json::Value;
use std::{io::Write, path::PathBuf};
use subxt_utils_fetchmetadata::Url;
mod fetch;
#[derive(Debug, ClapParser)]
pub struct Opts {
#[clap(long)]
url: Url,
#[clap(long, short, value_parser)]
output_file: Option<PathBuf>,
#[cfg(feature = "chain-spec-pruning")]
#[clap(long)]
state_root_hash: bool,
#[clap(long)]
remove_substitutes: bool,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ChainSpecError {
#[error("Failed to fetch the chain spec: {0}")]
FetchError(#[from] fetch::FetchSpecError),
#[cfg(feature = "chain-spec-pruning")]
#[error("Error while parsing the chain spec: {0})")]
ParseError(String),
#[cfg(feature = "chain-spec-pruning")]
#[error("Error computing state root hash: {0})")]
ComputeError(String),
#[error("Other: {0})")]
Other(String),
}
#[cfg(feature = "chain-spec-pruning")]
fn compute_state_root_hash(spec: &Value) -> Result<[u8; 32], ChainSpecError> {
let chain_spec = smoldot::chain_spec::ChainSpec::from_json_bytes(spec.to_string().as_bytes())
.map_err(|err| ChainSpecError::ParseError(err.to_string()))?;
let genesis_chain_information = chain_spec.to_chain_information().map(|(ci, _)| ci);
let state_root = match genesis_chain_information {
Ok(genesis_chain_information) => {
let header = genesis_chain_information.as_ref().finalized_block_header;
*header.state_root
}
Err(smoldot::chain_spec::FromGenesisStorageError::UnknownStorageItems) => *chain_spec
.genesis_storage()
.into_trie_root_hash()
.ok_or_else(|| {
ChainSpecError::ParseError(
"The chain spec does not contain the proper shape for the genesis.raw entry"
.to_string(),
)
})?,
Err(err) => return Err(ChainSpecError::ComputeError(err.to_string())),
};
Ok(state_root)
}
pub async fn run(opts: Opts, output: &mut impl Write) -> color_eyre::Result<()> {
let url = opts.url;
let mut spec = fetch::fetch_chain_spec(url).await?;
let mut output: Box<dyn Write> = match opts.output_file {
Some(path) => Box::new(std::fs::File::create(path)?),
None => Box::new(output),
};
#[cfg(feature = "chain-spec-pruning")]
if opts.state_root_hash {
let state_root_hash = compute_state_root_hash(&spec)?;
let state_root_hash = format!("0x{}", hex::encode(state_root_hash));
if let Some(genesis) = spec.get_mut("genesis") {
let object = genesis.as_object_mut().ok_or_else(|| {
ChainSpecError::Other("The genesis entry must be an object".to_string())
})?;
object.remove("raw").ok_or_else(|| {
ChainSpecError::Other("The genesis entry must contain a raw entry".to_string())
})?;
object.insert("stateRootHash".to_string(), Value::String(state_root_hash));
}
}
if opts.remove_substitutes {
let object = spec
.as_object_mut()
.ok_or_else(|| ChainSpecError::Other("The chain spec must be an object".to_string()))?;
object.remove("codeSubstitutes");
}
let json = serde_json::to_string_pretty(&spec)?;
write!(output, "{json}")?;
Ok(())
}