diff --git a/Cargo.lock b/Cargo.lock index 00eea545c712..fe0de516f105 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2511,6 +2511,32 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chain-spec-guide-runtime" +version = "0.0.0" +dependencies = [ + "docify", + "pallet-balances", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "polkadot-sdk-frame", + "sc-chain-spec", + "scale-info", + "serde", + "serde_json", + "sp-application-crypto", + "sp-core", + "sp-genesis-builder", + "sp-keyring", + "sp-runtime", + "sp-std 14.0.0", + "staging-chain-spec-builder", + "substrate-wasm-builder", +] + [[package]] name = "chrono" version = "0.4.31" @@ -14245,6 +14271,7 @@ dependencies = [ name = "polkadot-sdk-docs" version = "0.0.1" dependencies = [ + "chain-spec-guide-runtime", "cumulus-client-service", "cumulus-pallet-aura-ext", "cumulus-pallet-parachain-system", @@ -14281,6 +14308,7 @@ dependencies = [ "parity-scale-codec", "polkadot-sdk", "polkadot-sdk-frame", + "sc-chain-spec", "sc-cli", "sc-client-db", "sc-consensus-aura", @@ -14299,6 +14327,7 @@ dependencies = [ "sp-api", "sp-arithmetic", "sp-core", + "sp-genesis-builder", "sp-io", "sp-keyring", "sp-offchain", @@ -20546,7 +20575,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "staging-chain-spec-builder" -version = "1.6.0" +version = "1.6.1" dependencies = [ "clap 4.5.3", "log", diff --git a/Cargo.toml b/Cargo.toml index d6099e420f91..2b2a1cdc17d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,6 +144,7 @@ members = [ "cumulus/test/service", "cumulus/xcm/xcm-emulator", "docs/sdk", + "docs/sdk/src/reference_docs/chain_spec_runtime", "polkadot", "polkadot/cli", "polkadot/core-primitives", diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index a8c873be556c..a0953896356d 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -55,6 +55,7 @@ sc-consensus-manual-seal = { path = "../../substrate/client/consensus/manual-sea sc-consensus-pow = { path = "../../substrate/client/consensus/pow" } sc-executor = { path = "../../substrate/client/executor" } sc-service = { path = "../../substrate/client/service" } +sc-chain-spec = { path = "../../substrate/client/chain-spec" } substrate-wasm-builder = { path = "../../substrate/utils/wasm-builder" } @@ -90,6 +91,7 @@ sp-core = { path = "../../substrate/primitives/core" } sp-keyring = { path = "../../substrate/primitives/keyring" } sp-runtime = { path = "../../substrate/primitives/runtime" } sp-arithmetic = { path = "../../substrate/primitives/arithmetic" } +sp-genesis-builder = { path = "../../substrate/primitives/genesis-builder" } # Misc pallet dependencies pallet-referenda = { path = "../../substrate/frame/referenda" } @@ -102,3 +104,6 @@ sp-version = { path = "../../substrate/primitives/version" } # XCM xcm = { package = "staging-xcm", path = "../../polkadot/xcm" } xcm-docs = { path = "../../polkadot/xcm/docs" } + +# runtime guides +chain-spec-guide-runtime = { path = "./src/reference_docs/chain_spec_runtime" } diff --git a/docs/sdk/src/reference_docs/chain_spec_genesis.rs b/docs/sdk/src/reference_docs/chain_spec_genesis.rs index 2ac51a91f2de..b3377a330b3e 100644 --- a/docs/sdk/src/reference_docs/chain_spec_genesis.rs +++ b/docs/sdk/src/reference_docs/chain_spec_genesis.rs @@ -1,4 +1,185 @@ -//! Chain spec and genesis build. +//! # What is chain-spec. //! -//! What is chain-spec. -//! What is genesis state and how to build it. +//! 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: +//! - initial state of the runtime, +//! - network / logical properties of the chain, the most important property being the list of +//! bootnodes. +//! +//! This document describes how initial state is handled in pallets and runtime, and how to interact +//! with the runtime in order to build genesis state. +//! +//! For more information on chain specification and its properties, refer to +//! [`sc_chain_spec#from-initial-state-to-raw-genesis`]. +//! +//! The initial genesis state can be provided in the following formats: +//! - full +//! - patch +//! - raw +//! +//! Each of the formats is explained in [_chain-spec-format_][`sc_chain_spec#chain-spec-formats`]. +//! +//! +//! # `GenesisConfig` for `pallet` +//! +//! Every frame pallet may have its initial state which is defined by the `GenesisConfig` internal +//! struct. It is a regular rust struct, annotated with [`pallet::genesis_config`] attribute. +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", pallet_bar_GenesisConfig)] +//! +//! The struct shall be defined within the pallet `mod`, as in the following code: +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", pallet_bar)] +//! +//! The initial state conveyed in the `GenesisConfig` struct is transformed into state storage +//! items by means of the [`BuildGenesisConfig`] trait which shall be implemented for the pallet's +//! `GenesisConfig` struct. The [`pallet::genesis_build`] attribute shall be attached to the `impl` +//! block: +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", pallet_bar_build)] +//! +//! `GenesisConfig` may also contain more complicated types, including nested structs or enums, as +//! in the example for `pallet_foo`: +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", pallet_foo_GenesisConfig)] +//! +//! Note that [`serde`] attributes can be used to control how the data +//! structures are being stored into JSON. In the following example [`sp_core::bytes`] function is +//! used to serialize the `values` field. +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", SomeFooData2)] +//! +//! Please note that fields of `GenesisConfig` may not be directly mapped to storage items. In the +//! following example the initial struct fields are used to compute (sum) the value that will be +//! stored in state as `ProcessedEnumValue`: +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", pallet_foo_build)] +//! +//! # `GenesisConfig` for `runtimes` +//! +//! The runtime genesis config struct consists of configs for every pallet. For [_demonstration +//! runtime_][`chain_spec_guide_runtime`] used in this guide, it consists of `SystemConfig`, +//! `BarConfig` and `FooConfig`. This structure was automatically generated by macro and it can be +//! sneak peaked here: [`RuntimeGenesisConfig`]. For further reading on generated runtime +//! types refer to [`frame_runtime_types`]. +//! +//! The macro automatically adds an attribute that renames all the fields to [`camelCase`]. It is a +//! good practice to add it to nested structures too, to have the naming of the JSON keys consistent +//! across the chain-spec file. +//! +//! ## `Default` for `GenesisConfig` +//! +//! `GenesisConfig` of all pallets must implement `Defualt` trait. Those are aggregated into +//! runtime's `RuntimeGenesisConfig`'s `Default`. +//! +//! Default value of `RuntimeGenesisConfig` can be queried by [`GenesisBuilder::get_preset`] +//! function provided by runtime with `id:None`. +//! +//! A default value for RuntimeGenesisConfig usually is not operational. This is because for some +//! pallets it is not possible to define good defaults (e.g. an initial set of authorities). +//! +//! A default value is a base upon which a patch for `GenesisConfig` is applied. A good description +//! of how it exactly works is provided in [`get_storage_for_patch`] (and also in +//! [`GenesisBuilder::get_preset`]). A patch can be provided as a external file (manually created) +//! or as builtin runtime preset. More info on presets are in the material to follow. +//! +//! ## Implementing `GenesisBuilder` for runtime +//! +//! The runtime exposes a dedicated runtime API for interacting with its genesis config: +//! [`sp_genesis_builder::GenesisBuilder`]. The implementation shall be provided within +//! [`sp_api::impl_runtime_apis`] macro, typically making use of some helpers provided: +//! [`build_state`], [`get_preset`]. +//! A typical implementation of [`sp_genesis_builder::GenesisBuilder`] looks as follows: +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/runtime.rs", runtime_impl)] +//! +//! Please note that two functions are customized: `preset_names` and `get_preset`. The first one +//! just provides a `Vec` of the names of supported presets, while the latter one delegates the call +//! to a function that maps the name to an actual preset: +//! [`chain_spec_guide_runtime::presets::get_builtin_preset`] +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", get_builtin_preset)] +//! +//! ## Genesis state presets for runtime +//! +//! The runtime may provide many flavors of initial genesis state. This may be useful for predefined +//! testing networks, local development, or CI integration tests. Predefined genesis state may +//! contain a list of pre-funded accounts, predefined authorities for consensus, sudo key and many +//! others useful for testing. +//! +//! Internally presets can be provided in a number of ways: +//! - JSON in string form: +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", preset_1)] +//! - JSON using runtime types to serialize values: +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", preset_2)] +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", preset_3)] +//! It is worth noting that preset does not have to be the full `RuntimeGenesisConfig`, in that +//! sense that it does not have to contain all the keys of the struct. The preset is actually a JSON +//! patch that will be merged with default value of `RuntimeGenesisConfig`. This approach should +//! simplify maintanance of builtin presets. Following example illustrates runtime genesis config +//! patch: +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", preset_4)] +//! +//! ## Note on the importance of testing presets +//! +//! It is recommended to always test presets by adding the tests that convert the preset into the +//! raw storage. Converting to raw storage involves the deserialization of the provided JSON blob, +//! what enforces the verification of the preset. The following code shows one of the approaches +//! that can be taken for testing: +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", check_presets)] +//! +//! ## Note on tne importance of using `deny_unknown_fields` attribute +//! +//! It is worth noting that it is easy to make a hard to spot mistake as in the following example +//! ([`FooStruct`] does not contain `fieldC`): +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", preset_invalid)] +//! Even though `preset_invalid` contains a key that does not exist, the deserialization of the JSON +//! blob does not fail. The mispelling is silently ignored due to lack of [`deny_unknown_fields`] +//! attribute on the [`FooStruct`] struct, which is internally used in `GenesisConfig`. +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", invalid_preset_works)] +//! +//! ## Runtime `GenesisConfig` raw format +//! +//! A raw format of genesis config cotains just state's keys and values as they are stored in the +//! storage. This format is used to directly initialize the genesis storage. This format is useful +//! for long-term running chains, where the `GenesisConfig` stucture for pallets may be evolving +//! over the time. The JSON representation created at some point in time may no longer be +//! deserializable in future, making a chain specification useless. The raw format is recommended +//! for the production chains. +//! +//! For detailed description on how raw format is built please refer to +//! [_chain-spec-raw-genesis_][`sc_chain_spec#from-initial-state-to-raw-genesis`]. A plain and +//! corresponding raw examples of chain-spec are given in +//! [_chain-spec-examples_][`sc_chain_spec#json-chain-specification-example`]. +//! The [`chain_spec_builder`] util supports building the raw storage. +//! +//! # Interacting with the tool +//! +//! The [`chain_spec_builder`] util allows to interact with runtime in order to list or display +//! presets and build the chain specification file. It is possible to use the tool with the +//! [_demonstration runtime_][`chain_spec_guide_runtime`]. To build required packages just run the +//! following command: +//! ```ignore +//! cargo build -p staging-chain-spec-builder -p chain-spec-guide-runtime --release +//! ``` +//! The `chain-spec-builder` util can also be installed with `cargo install`: +//! ```ignore +//! cargo install staging-chain-spec-builder +//! cargo build -p chain-spec-guide-runtime --release +//! ``` +//! Here are some examples in the form of rust tests: +//! ## Listing available presets names: +#![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 +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", generate_chain_spec)] +//! +//! [`RuntimeGenesisConfig`]: +//! chain_spec_guide_runtime::runtime::RuntimeGenesisConfig +//! [`FooStruct`]: +//! chain_spec_guide_runtime::pallets::FooStruct +//! [`impl_runtime_apis`]: frame::runtime::prelude::impl_runtime_apis +//! [`build_state`]: frame_support::genesis_builder_helper::build_state +//! [`get_preset`]: frame_support::genesis_builder_helper::get_preset +//! [`pallet::genesis_build`]: frame_support::pallet_macros::genesis_build +//! [`pallet::genesis_config`]: frame_support::pallet_macros::genesis_config +//! [`BuildGenesisConfig`]: frame_support::traits::BuildGenesisConfig +//! [`chain_spec_builder`]: ../../../staging_chain_spec_builder/index.html +//! [`serde`]: https://serde.rs/field-attrs.html +//! [`get_storage_for_patch`]: sc_chain_spec::GenesisConfigBuilderRuntimeCaller::get_storage_for_patch +//! [`GenesisBuilder::get_preset`]: sp_genesis_builder::GenesisBuilder::get_preset +//! [`deny_unknown_fields`]: https://serde.rs/container-attrs.html#deny_unknown_fields +//! [`camelCase`]: https://serde.rs/container-attrs.html#rename_all diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml b/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml new file mode 100644 index 000000000000..c6dd3af9d90b --- /dev/null +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "chain-spec-guide-runtime" +description = "A minimal runtime for chain spec guide" +version = "0.0.0" +license = "MIT-0" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true +publish = false + +[dependencies] +docify = "0.2.8" +parity-scale-codec = { version = "3.6.12", default-features = false } +scale-info = { version = "2.6.0", default-features = false } +serde = { workspace = true, default-features = false } +serde_json = { workspace = true } + +# this is a frame-based runtime, thus importing `frame` with runtime feature enabled. +frame = { package = "polkadot-sdk-frame", path = "../../../../../substrate/frame", default-features = false, features = [ + "experimental", + "runtime", +] } + +# pallets that we want to use +pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } +pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false } +pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } +pallet-transaction-payment = { path = "../../../../../substrate/frame/transaction-payment", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { path = "../../../../../substrate/frame/transaction-payment/rpc/runtime-api", default-features = false } + +# genesis builder that allows us to interact with runtime genesis config +sp-genesis-builder = { path = "../../../../../substrate/primitives/genesis-builder", default-features = false } +sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false, features = ["serde"] } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +sp-keyring = { path = "../../../../../substrate/primitives/keyring", default-features = false } +sp-application-crypto = { path = "../../../../../substrate/primitives/application-crypto", default-features = false, features = ["serde"] } + +[build-dependencies] +substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } + +[dev-dependencies] +chain-spec-builder = { package = "staging-chain-spec-builder", path = "../../../../../substrate/bin/utils/chain-spec-builder" } +sc-chain-spec = { path = "../../../../../substrate/client/chain-spec" } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "scale-info/std", + + "frame/std", + + "pallet-balances/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + + "sp-application-crypto/std", + "sp-core/std", + "sp-genesis-builder/std", + "sp-keyring/std", + "sp-runtime/std", + "sp-std/std", + + "serde/std", + "serde_json/std", + "substrate-wasm-builder", +] diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/build.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/build.rs new file mode 100644 index 000000000000..e6f92757e225 --- /dev/null +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/build.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +fn main() { + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::build_using_defaults(); + } +} diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/lib.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/lib.rs new file mode 100644 index 000000000000..4606104fb968 --- /dev/null +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/lib.rs @@ -0,0 +1,27 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +//! A minimal runtime that shows runtime genesis state. + +pub mod pallets; +pub mod presets; +pub mod runtime; + +#[cfg(feature = "std")] +pub use runtime::{WASM_BINARY, WASM_BINARY_BLOATY}; diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/pallets.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/pallets.rs new file mode 100644 index 000000000000..be4455aa2197 --- /dev/null +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/pallets.rs @@ -0,0 +1,137 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Pallets for the chain-spec demo runtime. + +use frame::prelude::*; + +#[docify::export] +#[frame::pallet(dev_mode)] +pub mod pallet_bar { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub(super) type InitialAccount = StorageValue; + + /// Simple `GenesisConfig`. + #[pallet::genesis_config] + #[derive(DefaultNoBound)] + #[docify::export(pallet_bar_GenesisConfig)] + pub struct GenesisConfig { + pub initial_account: Option, + } + + #[pallet::genesis_build] + #[docify::export(pallet_bar_build)] + impl BuildGenesisConfig for GenesisConfig { + /// The storage building function that presents a direct mapping of the initial config + /// values to the storage items. + fn build(&self) { + InitialAccount::::set(self.initial_account.clone()); + } + } +} + +/// The sample structure used in `GenesisConfig`. +/// +/// This structure does not deny unknown fields. This may lead to some problems. +#[derive(Default, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FooStruct { + pub field_a: u8, + pub field_b: u8, +} + +/// The sample structure used in `GenesisConfig`. +/// +/// This structure does not deny unknown fields. This may lead to some problems. +#[derive(Default, serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct SomeFooData1 { + pub a: u8, + pub b: u8, +} + +/// Another sample structure used in `GenesisConfig`. +/// +/// The user defined serialization is used. +#[derive(Default, serde::Serialize, serde::Deserialize)] +#[docify::export] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct SomeFooData2 { + #[serde(default, with = "sp_core::bytes")] + pub values: Vec, +} + +/// Sample enum used in `GenesisConfig`. +#[derive(Default, serde::Serialize, serde::Deserialize)] +pub enum FooEnum { + #[default] + Data0, + Data1(SomeFooData1), + Data2(SomeFooData2), +} + +#[docify::export] +#[frame::pallet(dev_mode)] +pub mod pallet_foo { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type ProcessedEnumValue = StorageValue; + #[pallet::storage] + pub type SomeInteger = StorageValue; + + /// The more sophisticated structure for conveying initial state. + #[docify::export(pallet_foo_GenesisConfig)] + #[pallet::genesis_config] + #[derive(DefaultNoBound)] + pub struct GenesisConfig { + pub some_integer: u32, + pub some_enum: FooEnum, + pub some_struct: FooStruct, + #[serde(skip)] + _phantom: PhantomData, + } + + #[pallet::genesis_build] + #[docify::export(pallet_foo_build)] + impl BuildGenesisConfig for GenesisConfig { + /// The build method that indirectly maps an initial config values into the storage items. + fn build(&self) { + let processed_value: u64 = match &self.some_enum { + FooEnum::Data0 => 0, + FooEnum::Data1(v) => (v.a + v.b).into(), + FooEnum::Data2(v) => v.values.iter().map(|v| *v as u64).sum(), + }; + ProcessedEnumValue::::set(Some(processed_value)); + SomeInteger::::set(Some(self.some_integer)); + } + } +} diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/presets.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/presets.rs new file mode 100644 index 000000000000..c51947f6cc7c --- /dev/null +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/presets.rs @@ -0,0 +1,166 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Presets for the chain-spec demo runtime. + +use crate::pallets::{FooEnum, SomeFooData1, SomeFooData2}; +use serde_json::{json, to_string, Value}; +use sp_application_crypto::Ss58Codec; +use sp_keyring::AccountKeyring; +use sp_std::vec; + +/// A demo preset with strings only. +pub const PRESET_1: &str = "preset_1"; +/// A demo preset with real types. +pub const PRESET_2: &str = "preset_2"; +/// Another demo preset with real types. +pub const PRESET_3: &str = "preset_3"; +/// A single value patch preset. +pub const PRESET_4: &str = "preset_4"; +/// A single value patch preset. +pub const PRESET_INVALID: &str = "preset_invalid"; + +#[docify::export] +/// Function provides a preset demonstrating how use string representation of preset's internal +/// values. +fn preset_1() -> Value { + json!({ + "bar": { + "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", + }, + "foo": { + "someEnum": { + "Data2": { + "values": "0x0c0f" + } + }, + "someStruct" : { + "fieldA": 10, + "fieldB": 20 + }, + "someInteger": 100 + }, + }) +} + +#[docify::export] +/// Function provides a preset demonstrating how use the actual types to create a preset. +fn preset_2() -> Value { + json!({ + "bar": { + "initialAccount": AccountKeyring::Ferdie.public().to_ss58check(), + }, + "foo": { + "someEnum": FooEnum::Data2(SomeFooData2 { values: vec![12,16] }), + "someInteger": 200 + }, + }) +} + +#[docify::export] +/// Function provides a preset demonstrating how use the actual types to create a preset. +fn preset_3() -> Value { + json!({ + "bar": { + "initialAccount": AccountKeyring::Alice.public().to_ss58check(), + }, + "foo": { + "someEnum": FooEnum::Data1( + SomeFooData1 { + a: 12, + b: 16 + } + ), + "someInteger": 300 + }, + }) +} + +#[docify::export] +/// Function provides a minimal preset demonstrating how to patch single key in +/// `RuntimeGenesisConfig`. +fn preset_4() -> Value { + json!({ + "foo": { + "someEnum": { + "Data2": { + "values": "0x0c0f" + } + }, + }, + }) +} + +#[docify::export] +/// Function provides an invalid preset demonstrating how important is use of +/// [`deny_unknown_fields`] in data structures used in `GenesisConfig`. +fn preset_invalid() -> Value { + json!({ + "foo": { + "someStruct": { + "fieldC": 5 + }, + }, + }) +} + +/// Provides a JSON representation of preset identified by given `id`. +/// +/// If no preset with given `id` exits `None` is returned. +#[docify::export] +pub fn get_builtin_preset(id: &sp_genesis_builder::PresetId) -> Option> { + let preset = match id.try_into() { + Ok(PRESET_1) => preset_1(), + Ok(PRESET_2) => preset_2(), + Ok(PRESET_3) => preset_3(), + Ok(PRESET_4) => preset_4(), + Ok(PRESET_INVALID) => preset_invalid(), + _ => return None, + }; + + Some( + to_string(&preset) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) +} + +#[test] +#[docify::export] +fn check_presets() { + let builder = sc_chain_spec::GenesisConfigBuilderRuntimeCaller::<()>::new( + crate::WASM_BINARY.expect("wasm binary shall exists"), + ); + assert!(builder.get_storage_for_named_preset(Some(&PRESET_1.to_string())).is_ok()); + assert!(builder.get_storage_for_named_preset(Some(&PRESET_2.to_string())).is_ok()); + assert!(builder.get_storage_for_named_preset(Some(&PRESET_3.to_string())).is_ok()); + assert!(builder.get_storage_for_named_preset(Some(&PRESET_4.to_string())).is_ok()); +} + +#[test] +#[docify::export] +fn invalid_preset_works() { + let builder = sc_chain_spec::GenesisConfigBuilderRuntimeCaller::<()>::new( + crate::WASM_BINARY.expect("wasm binary shall exists"), + ); + // Even though a preset contains invalid_key, conversion to raw storage does not fail. This is + // because the [`FooStruct`] structure is not annotated with `deny_unknown_fields` [`serde`] + // attribute. + // This may lead to hard to debug problems, that's why using ['deny_unknown_fields'] is + // recommended. + assert!(builder.get_storage_for_named_preset(Some(&PRESET_INVALID.to_string())).is_ok()); +} diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs new file mode 100644 index 000000000000..6d9bc1260b11 --- /dev/null +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs @@ -0,0 +1,122 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A minimal runtime that shows runtime genesis state. + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +use crate::{ + pallets::{pallet_bar, pallet_foo}, + presets::*, +}; +use frame::{ + deps::frame_support::{ + genesis_builder_helper::{build_state, get_preset}, + runtime, + }, + prelude::*, + runtime::{ + apis::{self, impl_runtime_apis, ExtrinsicInclusionMode}, + prelude::*, + }, +}; +use sp_genesis_builder::PresetId; + +/// The runtime version. +#[runtime_version] +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("minimal-template-runtime"), + impl_name: create_runtime_str!("minimal-template-runtime"), + authoring_version: 1, + spec_version: 0, + impl_version: 1, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + state_version: 1, +}; + +/// The signed extensions that are added to the runtime. +type SignedExtra = (); + +// Composes the runtime by adding all the used pallets and deriving necessary types. +#[runtime] +mod runtime { + /// The main runtime type. + #[runtime::runtime] + #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeError, RuntimeOrigin, RuntimeTask)] + pub struct Runtime; + + /// Mandatory system pallet that should always be included in a FRAME runtime. + #[runtime::pallet_index(0)] + pub type System = frame_system; + + /// Sample pallet 1 + #[runtime::pallet_index(1)] + pub type Bar = pallet_bar; + + /// Sample pallet 2 + #[runtime::pallet_index(2)] + pub type Foo = pallet_foo; +} + +parameter_types! { + pub const Version: RuntimeVersion = VERSION; +} + +/// Implements the types required for the system pallet. +#[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; + type Version = Version; +} + +impl pallet_bar::Config for Runtime {} +impl pallet_foo::Config for Runtime {} + +type Block = frame::runtime::types_common::BlockOf; +type Header = HeaderFor; + +#[docify::export(runtime_impl)] +impl_runtime_apis! { + impl sp_genesis_builder::GenesisBuilder for Runtime { + fn build_state(config: Vec) -> sp_genesis_builder::Result { + build_state::(config) + } + + fn get_preset(id: &Option) -> Option> { + get_preset::(id, get_builtin_preset) + } + + fn preset_names() -> Vec { + vec![ + PresetId::from(PRESET_1), + PresetId::from(PRESET_2), + PresetId::from(PRESET_3), + PresetId::from(PRESET_4), + PresetId::from(PRESET_INVALID) + ] + } + } + + impl apis::Core for Runtime { + fn version() -> RuntimeVersion { VERSION } + fn execute_block(_: Block) { } + fn initialize_block(_: &Header) -> ExtrinsicInclusionMode { ExtrinsicInclusionMode::default() } + } +} diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs new file mode 100644 index 000000000000..d08f547bba61 --- /dev/null +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs @@ -0,0 +1,129 @@ +use serde_json::{json, Value}; +use std::{process::Command, str}; + +const WASM_FILE_PATH: &str = + "../../../../../target/release/wbuild/chain-spec-guide-runtime/chain_spec_guide_runtime.wasm"; + +const CHAIN_SPEC_BUILDER_PATH: &str = "../../../../../target/release/chain-spec-builder"; + +fn get_chain_spec_builder_path() -> &'static str { + // dev-dependencies do not build binary. So let's do the naive work-around here: + let _ = std::process::Command::new("cargo") + .arg("build") + .arg("--release") + .arg("-p") + .arg("staging-chain-spec-builder") + .arg("--bin") + .arg("chain-spec-builder") + .status() + .expect("Failed to execute command"); + CHAIN_SPEC_BUILDER_PATH +} + +#[test] +#[docify::export] +fn list_presets() { + let output = Command::new(get_chain_spec_builder_path()) + .arg("list-presets") + .arg("-r") + .arg(WASM_FILE_PATH) + .output() + .expect("Failed to execute command"); + + let output: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); + + let expected_output = json!({ + "presets":[ + "preset_1", + "preset_2", + "preset_3", + "preset_4", + "preset_invalid" + ] + }); + assert_eq!(output, expected_output, "Output did not match expected"); +} + +#[test] +#[docify::export] +fn get_preset() { + let output = Command::new(get_chain_spec_builder_path()) + .arg("display-preset") + .arg("-r") + .arg(WASM_FILE_PATH) + .arg("-p") + .arg("preset_2") + .output() + .expect("Failed to execute command"); + + let output: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); + + //note: copy of chain_spec_guide_runtime::preset_1 + let expected_output = json!({ + "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_chain_spec() { + let output = Command::new(get_chain_spec_builder_path()) + .arg("-c") + .arg("/dev/stdout") + .arg("create") + .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, + "properties": null, + "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"); +} diff --git a/docs/sdk/src/reference_docs/mod.rs b/docs/sdk/src/reference_docs/mod.rs index 6fa25bf36e1b..e50690b50212 100644 --- a/docs/sdk/src/reference_docs/mod.rs +++ b/docs/sdk/src/reference_docs/mod.rs @@ -76,7 +76,6 @@ pub mod frame_benchmarking_weight; pub mod frame_tokens; /// Learn about chain specification file and the genesis state of the blockchain. -// TODO: @michalkucharczyk https://github.com/paritytech/polkadot-sdk-docs/issues/51 pub mod chain_spec_genesis; /// Learn about all the memory limitations of the WASM runtime when it comes to memory usage. diff --git a/substrate/bin/utils/chain-spec-builder/Cargo.toml b/substrate/bin/utils/chain-spec-builder/Cargo.toml index de06bbb3fff6..88585649acfe 100644 --- a/substrate/bin/utils/chain-spec-builder/Cargo.toml +++ b/substrate/bin/utils/chain-spec-builder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "staging-chain-spec-builder" -version = "1.6.0" +version = "1.6.1" authors.workspace = true edition.workspace = true build = "build.rs" diff --git a/substrate/bin/utils/chain-spec-builder/bin/main.rs b/substrate/bin/utils/chain-spec-builder/bin/main.rs index 8d6425a46c77..18da3c30691b 100644 --- a/substrate/bin/utils/chain-spec-builder/bin/main.rs +++ b/substrate/bin/utils/chain-spec-builder/bin/main.rs @@ -99,7 +99,7 @@ fn inner_main() -> Result<(), String> { ) }) .collect(); - println!("{presets:#?}"); + 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()) diff --git a/substrate/client/chain-spec/src/lib.rs b/substrate/client/chain-spec/src/lib.rs index 653c3c618b77..b59ad68610ec 100644 --- a/substrate/client/chain-spec/src/lib.rs +++ b/substrate/client/chain-spec/src/lib.rs @@ -123,7 +123,10 @@ //! As the compiled WASM blob of the runtime code is stored in the chain's state, the initial //! runtime must also be provided within the chain specification. //! -//! In essence, the most important formats of genesis initial state are: +//! # `chain-spec` formats +//! +//! In essence, the most important formats of genesis initial state in chain specification files +//! are: //! //! //! @@ -135,14 +138,14 @@ //! //! //! //! //!
-//! runtime / full config +//! full config //! A JSON object that provides an explicit and comprehensive representation of the //! RuntimeGenesisConfig struct, which is generated by polkadot_sdk_frame::runtime::prelude::construct_runtime macro (example of generated struct). Must contain all the keys of +//! >example of generated struct). Must contain *all* the keys of //! the genesis config, no defaults will be used. //! //! This format explicitly provides the code of the runtime. @@ -154,7 +157,8 @@ //! A JSON object that offers a partial representation of the //! RuntimeGenesisConfig provided by the runtime. It contains a patch, which is //! essentially a list of key-value pairs to customize in the default runtime's -//! RuntimeGenesisConfig. +//! RuntimeGenesisConfig: `full = default + patch`. Please note that `default` +//! `RuntimeGenesisConfig` may not be functional. //! This format explicitly provides the code of the runtime. //!