use std::{fs, path::PathBuf}; use clap::{Parser, Subcommand}; use serde_json::Value; use sc_chain_spec::{ ChainType, GenericChainSpec, GenesisConfigBuilderRuntimeCaller, }; #[derive(Debug, Parser)] #[command(rename_all = "kebab-case", version, about)] pub struct ChainSpecBuilder { #[command(subcommand)] pub command: ChainSpecBuilderCmd, /// The path where the chain should be saved. #[arg(long, short, default_value = "./chain_spec.json")] pub chain_spec_path: PathBuf, } #[derive(Debug, Subcommand)] #[command(rename_all = "kebab-case")] pub enum ChainSpecBuilderCmd { Create(CreateCmd), Verify(VerifyCmd), UpdateCode(UpdateCodeCmd), ConvertToRaw(ConvertToRawCmd), ListPresets(ListPresetsCmd), DisplayPreset(DisplayPresetCmd), } #[derive(Parser, Debug)] pub struct CreateCmd { /// The name of chain. #[arg(long, short = 'n', default_value = "Casper")] chain_name: String, /// The chain id. #[arg(long, short = 'i', default_value = "casper")] chain_id: String, /// The path to runtime wasm blob. #[arg(long, short)] runtime_wasm_path: PathBuf, /// Export chainspec as raw storage. #[arg(long, short = 's')] raw_storage: bool, /// Verify the genesis config. This silently generates the raw storage from /// genesis config. Any errors will be reported. #[arg(long, short = 'v')] verify: bool, #[command(subcommand)] action: GenesisBuildAction, } #[derive(Subcommand, Debug, Clone)] enum GenesisBuildAction { Patch(PatchCmd), Full(FullCmd), Default(DefaultCmd), NamedPreset(NamedPresetCmd), } /// Pathches the runtime's default genesis config with provided patch. #[derive(Parser, Debug, Clone)] struct PatchCmd { /// The path to the full runtime genesis config json file. patch_path: PathBuf, } /// Build the genesis config for runtime using provided json file. /// No defaults will be used. #[derive(Parser, Debug, Clone)] struct FullCmd { /// The path to the full runtime genesis config json file. config_path: PathBuf, } /// Gets the default genesis config for the runtime and uses it in ChainSpec. /// Please note that default genesis config may not be valid. For some runtimes /// initial values should be added there (e.g. session keys, babe epoch). #[derive(Parser, Debug, Clone)] struct DefaultCmd {} /// Uses named preset provided by runtime to build the chain spec. #[derive(Parser, Debug, Clone)] struct NamedPresetCmd { preset_name: String, } /// Updates the coe on the provided input chain spec. /// /// The code field of the chain spec will be updated with the runtime provided /// in the command line. The operation supports both plain and raw formats. /// /// This command does not update chain-spec file in-place. The result of this /// command will be stored in a file given as `-c/--chain-spec-path` command /// line argument. #[derive(Parser, Debug, Clone)] pub struct UpdateCodeCmd { /// Chain spec to be updated. /// /// Please note that the file will not be updated in-place. pub input_chain_spec: PathBuf, /// The path to new runtime wasm blob to be stored into chain-spec. pub runtime_wasm_path: PathBuf, } /// Converts the given chain spec into raw format. #[derive(Parser, Debug, Clone)] pub struct ConvertToRawCmd { /// Chain spec to be converted. pub input_chain_spec: PathBuf, } /// Lists avaiable presets. #[derive(Parser, Debug, Clone)] pub struct ListPresetsCmd { /// The path to runtime wasm blob. #[arg(long, short)] pub runtime_wasm_path: PathBuf, } /// Displays given preset. #[derive(Parser, Debug, Clone)] pub struct DisplayPresetCmd { /// The path to runtime wasm blob. #[arg(long, short)] pub runtime_wasm_path: PathBuf, /// Preset to be displayed. If none if given default will be displayed. #[arg(long, short)] pub preset_name: Option, } /// Verifies the provided input chain spec. /// /// Silently checks if given input chain spec can be converted to raw. It allows /// to check if all `RuntimeGenesisConfig` fields are properly initialized and /// if the json does not contain invalid fields. #[derive(Parser, Debug, Clone)] pub struct VerifyCmd { /// Chain spec to be verified. pub input_chain_spec: PathBuf, } /// Processes `CreateCmd` and returns JSON version of `ChainSpec`. pub fn generate_chain_spec_for_runtime(cmd: &CreateCmd) -> Result { let code = fs::read(cmd.runtime_wasm_path.as_path()) .map_err(|e| format!("wasm blob shall be readable {e}"))?; let builder = GenericChainSpec::<()>::builder(&code[..], Default::default()) .with_name(&cmd.chain_name[..]) .with_id(&cmd.chain_id[..]) .with_chain_type(ChainType::Live); let builder = match cmd.action { GenesisBuildAction::NamedPreset(NamedPresetCmd { ref preset_name }) => builder.with_genesis_config_preset_name(&preset_name), GenesisBuildAction::Patch(PatchCmd { ref patch_path }) => { let patch = fs::read(patch_path.as_path()) .map_err(|e| format!("patch file {patch_path:?} shall be readable: {e}"))?; builder.with_genesis_config_patch(serde_json::from_slice::(&patch[..]).map_err( |e| format!("patch file {patch_path:?} shall contain a valid json: {e}"), )?) }, GenesisBuildAction::Full(FullCmd { ref config_path }) => { let config = fs::read(config_path.as_path()) .map_err(|e| format!("config file {config_path:?} shall be readable: {e}"))?; builder.with_genesis_config(serde_json::from_slice::(&config[..]).map_err( |e| format!("config file {config_path:?} shall contain a valid json: {e}"), )?) }, GenesisBuildAction::Default(DefaultCmd {}) => { let caller: GenesisConfigBuilderRuntimeCaller = GenesisConfigBuilderRuntimeCaller::new(&code[..]); let default_config = caller .get_default_config() .map_err(|e| format!("getting default config from runtime should work: {e}"))?; builder.with_genesis_config(default_config) }, }; let chain_spec = builder.build(); match (cmd.verify, cmd.raw_storage) { (_, true) => chain_spec.as_json(true), (true, false) => { chain_spec.as_json(true)?; println!("Genesis config verification: OK"); chain_spec.as_json(false) }, (false, false) => chain_spec.as_json(false), } }