Crate sc_chain_spec

source ·
Expand description

This crate includes structs and utilities for defining configuration files (known as chain specification) for both runtime and node.

§Intro: Chain Specification

The chain specification comprises parameters and settings that define the properties and an initial state of a chain. Users typically interact with the JSON representation of the chain spec. Internally, the chain spec is embodied by the GenericChainSpec struct, and specific properties can be accessed using the ChainSpec trait.

In summary, although not restricted to, the primary role of the chain spec is to provide a list of well-known boot nodes for the blockchain network and the means for initializing the genesis storage. This initialization is necessary for creating a genesis block upon which subsequent blocks are built. When the node is launched for the first time, it reads the chain spec, initializes the genesis block, and establishes connections with the boot nodes.

The JSON chain spec is divided into two main logical sections:

  • one section details general chain properties,
  • second explicitly or indirectly defines the genesis storage, which, in turn, determines the genesis hash of the chain,

The chain specification consists of the following fields:

Chain spec key Description
name The human readable name of the chain.
id The id of the chain.
chainType The chain type of this chain (refer to ChainType ).
bootNodes A list of multi addresses that belong to boot nodes of the chain.
telemetryEndpoints Optional list of multi address, verbosity of telemetry endpoints. The verbosity goes from 0 to 9. With 0 being the mode with the lowest verbosity.
protocolId Optional networking protocol id that identifies the chain.
forkId Optional fork id. Should most likely be left empty. Can be used to signal a fork on the network level when two chains have the same genesis hash.
properties Custom properties. Shall be provided in the form of key-value json object.
consensusEngine Deprecated field. Should be ignored.
codeSubstitutes Optional map of block_number to wasm_code. More details in material to follow.
genesis Defines the initial state of the runtime. More details in material to follow.

§genesis: Initial Runtime State

All nodes in the network must build subsequent blocks upon exactly the same genesis block.

The information configured in the genesis section of a chain specification is used to build the genesis storage, which is essential for creating the genesis block, since the block header includes the storage root hash.

The genesis key of the chain specification definition describes the initial state of the runtime. For example, it may contain:

  • an initial list of funded accounts,
  • the administrative account that controls the sudo key,
  • an initial authorities set for consensus, etc.

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.

§chain-spec formats

In essence, the most important formats of genesis initial state in chain specification files are:

Format Description
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 the genesis config, no defaults will be used.

This format explicitly provides the code of the runtime.

patch 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: `full = default + patch`. Please note that `default` `RuntimeGenesisConfig` may not be functional. This format explicitly provides the code of the runtime.
raw A JSON object with two fields: top and children_default. Each field is a map of key => value pairs representing entries in a genesis storage trie. The runtime code is one of such entries.

The main purpose of the RuntimeGenesisConfig patch is to:

  • minimize the maintenance effort when RuntimeGenesisConfig is changed in the future (e.g. new pallets added to the runtime or pallet’s genesis config changed),
  • increase the readability - it only contains the relevant fields,
  • allow to apply numerous changes in distinct domains (e.g. for zombienet).

For production or long-lasting blockchains, using the raw format in the chain specification is recommended. Only the raw format guarantees that storage root hash will remain unchanged when the RuntimeGenesisConfig format changes due to software upgrade.

JSON examples in the following section illustrate the raw patch and full genesis fields.

§From Initial State to Raw Genesis.

To generate a raw genesis storage from the JSON representation of the runtime genesis config, the node needs to interact with the runtime.

This interaction involves passing the runtime genesis config JSON blob to the runtime using the sp_genesis_builder::GenesisBuilder::build_state function. During this operation, the runtime converts the JSON representation of the genesis config into sp_io::storage items. It is a crucial step for computing the storage root hash, which is a key component in determining the genesis hash.

Consequently, the runtime must support the sp_genesis_builder::GenesisBuilder API to utilize either patch or full formats.

This entire process is encapsulated within the implementation of the BuildStorage trait, which can be accessed through the ChainSpec::as_storage_builder method. There is an intermediate internal helper that facilitates this interaction, GenesisConfigBuilderRuntimeCaller, which serves as a straightforward wrapper for sc_executor::WasmExecutor.

In case of raw genesis state the node does not interact with the runtime regarding the computation of initial state.

The plain and raw chain specification JSON blobs can be found in JSON examples section.

§Optional Code Mapping

Optional map of block_number to wasm_code.

The given wasm_code will be used to substitute the on-chain wasm code starting with the given block number until the spec_version on-chain changes. The given wasm_code should be as close as possible to the on-chain wasm code. A substitute should be used to fix a bug that cannot be fixed with a runtime upgrade, if for example the runtime is constantly panicking. Introducing new runtime APIs isn’t supported, because the node will read the runtime version from the on-chain wasm code.

Use this functionality only when there is no other way around it, and only patch the problematic bug; the rest should be done with an on-chain runtime upgrade.

§Building a Chain Specification

The ChainSpecBuilder should be used to create an instance of a chain specification. Its API allows configuration of all fields of the chain spec. To generate a JSON representation of the specification, use ChainSpec::as_json.

The sample code to generate a chain spec is as follows:

#[test]
fn build_chain_spec_with_patch_works() {
	let output = ChainSpec::<()>::builder(
		substrate_test_runtime::wasm_binary_unwrap().into(),
		Default::default(),
	)
	.with_name("TestName")
	.with_id("test_id")
	.with_chain_type(ChainType::Local)
	.with_genesis_config_patch(json!({
		"babe": {
			"epochConfig": {
				"c": [
					7,
					10
				],
				"allowed_slots": "PrimaryAndSecondaryPlainSlots"
			}
		},
		"substrateTest": {
			"authorities": [
				AccountKeyring::Ferdie.public().to_ss58check(),
				AccountKeyring::Alice.public().to_ss58check()
			],
		}
	}))
	.build();

	let raw_chain_spec = output.as_json(true);
	assert!(raw_chain_spec.is_ok());
}

§JSON chain specification example

The following are the plain and raw versions of the chain specification JSON files, resulting from executing of the above example:

{
  "name": "TestName",
  "id": "test_id",
  "chainType": "Local",
  "bootNodes": [],
  "telemetryEndpoints": null,
  "protocolId": null,
  "properties": null,
  "codeSubstitutes": {},
  "genesis": {
    "runtimeGenesis": {
      "patch": {
        "babe": {
          "epochConfig": {
            "allowed_slots": "PrimaryAndSecondaryPlainSlots",
            "c": [
              7,
              10
            ]
          }
        },
        "substrateTest": {
          "authorities": [
            "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
            "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
          ]
        }
      },
      "code": "0x0"
    }
  }
}
{
  "name": "TestName",
  "id": "test_id",
  "chainType": "Local",
  "bootNodes": [],
  "telemetryEndpoints": null,
  "protocolId": null,
  "properties": null,
  "codeSubstitutes": {},
  "genesis": {
    "raw": {
      "top": {
        "0x00771836bebdd29870ff246d305c578c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000",
        "0x00771836bebdd29870ff246d305c578c5e0621c4869aa60c02be9adcc98a0d1d": "0x081cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
        "0x1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000",
        "0x1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4": "0x00000000",
        "0x1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef": "0x07000000000000000a0000000000000001",
        "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000",
        "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01",
        "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545",
        "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746bb1bdbcacd6ac9340000000000000000": "0x4545454545454545454545454545454545454545454545454545454545454545",
        "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01",
        "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x0000",
        "0x3a636f6465": "0x0",
        "0x3a65787472696e7369635f696e646578": "0x00000000",
        "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100",
        "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x0000000000000000"
      },
      "childrenDefault": {}
    }
  }
}

The following example shows the plain full config version of chain spec:

{
  "name": "TestName",
  "id": "test_id",
  "chainType": "Local",
  "bootNodes": [],
  "telemetryEndpoints": null,
  "protocolId": null,
  "properties": null,
  "codeSubstitutes": {},
  "genesis": {
    "runtimeGenesis": {
      "config": {
        "babe": {
          "authorities": [
            [
              "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
              1
            ],
            [
              "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
              1
            ],
            [
              "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y",
              1
            ]
          ],
          "epochConfig": {
            "allowed_slots": "PrimaryAndSecondaryPlainSlots",
            "c": [
              3,
              10
            ]
          }
        },
        "balances": {
          "balances": [
            [
              "5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH",
              100000000000000000
            ],
            [
              "5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o",
              100000000000000000
            ],
            [
              "5Dfis6XL8J2P6JHUnUtArnFWndn62SydeP8ee8sG2ky9nfm9",
              100000000000000000
            ],
            [
              "5F4H97f7nQovyrbiq4ZetaaviNwThSVcFobcA5aGab6167dK",
              100000000000000000
            ],
            [
              "5DiDShBWa1fQx6gLzpf3SFBhMinCoyvHM1BWjPNsmXS8hkrW",
              100000000000000000
            ],
            [
              "5EFb84yH9tpcFuiKUcsmdoF7xeeY3ajG1ZLQimxQoFt9HMKR",
              100000000000000000
            ],
            [
              "5DZLHESsfGrJ5YzT3HuRPXsSNb589xQ4Unubh1mYLodzKdVY",
              100000000000000000
            ],
            [
              "5GHJzqvG6tXnngCpG7B12qjUvbo5e4e9z8Xjidk3CQZHxTPZ",
              100000000000000000
            ],
            [
              "5CUnSsgAyLND3bxxnfNhgWXSe9Wn676JzLpGLgyJv858qhoX",
              100000000000000000
            ],
            [
              "5CVKn7HAZW1Ky4r7Vkgsr7VEW88C2sHgUNDiwHY9Ct2hjU8q",
              100000000000000000
            ],
            [
              "5H673aukQ4PeDe1U2nuv1bi32xDEziimh3PZz7hDdYUB7TNz",
              100000000000000000
            ],
            [
              "5HTe9L15LJryjUAt1jZXZCBPnzbbGnpvFwbjE3NwCWaAqovf",
              100000000000000000
            ],
            [
              "5D7LFzGpMwHPyDBavkRbWSKWTtJhCaPPZ379wWLT23bJwXJz",
              100000000000000000
            ],
            [
              "5CLepMARnEgtVR1EkUuJVUvKh97gzergpSxUU3yKGx1v6EwC",
              100000000000000000
            ],
            [
              "5Chb2UhfvZpmjjEziHbFbotM4quX32ZscRV6QJBt1rUKzz51",
              100000000000000000
            ],
            [
              "5HmRp3i3ZZk7xsAvbi8hyXVP6whSMnBJGebVC4FsiZVhx52e",
              100000000000000000
            ],
            [
              "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
              100000000000000000
            ],
            [
              "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
              100000000000000000
            ],
            [
              "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y",
              100000000000000000
            ]
          ]
        },
        "substrateTest": {
          "authorities": [
            "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
            "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
            "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y"
          ]
        },
        "system": {}
      },
      "code": "0x0"
    }
  }
}

The ChainSpec trait represents the API to access values defined in the JSON chain specification.

§Custom Chain Spec Extensions

The basic chain spec type containing all required parameters is GenericChainSpec. It can be extended with additional options containing configuration specific to your chain. Usually, the extension will be a combination of types exposed by Substrate core modules.

To allow the core modules to retrieve their configuration from your extension, you should use ChainSpecExtension macro exposed by this crate.

use std::collections::HashMap;
use sc_chain_spec::{GenericChainSpec, ChainSpecExtension};

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecExtension)]
pub struct MyExtension {
	pub known_blocks: HashMap<u64, String>,
}

pub type MyChainSpec = GenericChainSpec<MyExtension>;

Some parameters may require different values depending on the current blockchain height (a.k.a. forks). You can use the ChainSpecGroup macro and the provided Forks structure to add such parameters to your chain spec. This will allow overriding a single parameter starting at a specific block number.

use sc_chain_spec::{Forks, ChainSpecGroup, ChainSpecExtension, GenericChainSpec};

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup)]
pub struct ClientParams {
	max_block_size: usize,
	max_extrinsic_size: usize,
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup)]
pub struct PoolParams {
	max_transaction_size: usize,
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup, ChainSpecExtension)]
pub struct Extension {
	pub client: ClientParams,
	pub pool: PoolParams,
}

pub type BlockNumber = u64;

/// A chain spec supporting forkable `ClientParams`.
pub type MyChainSpec1 = GenericChainSpec<Forks<BlockNumber, ClientParams>>;

/// A chain spec supporting forkable `Extension`.
pub type MyChainSpec2 = GenericChainSpec<Forks<BlockNumber, Extension>>;

It’s also possible to have a set of parameters that are allowed to change with block numbers (i.e., they are forkable), and another set that is not subject to changes. This can also be achieved by declaring an extension that contains Forks within it.

use serde::{Serialize, Deserialize};
use sc_chain_spec::{Forks, GenericChainSpec, ChainSpecGroup, ChainSpecExtension};

#[derive(Clone, Debug, Serialize, Deserialize, ChainSpecGroup)]
pub struct ClientParams {
	max_block_size: usize,
	max_extrinsic_size: usize,
}

#[derive(Clone, Debug, Serialize, Deserialize, ChainSpecGroup)]
pub struct PoolParams {
	max_transaction_size: usize,
}

#[derive(Clone, Debug, Serialize, Deserialize, ChainSpecExtension)]
pub struct Extension {
	pub client: ClientParams,
	#[forks]
	pub pool: Forks<u64, PoolParams>,
}

pub type MyChainSpec = GenericChainSpec<Extension>;

The chain spec can be extended with other fields that are opaque to the default chain spec. Specific node implementations will need to be able to deserialize these extensions.

Re-exports§

  • pub use self::json_patch::merge as json_merge;

Modules§

  • A helper module providing json patching functions.

Structs§

Enums§

Constants§

Traits§

  • Trait for building the genesis block.
  • Common interface of a chain specification.
  • A collection of ChainSpec extensions.
  • A ChainSpec extension fork definition.
  • A subset of the Extension trait that only allows for querying extensions.
  • A ChainSpec extension.

Functions§

Type Aliases§

  • A type denoting empty extensions.
  • Arbitrary properties defined in chain spec as a JSON object

Derive Macros§