Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 18330f8

Browse files
chain-spec-builder: support for runtime wasm blobs
1 parent ac2d1e2 commit 18330f8

File tree

2 files changed

+252
-72
lines changed

2 files changed

+252
-72
lines changed

bin/utils/chain-spec-builder/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" }
2323
sp-core = { version = "21.0.0", path = "../../../primitives/core" }
2424
sp-keystore = { version = "0.27.0", path = "../../../primitives/keystore" }
2525
kitchensink-runtime = { version = "3.0.0-dev", path = "../../node/runtime" }
26+
serde_json = "1.0.100"
27+
sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" }
28+
log = "0.4.17"

bin/utils/chain-spec-builder/src/main.rs

Lines changed: 249 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ use std::{
2222
};
2323

2424
use ansi_term::Style;
25-
use clap::Parser;
25+
use clap::{Parser, Subcommand};
2626
use rand::{distributions::Alphanumeric, rngs::OsRng, Rng};
27+
use sc_chain_spec::{GenericChainSpec, GenesisConfigBuilderRuntimeCaller};
2728

2829
use node_cli::chain_spec::{self, AccountId};
2930
use sc_keystore::LocalKeystore;
31+
use serde_json::Value;
3032
use sp_core::{
3133
crypto::{ByteArray, Ss58Codec},
3234
sr25519,
@@ -35,64 +37,152 @@ use sp_keystore::KeystorePtr;
3537

3638
/// A utility to easily create a testnet chain spec definition with a given set
3739
/// of authorities and endowed accounts and/or generate random accounts.
38-
#[derive(Parser)]
40+
#[derive(Debug, Parser)]
3941
#[command(rename_all = "kebab-case")]
40-
enum ChainSpecBuilder {
41-
/// Create a new chain spec with the given authorities, endowed and sudo
42-
/// accounts.
43-
New {
44-
/// Authority key seed.
45-
#[arg(long, short, required = true)]
46-
authority_seeds: Vec<String>,
47-
/// Active nominators (SS58 format), each backing a random subset of the aforementioned
48-
/// authorities.
49-
#[arg(long, short, default_value = "0")]
50-
nominator_accounts: Vec<String>,
51-
/// Endowed account address (SS58 format).
52-
#[arg(long, short)]
53-
endowed_accounts: Vec<String>,
54-
/// Sudo account address (SS58 format).
55-
#[arg(long, short)]
56-
sudo_account: String,
57-
/// The path where the chain spec should be saved.
58-
#[arg(long, short, default_value = "./chain_spec.json")]
59-
chain_spec_path: PathBuf,
60-
},
61-
/// Create a new chain spec with the given number of authorities and endowed
62-
/// accounts. Random keys will be generated as required.
63-
Generate {
64-
/// The number of authorities.
65-
#[arg(long, short)]
66-
authorities: usize,
67-
/// The number of nominators backing the aforementioned authorities.
68-
///
69-
/// Will nominate a random subset of `authorities`.
70-
#[arg(long, short, default_value_t = 0)]
71-
nominators: usize,
72-
/// The number of endowed accounts.
73-
#[arg(long, short, default_value_t = 0)]
74-
endowed: usize,
75-
/// The path where the chain spec should be saved.
76-
#[arg(long, short, default_value = "./chain_spec.json")]
77-
chain_spec_path: PathBuf,
78-
/// Path to use when saving generated keystores for each authority.
79-
///
80-
/// At this path, a new folder will be created for each authority's
81-
/// keystore named `auth-$i` where `i` is the authority index, i.e.
82-
/// `auth-0`, `auth-1`, etc.
83-
#[arg(long, short)]
84-
keystore_path: Option<PathBuf>,
85-
},
42+
struct ChainSpecBuilder {
43+
#[command(subcommand)]
44+
command: ChainSpecBuilderCmd,
45+
/// The path where the chain spec should be saved.
46+
#[arg(long, short, default_value = "./chain_spec.json")]
47+
chain_spec_path: PathBuf,
8648
}
8749

88-
impl ChainSpecBuilder {
89-
/// Returns the path where the chain spec should be saved.
90-
fn chain_spec_path(&self) -> &Path {
91-
match self {
92-
ChainSpecBuilder::New { chain_spec_path, .. } => chain_spec_path.as_path(),
93-
ChainSpecBuilder::Generate { chain_spec_path, .. } => chain_spec_path.as_path(),
94-
}
95-
}
50+
#[derive(Debug, Subcommand)]
51+
#[command(rename_all = "kebab-case")]
52+
enum ChainSpecBuilderCmd {
53+
New(NewCmd),
54+
Generate(GenerateCmd),
55+
Runtime(RuntimeCmd),
56+
Edit(EditCmd),
57+
Verify(VerifyCmd),
58+
}
59+
60+
/// Create a new chain spec with the given authorities, endowed and sudo
61+
/// accounts. Only works for kitchen-sink runtime
62+
#[derive(Parser, Debug)]
63+
#[command(rename_all = "kebab-case")]
64+
struct NewCmd {
65+
/// Authority key seed.
66+
#[arg(long, short, required = true)]
67+
authority_seeds: Vec<String>,
68+
/// Active nominators (SS58 format), each backing a random subset of the aforementioned
69+
/// authorities.
70+
#[arg(long, short, default_value = "0")]
71+
nominator_accounts: Vec<String>,
72+
/// Endowed account address (SS58 format).
73+
#[arg(long, short)]
74+
endowed_accounts: Vec<String>,
75+
/// Sudo account address (SS58 format).
76+
#[arg(long, short)]
77+
sudo_account: String,
78+
}
79+
80+
/// Create a new chain spec with the given number of authorities and endowed
81+
/// accounts. Random keys will be generated as required. Only works for kitchen-sink runtime
82+
#[derive(Parser, Debug)]
83+
struct GenerateCmd {
84+
/// The number of authorities.
85+
#[arg(long, short)]
86+
authorities: usize,
87+
/// The number of nominators backing the aforementioned authorities.
88+
///
89+
/// Will nominate a random subset of `authorities`.
90+
#[arg(long, short, default_value_t = 0)]
91+
nominators: usize,
92+
/// The number of endowed accounts.
93+
#[arg(long, short, default_value_t = 0)]
94+
endowed: usize,
95+
/// Path to use when saving generated keystores for each authority.
96+
///
97+
/// At this path, a new folder will be created for each authority's
98+
/// keystore named `auth-$i` where `i` is the authority index, i.e.
99+
/// `auth-0`, `auth-1`, etc.
100+
#[arg(long, short)]
101+
keystore_path: Option<PathBuf>,
102+
}
103+
104+
/// Create a new chain spec by interacting with the provided runtime wasm blob.
105+
#[derive(Parser, Debug)]
106+
struct RuntimeCmd {
107+
/// The name of chain
108+
#[arg(long, short = 'n', default_value = "Custom")]
109+
chain_name: String,
110+
/// The chain id
111+
#[arg(long, short = 'i', default_value = "custom")]
112+
chain_id: String,
113+
/// The path to runtime wasm blob
114+
#[arg(long, short)]
115+
runtime_wasm_path: PathBuf,
116+
/// Export chainspec as raw storage
117+
#[arg(long, short = 's')]
118+
raw_storage: bool,
119+
/// Verify the genesis config. This silently generates the raw storage from genesis config. Any
120+
/// errors will be reported.
121+
#[arg(long, short = 'v')]
122+
verify: bool,
123+
#[command(subcommand)]
124+
action: GenesisBuildAction,
125+
}
126+
127+
#[derive(Subcommand, Debug, Clone)]
128+
enum GenesisBuildAction {
129+
Patch(PatchCmd),
130+
Full(FullCmd),
131+
Default(DefaultCmd),
132+
}
133+
134+
/// Patches the runtime's default genesis config with provided patch.
135+
#[derive(Parser, Debug, Clone)]
136+
struct PatchCmd {
137+
/// The path to the runtime genesis config patch.
138+
#[arg(long, short)]
139+
patch_path: PathBuf,
140+
}
141+
142+
/// Build the genesis config for runtime using provided json file. No defaults will be used.
143+
#[derive(Parser, Debug, Clone)]
144+
struct FullCmd {
145+
/// The path to the full runtime genesis config json file.
146+
#[arg(long, short)]
147+
config_path: PathBuf,
148+
}
149+
150+
/// Gets the default genesis config for the runtime and uses it in ChainSpec. Please note that
151+
/// default genesis config may not be valid. For some runtimes initial values should be added there
152+
/// (e.g. session keys, babe epoch).
153+
#[derive(Parser, Debug, Clone)]
154+
struct DefaultCmd {
155+
#[arg(long, short)]
156+
/// If provided stores the default genesis config json file at given path (in addition to
157+
/// chain-spec).
158+
default_config_path: Option<PathBuf>,
159+
}
160+
161+
/// Edits provided input chain spec. Input can be converted into raw storage chain-spec. The code
162+
/// can be updated with the runtime provided in the command line.
163+
#[derive(Parser, Debug, Clone)]
164+
struct EditCmd {
165+
#[arg(long, short)]
166+
/// Chain spec to be edited
167+
input_chain_spec: PathBuf,
168+
/// The path to new runtime wasm blob to be stored into chain-spec
169+
#[arg(long, short = 'r')]
170+
runtime_wasm_path: Option<PathBuf>,
171+
/// Convert genesis spec to raw format
172+
#[arg(long, short = 's')]
173+
convert_to_raw: bool,
174+
}
175+
176+
/// Verifies provided input chain spec. If the runtime is provided verification is performed against
177+
/// new runtime.
178+
#[derive(Parser, Debug, Clone)]
179+
struct VerifyCmd {
180+
#[arg(long, short)]
181+
/// Chain spec to be edited
182+
input_chain_spec: PathBuf,
183+
/// The path to new runtime wasm blob to be stored into chain-spec
184+
#[arg(long, short = 'r')]
185+
runtime_wasm_path: Option<PathBuf>,
96186
}
97187

98188
fn generate_chain_spec(
@@ -207,19 +297,80 @@ fn print_seeds(
207297
println!("//{}", sudo_seed);
208298
}
209299

300+
fn generate_chain_spec_for_runtime(cmd: &RuntimeCmd) -> Result<String, String> {
301+
let code = fs::read(cmd.runtime_wasm_path.as_path()).expect("wasm blob file is readable");
302+
303+
let builder = chain_spec::ChainSpec::builder()
304+
.with_name(&cmd.chain_name[..])
305+
.with_id(&cmd.chain_id[..])
306+
.with_chain_type(sc_chain_spec::ChainType::Live)
307+
.with_extensions(Default::default())
308+
.with_code(&code[..]);
309+
310+
let builder = match cmd.action {
311+
GenesisBuildAction::Patch(PatchCmd { ref patch_path }) => {
312+
let patch = fs::read(patch_path.as_path())
313+
.map_err(|e| format!("patch file {patch_path:?} shall be readable: {e}"))?;
314+
builder.with_genesis_patch(serde_json::from_slice::<Value>(&patch[..]).map_err(
315+
|e| format!("patch file {patch_path:?} shall contain a valid json: {e}"),
316+
)?)
317+
},
318+
GenesisBuildAction::Full(FullCmd { ref config_path }) => {
319+
let config = fs::read(config_path.as_path())
320+
.map_err(|e| format!("config file {config_path:?} shall be readable: {e}"))?;
321+
builder.with_no_genesis_defaults(serde_json::from_slice::<Value>(&config[..]).map_err(
322+
|e| format!("config file {config_path:?} shall contain a valid json: {e}"),
323+
)?)
324+
},
325+
GenesisBuildAction::Default(DefaultCmd { ref default_config_path }) => {
326+
let caller = GenesisConfigBuilderRuntimeCaller::new(&code[..]);
327+
let default_config = caller
328+
.get_default_config()
329+
.expect("getting default config from runtime should work");
330+
default_config_path.clone().map(|path| {
331+
fs::write(path.as_path(), serde_json::to_string_pretty(&default_config).unwrap())
332+
.map_err(|err| err.to_string())
333+
});
334+
builder.with_no_genesis_defaults(default_config)
335+
},
336+
};
337+
338+
let chain_spec = builder.build();
339+
340+
match (cmd.verify, cmd.raw_storage) {
341+
(_, true) => chain_spec.as_json(true),
342+
(true, false) => {
343+
chain_spec.as_json(true)?;
344+
println!("Genesis config verification: OK");
345+
chain_spec.as_json(false)
346+
},
347+
(false, false) => chain_spec.as_json(false),
348+
}
349+
}
350+
210351
fn main() -> Result<(), String> {
352+
sp_tracing::try_init_simple();
353+
354+
let builder = ChainSpecBuilder::parse();
211355
#[cfg(build_type = "debug")]
212-
println!(
213-
"The chain spec builder builds a chain specification that includes a Substrate runtime \
356+
if matches!(builder.command, ChainSpecBuilderCmd::Generate(_) | ChainSpecBuilderCmd::New(_)) {
357+
println!(
358+
"The chain spec builder builds a chain specification that includes a Substrate runtime \
214359
compiled as WASM. To ensure proper functioning of the included runtime compile (or run) \
215360
the chain spec builder binary in `--release` mode.\n",
216-
);
361+
);
362+
}
217363

218-
let builder = ChainSpecBuilder::parse();
219-
let chain_spec_path = builder.chain_spec_path().to_path_buf();
364+
let chain_spec_path = builder.chain_spec_path.to_path_buf();
365+
let mut write_chain_spec = true;
220366

221-
let (authority_seeds, nominator_accounts, endowed_accounts, sudo_account) = match builder {
222-
ChainSpecBuilder::Generate { authorities, nominators, endowed, keystore_path, .. } => {
367+
let chain_spec_json = match builder.command {
368+
ChainSpecBuilderCmd::Generate(GenerateCmd {
369+
authorities,
370+
nominators,
371+
endowed,
372+
keystore_path,
373+
}) => {
223374
let authorities = authorities.max(1);
224375
let rand_str = || -> String {
225376
OsRng.sample_iter(&Alphanumeric).take(32).map(char::from).collect()
@@ -253,19 +404,45 @@ fn main() -> Result<(), String> {
253404
let sudo_account =
254405
chain_spec::get_account_id_from_seed::<sr25519::Public>(&sudo_seed).to_ss58check();
255406

256-
(authority_seeds, nominator_accounts, endowed_accounts, sudo_account)
407+
generate_chain_spec(authority_seeds, nominator_accounts, endowed_accounts, sudo_account)
257408
},
258-
ChainSpecBuilder::New {
409+
ChainSpecBuilderCmd::New(NewCmd {
259410
authority_seeds,
260411
nominator_accounts,
261412
endowed_accounts,
262413
sudo_account,
263-
..
264-
} => (authority_seeds, nominator_accounts, endowed_accounts, sudo_account),
265-
};
266-
267-
let json =
268-
generate_chain_spec(authority_seeds, nominator_accounts, endowed_accounts, sudo_account)?;
414+
}) =>
415+
generate_chain_spec(authority_seeds, nominator_accounts, endowed_accounts, sudo_account),
416+
ChainSpecBuilderCmd::Runtime(cmd) => generate_chain_spec_for_runtime(&cmd),
417+
ChainSpecBuilderCmd::Edit(EditCmd {
418+
ref input_chain_spec,
419+
ref runtime_wasm_path,
420+
convert_to_raw,
421+
}) => {
422+
let mut chain_spec = GenericChainSpec::<()>::from_json_file(input_chain_spec.clone())?;
423+
runtime_wasm_path.clone().and_then(|path| {
424+
chain_spec
425+
.set_code(&fs::read(path.as_path()).expect("wasm blob file is readable")[..])
426+
.into()
427+
});
428+
429+
chain_spec.as_json(convert_to_raw)
430+
},
431+
ChainSpecBuilderCmd::Verify(VerifyCmd { ref input_chain_spec, ref runtime_wasm_path }) => {
432+
write_chain_spec = false;
433+
let mut chain_spec = GenericChainSpec::<()>::from_json_file(input_chain_spec.clone())?;
434+
runtime_wasm_path.clone().and_then(|path| {
435+
chain_spec
436+
.set_code(&fs::read(path.as_path()).expect("wasm blob file is readable")[..])
437+
.into()
438+
});
439+
chain_spec.as_json(true)
440+
},
441+
}?;
269442

270-
fs::write(chain_spec_path, json).map_err(|err| err.to_string())
443+
if write_chain_spec {
444+
fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())
445+
} else {
446+
Ok(())
447+
}
271448
}

0 commit comments

Comments
 (0)