Skip to content

Commit

Permalink
Add parachain related parameters to chain-spec-builder (#4889)
Browse files Browse the repository at this point in the history
When using with `polkadot-parachain`, you usually need to specify the
`relay_chain` and `para_id` fields in the chain spec.

With this PR it can be achieved by specifying newly added `--para-id`
and `--relay-chain` command line args, e.g:
```
chain-spec-builder create -r _runtime.wasm  --para-id 100 --relay-chain xxx default
```

This was implemented by simple _json_ blobs merging.

Additionally unit tests covering basic functionality were added.

Also adds a fix for not overwriting the chain spec with the default
config each time, swallowing not standard fields is also fixed.

Fixes: #4873

---------

Co-authored-by: Sebastian Kunert <[email protected]>
Co-authored-by: Michal Kucharczyk <[email protected]>
  • Loading branch information
3 people authored Sep 4, 2024
1 parent 4d2f793 commit 89b41c5
Show file tree
Hide file tree
Showing 25 changed files with 1,137 additions and 130 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions docs/sdk/src/reference_docs/chain_spec_genesis.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! # What is chain-spec.
//! # What is a chain specification
//!
//! A chain specification file defines the set of properties that are required to run the node as
//! part of the chain. The chain specification consists of two main parts:
Expand Down Expand Up @@ -165,8 +165,10 @@
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", list_presets)]
//! ## Displaying preset with given name
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", get_preset)]
//! ## Building chain-spec using given preset
//! ## Building a solo chain-spec (the default) using given preset
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", generate_chain_spec)]
//! ## Building a parachain chain-spec using given preset
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", generate_para_chain_spec)]
//!
//! [`RuntimeGenesisConfig`]:
//! chain_spec_guide_runtime::runtime::RuntimeGenesisConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,66 @@ fn generate_chain_spec() {
"bootNodes": [],
"telemetryEndpoints": null,
"protocolId": null,
"properties": null,
"properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" },
"codeSubstitutes": {},
"genesis": {
"runtimeGenesis": {
"code": "0x123",
"patch": {
"bar": {
"initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL"
},
"foo": {
"someEnum": {
"Data2": {
"values": "0x0c10"
}
},
"someInteger": 200
}
}
}
}
});
assert_eq!(output, expected_output, "Output did not match expected");
}

#[test]
#[docify::export]
fn generate_para_chain_spec() {
let output = Command::new(get_chain_spec_builder_path())
.arg("-c")
.arg("/dev/stdout")
.arg("create")
.arg("-c")
.arg("polkadot")
.arg("-p")
.arg("1000")
.arg("-r")
.arg(WASM_FILE_PATH)
.arg("named-preset")
.arg("preset_2")
.output()
.expect("Failed to execute command");

let mut output: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();

//remove code field for better readability
if let Some(code) = output["genesis"]["runtimeGenesis"].as_object_mut().unwrap().get_mut("code")
{
*code = Value::String("0x123".to_string());
}

let expected_output = json!({
"name": "Custom",
"id": "custom",
"chainType": "Live",
"bootNodes": [],
"telemetryEndpoints": null,
"protocolId": null,
"relay_chain": "polkadot",
"para_id": 1000,
"properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" },
"codeSubstitutes": {},
"genesis": {
"runtimeGenesis": {
Expand Down
15 changes: 15 additions & 0 deletions prdoc/pr_4889.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: Add CLI options for parachain chain specifications + fix bug for swallowing custom fields

doc:
- audience: Node Operator
description: |
Parachain ID and relay chain can be specified via the CLI arguments for when creating a chain spec.
A bug that also swallowed custom fields outside of the default config has also been fixed.


crates:
- name: staging-chain-spec-builder
bump: major
4 changes: 4 additions & 0 deletions substrate/bin/utils/chain-spec-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ clap = { features = ["derive"], workspace = true }
log = { workspace = true, default-features = true }
sc-chain-spec = { features = ["clap"], workspace = true, default-features = true }
serde_json = { workspace = true, default-features = true }
serde = { workspace = true, default-features = true }
sp-tracing = { workspace = true, default-features = true }

[dev-dependencies]
substrate-test-runtime = { workspace = true }
108 changes: 2 additions & 106 deletions substrate/bin/utils/chain-spec-builder/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,9 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use chain_spec_builder::{
generate_chain_spec_for_runtime, AddCodeSubstituteCmd, ChainSpecBuilder, ChainSpecBuilderCmd,
ConvertToRawCmd, DisplayPresetCmd, ListPresetsCmd, UpdateCodeCmd, VerifyCmd,
};
use chain_spec_builder::ChainSpecBuilder;
use clap::Parser;
use sc_chain_spec::{
set_code_substitute_in_json_chain_spec, update_code_in_json_chain_spec, GenericChainSpec,
GenesisConfigBuilderRuntimeCaller,
};
use staging_chain_spec_builder as chain_spec_builder;
use std::fs;

type ChainSpec = GenericChainSpec<(), ()>;

//avoid error message escaping
fn main() {
Expand All @@ -42,99 +32,5 @@ fn inner_main() -> Result<(), String> {
sp_tracing::try_init_simple();

let builder = ChainSpecBuilder::parse();
let chain_spec_path = builder.chain_spec_path.to_path_buf();

match builder.command {
ChainSpecBuilderCmd::Create(cmd) => {
let chain_spec_json = generate_chain_spec_for_runtime(&cmd)?;
fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?;
},
ChainSpecBuilderCmd::UpdateCode(UpdateCodeCmd {
ref input_chain_spec,
ref runtime_wasm_path,
}) => {
let chain_spec = ChainSpec::from_json_file(input_chain_spec.clone())?;

let mut chain_spec_json =
serde_json::from_str::<serde_json::Value>(&chain_spec.as_json(false)?)
.map_err(|e| format!("Conversion to json failed: {e}"))?;
update_code_in_json_chain_spec(
&mut chain_spec_json,
&fs::read(runtime_wasm_path.as_path())
.map_err(|e| format!("Wasm blob file could not be read: {e}"))?[..],
);

let chain_spec_json = serde_json::to_string_pretty(&chain_spec_json)
.map_err(|e| format!("to pretty failed: {e}"))?;
fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?;
},
ChainSpecBuilderCmd::AddCodeSubstitute(AddCodeSubstituteCmd {
ref input_chain_spec,
ref runtime_wasm_path,
block_height,
}) => {
let chain_spec = ChainSpec::from_json_file(input_chain_spec.clone())?;

let mut chain_spec_json =
serde_json::from_str::<serde_json::Value>(&chain_spec.as_json(false)?)
.map_err(|e| format!("Conversion to json failed: {e}"))?;

set_code_substitute_in_json_chain_spec(
&mut chain_spec_json,
&fs::read(runtime_wasm_path.as_path())
.map_err(|e| format!("Wasm blob file could not be read: {e}"))?[..],
block_height,
);
let chain_spec_json = serde_json::to_string_pretty(&chain_spec_json)
.map_err(|e| format!("to pretty failed: {e}"))?;
fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?;
},
ChainSpecBuilderCmd::ConvertToRaw(ConvertToRawCmd { ref input_chain_spec }) => {
let chain_spec = ChainSpec::from_json_file(input_chain_spec.clone())?;

let chain_spec_json =
serde_json::from_str::<serde_json::Value>(&chain_spec.as_json(true)?)
.map_err(|e| format!("Conversion to json failed: {e}"))?;

let chain_spec_json = serde_json::to_string_pretty(&chain_spec_json)
.map_err(|e| format!("Conversion to pretty failed: {e}"))?;
fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?;
},
ChainSpecBuilderCmd::Verify(VerifyCmd { ref input_chain_spec }) => {
let chain_spec = ChainSpec::from_json_file(input_chain_spec.clone())?;
let _ = serde_json::from_str::<serde_json::Value>(&chain_spec.as_json(true)?)
.map_err(|e| format!("Conversion to json failed: {e}"))?;
},
ChainSpecBuilderCmd::ListPresets(ListPresetsCmd { runtime_wasm_path }) => {
let code = fs::read(runtime_wasm_path.as_path())
.map_err(|e| format!("wasm blob shall be readable {e}"))?;
let caller: GenesisConfigBuilderRuntimeCaller =
GenesisConfigBuilderRuntimeCaller::new(&code[..]);
let presets = caller
.preset_names()
.map_err(|e| format!("getting default config from runtime should work: {e}"))?;
let presets: Vec<String> = presets
.into_iter()
.map(|preset| {
String::from(
TryInto::<&str>::try_into(&preset)
.unwrap_or_else(|_| "cannot display preset id")
.to_string(),
)
})
.collect();
println!("{}", serde_json::json!({"presets":presets}).to_string());
},
ChainSpecBuilderCmd::DisplayPreset(DisplayPresetCmd { runtime_wasm_path, preset_name }) => {
let code = fs::read(runtime_wasm_path.as_path())
.map_err(|e| format!("wasm blob shall be readable {e}"))?;
let caller: GenesisConfigBuilderRuntimeCaller =
GenesisConfigBuilderRuntimeCaller::new(&code[..]);
let preset = caller
.get_named_preset(preset_name.as_ref())
.map_err(|e| format!("getting default config from runtime should work: {e}"))?;
println!("{preset}");
},
};
Ok(())
builder.run()
}
Loading

0 comments on commit 89b41c5

Please sign in to comment.