1#![doc = include_str!("../README.md")]
19#[cfg(feature = "generate-readme")]
20docify::compile_markdown!("README.docify.md", "README.md");
21
22use clap::{Parser, Subcommand};
23use sc_chain_spec::{
24 json_patch, set_code_substitute_in_json_chain_spec, update_code_in_json_chain_spec, ChainType,
25 GenericChainSpec, GenesisConfigBuilderRuntimeCaller,
26};
27use serde::{Deserialize, Serialize};
28use serde_json::Value;
29use std::{
30 borrow::Cow,
31 fs,
32 path::{Path, PathBuf},
33};
34
35#[derive(Debug, Parser)]
37#[command(rename_all = "kebab-case", version, about)]
38pub struct ChainSpecBuilder {
39 #[command(subcommand)]
40 pub command: ChainSpecBuilderCmd,
41 #[arg(long, short, default_value = "./chain_spec.json")]
43 pub chain_spec_path: PathBuf,
44}
45
46#[derive(Debug, Subcommand)]
47#[command(rename_all = "kebab-case")]
48pub enum ChainSpecBuilderCmd {
49 Create(CreateCmd),
50 Verify(VerifyCmd),
51 UpdateCode(UpdateCodeCmd),
52 ConvertToRaw(ConvertToRawCmd),
53 ListPresets(ListPresetsCmd),
54 DisplayPreset(DisplayPresetCmd),
55 AddCodeSubstitute(AddCodeSubstituteCmd),
56}
57
58#[derive(Parser, Debug)]
60pub struct CreateCmd {
61 #[arg(long, short = 'n', default_value = "Custom")]
63 chain_name: String,
64 #[arg(long, short = 'i', default_value = "custom")]
66 chain_id: String,
67 #[arg(value_enum, short = 't', default_value = "live")]
69 chain_type: ChainType,
70 #[arg(long, value_enum, short = 'p', requires = "relay_chain")]
78 #[deprecated(
79 note = "The para_id information is not required anymore and will be removed starting with `stable2512`. Runtimes must implement a new API called `cumulus_primitives_core::GetParachainInfo` to still be compatible with node versions starting with `stable2512`."
80 )]
81 pub para_id: Option<u32>,
82 #[arg(long, value_enum, short = 'c')]
84 pub relay_chain: Option<String>,
85 #[arg(long, short, alias = "runtime-wasm-path")]
87 runtime: PathBuf,
88 #[arg(long, short = 's')]
90 raw_storage: bool,
91 #[arg(long, short = 'v')]
94 verify: bool,
95 #[arg(long, default_value = "tokenSymbol=UNIT,tokenDecimals=12")]
106 pub properties: Vec<String>,
107 #[command(subcommand)]
108 action: GenesisBuildAction,
109
110 #[clap(skip)]
112 code: Option<Cow<'static, [u8]>>,
113}
114
115#[derive(Subcommand, Debug, Clone)]
116enum GenesisBuildAction {
117 Patch(PatchCmd),
118 Full(FullCmd),
119 Default(DefaultCmd),
120 NamedPreset(NamedPresetCmd),
121}
122
123#[derive(Parser, Debug, Clone)]
125struct PatchCmd {
126 patch_path: PathBuf,
128}
129
130#[derive(Parser, Debug, Clone)]
132struct FullCmd {
133 config_path: PathBuf,
135}
136
137#[derive(Parser, Debug, Clone)]
141struct DefaultCmd {}
142
143#[derive(Parser, Debug, Clone)]
145struct NamedPresetCmd {
146 preset_name: String,
147}
148
149#[derive(Parser, Debug, Clone)]
157pub struct UpdateCodeCmd {
158 pub input_chain_spec: PathBuf,
162 #[arg(alias = "runtime-wasm-path")]
164 pub runtime: PathBuf,
165}
166
167#[derive(Parser, Debug, Clone)]
177pub struct AddCodeSubstituteCmd {
178 pub input_chain_spec: PathBuf,
180 #[arg(alias = "runtime-wasm-path")]
182 pub runtime: PathBuf,
183 pub block_height: u64,
185}
186
187#[derive(Parser, Debug, Clone)]
189pub struct ConvertToRawCmd {
190 pub input_chain_spec: PathBuf,
192}
193
194#[derive(Parser, Debug, Clone)]
196pub struct ListPresetsCmd {
197 #[arg(long, short, alias = "runtime-wasm-path")]
199 pub runtime: PathBuf,
200}
201
202#[derive(Parser, Debug, Clone)]
204pub struct DisplayPresetCmd {
205 #[arg(long, short, alias = "runtime-wasm-path")]
207 pub runtime: PathBuf,
208 #[arg(long, short)]
210 pub preset_name: Option<String>,
211}
212
213#[derive(Parser, Debug, Clone)]
219pub struct VerifyCmd {
220 pub input_chain_spec: PathBuf,
222}
223
224#[derive(Deserialize, Serialize, Clone)]
225pub struct ParachainExtension {
226 pub relay_chain: String,
228 #[deprecated(
232 note = "The para_id information is not required anymore and will be removed starting with `stable2512`. Runtimes must implement a new API called `cumulus_primitives_core::GetParachainInfo` to still be compatible with node versions starting with `stable2512`."
233 )]
234 pub para_id: Option<u32>,
235}
236
237type ChainSpec = GenericChainSpec<()>;
238
239impl ChainSpecBuilder {
240 pub fn run(&self) -> Result<(), String> {
242 let chain_spec_path = self.chain_spec_path.to_path_buf();
243
244 match &self.command {
245 ChainSpecBuilderCmd::Create(cmd) => {
246 let chain_spec_json = generate_chain_spec_for_runtime(&cmd)?;
247 fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?;
248 },
249 ChainSpecBuilderCmd::UpdateCode(UpdateCodeCmd {
250 ref input_chain_spec,
251 ref runtime,
252 }) => {
253 let mut chain_spec_json = extract_chain_spec_json(input_chain_spec.as_path())?;
254
255 update_code_in_json_chain_spec(
256 &mut chain_spec_json,
257 &fs::read(runtime.as_path())
258 .map_err(|e| format!("Wasm blob file could not be read: {e}"))?[..],
259 );
260
261 let chain_spec_json = serde_json::to_string_pretty(&chain_spec_json)
262 .map_err(|e| format!("to pretty failed: {e}"))?;
263 fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?;
264 },
265 ChainSpecBuilderCmd::AddCodeSubstitute(AddCodeSubstituteCmd {
266 ref input_chain_spec,
267 ref runtime,
268 block_height,
269 }) => {
270 let mut chain_spec_json = extract_chain_spec_json(input_chain_spec.as_path())?;
271
272 set_code_substitute_in_json_chain_spec(
273 &mut chain_spec_json,
274 &fs::read(runtime.as_path())
275 .map_err(|e| format!("Wasm blob file could not be read: {e}"))?[..],
276 *block_height,
277 );
278 let chain_spec_json = serde_json::to_string_pretty(&chain_spec_json)
279 .map_err(|e| format!("to pretty failed: {e}"))?;
280 fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?;
281 },
282 ChainSpecBuilderCmd::ConvertToRaw(ConvertToRawCmd { ref input_chain_spec }) => {
283 let chain_spec = ChainSpec::from_json_file(input_chain_spec.clone())?;
284
285 let mut genesis_json =
286 serde_json::from_str::<serde_json::Value>(&chain_spec.as_json(true)?)
287 .map_err(|e| format!("Conversion to json failed: {e}"))?;
288
289 genesis_json.as_object_mut().map(|map| {
292 map.retain(|key, _| key == "genesis");
293 });
294
295 let mut org_chain_spec_json = extract_chain_spec_json(input_chain_spec.as_path())?;
296
297 org_chain_spec_json
300 .get_mut("genesis")
301 .and_then(|genesis| genesis.as_object_mut())
302 .and_then(|genesis| genesis.remove("runtimeGenesis"));
303 json_patch::merge(&mut org_chain_spec_json, genesis_json);
304
305 let chain_spec_json = serde_json::to_string_pretty(&org_chain_spec_json)
306 .map_err(|e| format!("Conversion to pretty failed: {e}"))?;
307 fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?;
308 },
309 ChainSpecBuilderCmd::Verify(VerifyCmd { ref input_chain_spec }) => {
310 let chain_spec = ChainSpec::from_json_file(input_chain_spec.clone())?;
311 serde_json::from_str::<serde_json::Value>(&chain_spec.as_json(true)?)
312 .map_err(|e| format!("Conversion to json failed: {e}"))?;
313 },
314 ChainSpecBuilderCmd::ListPresets(ListPresetsCmd { runtime }) => {
315 let code = fs::read(runtime.as_path())
316 .map_err(|e| format!("wasm blob shall be readable {e}"))?;
317 let caller: GenesisConfigBuilderRuntimeCaller =
318 GenesisConfigBuilderRuntimeCaller::new(&code[..]);
319 let presets = caller
320 .preset_names()
321 .map_err(|e| format!("getting default config from runtime should work: {e}"))?;
322 println!("{}", serde_json::json!({"presets":presets}).to_string());
323 },
324 ChainSpecBuilderCmd::DisplayPreset(DisplayPresetCmd { runtime, preset_name }) => {
325 let code = fs::read(runtime.as_path())
326 .map_err(|e| format!("wasm blob shall be readable {e}"))?;
327 let caller: GenesisConfigBuilderRuntimeCaller =
328 GenesisConfigBuilderRuntimeCaller::new(&code[..]);
329 let preset = caller
330 .get_named_preset(preset_name.as_ref())
331 .map_err(|e| format!("getting default config from runtime should work: {e}"))?;
332 println!("{preset}");
333 },
334 }
335 Ok(())
336 }
337
338 pub fn set_create_cmd_runtime_code(&mut self, code: Cow<'static, [u8]>) {
343 match &mut self.command {
344 ChainSpecBuilderCmd::Create(cmd) => {
345 cmd.code = Some(code);
346 },
347 _ => {
348 panic!("Overwriting code blob is only supported for CreateCmd");
349 },
350 };
351 }
352}
353
354fn process_action<T: Serialize + Clone + Sync + 'static>(
355 cmd: &CreateCmd,
356 code: &[u8],
357 builder: sc_chain_spec::ChainSpecBuilder<T>,
358) -> Result<String, String> {
359 let builder = match cmd.action {
360 GenesisBuildAction::NamedPreset(NamedPresetCmd { ref preset_name }) =>
361 builder.with_genesis_config_preset_name(&preset_name),
362 GenesisBuildAction::Patch(PatchCmd { ref patch_path }) => {
363 let patch = fs::read(patch_path.as_path())
364 .map_err(|e| format!("patch file {patch_path:?} shall be readable: {e}"))?;
365 builder.with_genesis_config_patch(serde_json::from_slice::<Value>(&patch[..]).map_err(
366 |e| format!("patch file {patch_path:?} shall contain a valid json: {e}"),
367 )?)
368 },
369 GenesisBuildAction::Full(FullCmd { ref config_path }) => {
370 let config = fs::read(config_path.as_path())
371 .map_err(|e| format!("config file {config_path:?} shall be readable: {e}"))?;
372 builder.with_genesis_config(serde_json::from_slice::<Value>(&config[..]).map_err(
373 |e| format!("config file {config_path:?} shall contain a valid json: {e}"),
374 )?)
375 },
376 GenesisBuildAction::Default(DefaultCmd {}) => {
377 let caller: GenesisConfigBuilderRuntimeCaller =
378 GenesisConfigBuilderRuntimeCaller::new(&code);
379 let default_config = caller
380 .get_default_config()
381 .map_err(|e| format!("getting default config from runtime should work: {e}"))?;
382 builder.with_genesis_config(default_config)
383 },
384 };
385
386 let chain_spec = builder.build();
387
388 match (cmd.verify, cmd.raw_storage) {
389 (_, true) => chain_spec.as_json(true),
390 (true, false) => {
391 chain_spec.as_json(true)?;
392 println!("Genesis config verification: OK");
393 chain_spec.as_json(false)
394 },
395 (false, false) => chain_spec.as_json(false),
396 }
397}
398
399impl CreateCmd {
400 fn get_runtime_code(&self) -> Result<Cow<'static, [u8]>, String> {
404 Ok(if let Some(code) = self.code.clone() {
405 code
406 } else {
407 fs::read(self.runtime.as_path())
408 .map_err(|e| format!("wasm blob shall be readable {e}"))?
409 .into()
410 })
411 }
412}
413
414fn parse_properties(raw: &String, props: &mut sc_chain_spec::Properties) -> Result<(), String> {
416 for pair in raw.split(',') {
417 let mut iter = pair.splitn(2, '=');
418 let key = iter
419 .next()
420 .ok_or_else(|| format!("Invalid chain property key: {pair}"))?
421 .trim()
422 .to_owned();
423 let value_str = iter
424 .next()
425 .ok_or_else(|| format!("Invalid chain property value for key: {key}"))?
426 .trim();
427
428 let value = match value_str.parse::<bool>() {
430 Ok(b) => Value::Bool(b),
431 Err(_) => match value_str.parse::<u32>() {
432 Ok(i) => Value::Number(i.into()),
433 Err(_) => Value::String(value_str.to_string()),
434 },
435 };
436
437 props.insert(key, value);
438 }
439 Ok(())
440}
441
442pub fn generate_chain_spec_for_runtime(cmd: &CreateCmd) -> Result<String, String> {
444 let code = cmd.get_runtime_code()?;
445
446 let chain_type = &cmd.chain_type;
447
448 let mut properties = sc_chain_spec::Properties::new();
449 for raw in &cmd.properties {
450 parse_properties(raw, &mut properties)?;
451 }
452
453 let builder = ChainSpec::builder(&code[..], Default::default())
454 .with_name(&cmd.chain_name[..])
455 .with_id(&cmd.chain_id[..])
456 .with_properties(properties)
457 .with_chain_type(chain_type.clone());
458
459 let chain_spec_json_string = process_action(&cmd, &code[..], builder)?;
460 let parachain_properties = cmd.relay_chain.as_ref().map(|rc| {
461 #[allow(deprecated)]
463 cmd.para_id
464 .map(|para_id| {
465 eprintln!("Note: usage of deprecated `para-id` flag is not recommended. Please consider implementing the `cumulus_primitives_core::GetParachainInfo` runtime API for your runtime. The `para-id` flag will be removed starting with `stable2512`.");
467 serde_json::json!({
468 "relay_chain": rc,
469 "para_id": para_id,
470 })
471 })
472 .unwrap_or(serde_json::json!({
473 "relay_chain": rc,
474 }))
475 });
476
477 let chain_spec = parachain_properties
478 .map(|props| {
479 let chain_spec_json_blob = serde_json::from_str(chain_spec_json_string.as_str())
480 .map_err(|e| format!("deserialization a json failed {e}"));
481 chain_spec_json_blob.and_then(|mut cs| {
482 json_patch::merge(&mut cs, props);
483 serde_json::to_string_pretty(&cs).map_err(|e| format!("to pretty failed: {e}"))
484 })
485 })
486 .unwrap_or(Ok(chain_spec_json_string));
487 chain_spec
488}
489
490fn extract_chain_spec_json(input_chain_spec: &Path) -> Result<serde_json::Value, String> {
492 let chain_spec = &fs::read(input_chain_spec)
493 .map_err(|e| format!("Provided chain spec could not be read: {e}"))?;
494
495 serde_json::from_slice(&chain_spec).map_err(|e| format!("Conversion to json failed: {e}"))
496}