referrerpolicy=no-referrer-when-downgrade

Module polkadot_sdk_docs::guides::your_first_runtime

source ·
Expand description

Write your first real runtime, compiling it to WASM.

§Your first Runtime

This guide will walk you through the steps to add your pallet to a runtime.

The good news is, in crate::guides::your_first_pallet, we have already created a test runtime that was used for testing, and a real runtime is not that much different!

§Setup

A runtime shares a few similar setup requirements as with a pallet:

But, more specifically, it also contains:

  • a build.rs that uses substrate_wasm_builder. This entails declaring [build-dependencies] in the Cargo manifest file:
[build-dependencies]
substrate-wasm-builder = { ... }

Note that a runtime must always be one-runtime-per-crate. You cannot define multiple runtimes per rust crate.

You can find the full code of this guide in first_runtime.

§Your First Runtime

The first new property of a real runtime that it must define its frame::runtime::prelude::RuntimeVersion:

#[runtime_version]
pub const VERSION: RuntimeVersion = RuntimeVersion {
	spec_name: alloc::borrow::Cow::Borrowed("first-runtime"),
	impl_name: alloc::borrow::Cow::Borrowed("first-runtime"),
	authoring_version: 1,
	spec_version: 0,
	impl_version: 1,
	apis: RUNTIME_API_VERSIONS,
	transaction_version: 1,
	system_version: 1,
};

The version contains a number of very important fields, such as spec_version and spec_name that play an important role in identifying your runtime and its version, more importantly in runtime upgrades. More about runtime upgrades in crate::reference_docs::frame_runtime_upgrades_and_migrations.

Then, a real runtime also contains the impl of all individual pallets’ trait Config for struct Runtime, and a frame::runtime::prelude::construct_runtime macro that amalgamates them all.

In the case of our example:

impl our_first_pallet::Config for Runtime {
	type RuntimeEvent = RuntimeEvent;
}

In this example, we bring in a number of other pallets from frame into the runtime, each of their Config need to be implemented for struct Runtime:

use super::*;

parameter_types! {
	pub const Version: RuntimeVersion = VERSION;
}

#[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)]
impl frame_system::Config for Runtime {
	type Block = Block;
	type Version = Version;
	type AccountData =
		pallet_balances::AccountData<<Runtime as pallet_balances::Config>::Balance>;
}

#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Runtime {
	type AccountStore = System;
}

#[derive_impl(pallet_sudo::config_preludes::TestDefaultConfig)]
impl pallet_sudo::Config for Runtime {}

#[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)]
impl pallet_timestamp::Config for Runtime {}

#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)]
impl pallet_transaction_payment::Config for Runtime {
	type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter<Balances, ()>;
	// We specify a fixed length to fee here, which essentially means all transactions charge
	// exactly 1 unit of fee.
	type LengthToFee = FixedFee<1, <Self as pallet_balances::Config>::Balance>;
	type WeightToFee = NoFee<<Self as pallet_balances::Config>::Balance>;
}

Notice how we use frame::pallet_macros::derive_impl to provide “default” configuration items for each pallet. Feel free to dive into the definition of each default prelude (eg. frame::prelude::frame_system::pallet::config_preludes) to learn more which types are exactly used.

Recall that in test runtime in crate::guides::your_first_pallet, we provided type AccountId = u64 to frame_system, while in this case we rely on whatever is provided by SolochainDefaultConfig, which is indeed a “real” 32 byte account id.

Then, a familiar instance of construct_runtime amalgamates all of the pallets:

construct_runtime!(
	pub struct Runtime {
		// Mandatory for all runtimes
		System: frame_system,

		// A number of other pallets from FRAME.
		Timestamp: pallet_timestamp,
		Balances: pallet_balances,
		Sudo: pallet_sudo,
		TransactionPayment: pallet_transaction_payment,

		// Our local pallet
		FirstPallet: our_first_pallet,
	}
);

Recall from crate::reference_docs::wasm_meta_protocol that every (real) runtime needs to implement a set of runtime APIs that will then let the node to communicate with it. The final steps of crafting a runtime are related to achieving exactly this.

First, we define a number of types that eventually lead to the creation of an instance of frame::runtime::prelude::Executive. The executive is a handy FRAME utility that, through amalgamating all pallets and further types, implements some of the very very core pieces of the runtime logic, such as how blocks are executed and other runtime-api implementations.

use super::*;
pub(super) type SignedExtra = (
	// `frame` already provides all the signed extensions from `frame-system`. We just add the
	// one related to tx-payment here.
	frame::runtime::types_common::SystemTransactionExtensionsOf<Runtime>,
	pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
);

pub(super) type Block = frame::runtime::types_common::BlockOf<Runtime, SignedExtra>;
pub(super) type Header = HeaderFor<Runtime>;

pub(super) type RuntimeExecutive = Executive<
	Runtime,
	Block,
	frame_system::ChainContext<Runtime>,
	Runtime,
	AllPalletsWithSystem,
>;

Finally, we use frame::runtime::prelude::impl_runtime_apis to implement all of the runtime APIs that the runtime wishes to expose. As you will see in the code, most of these runtime API implementations are merely forwarding calls to RuntimeExecutive which handles the actual logic. Given that the implementation block is somewhat large, we won’t repeat it here. You can look for impl_runtime_apis! in first_runtime.

impl_runtime_apis! {
	impl apis::Core<Block> for Runtime {
		fn version() -> RuntimeVersion {
			VERSION
		}

		fn execute_block(block: Block) {
			RuntimeExecutive::execute_block(block)
		}

		fn initialize_block(header: &Header) -> ExtrinsicInclusionMode {
			RuntimeExecutive::initialize_block(header)
		}
	}

	// many more trait impls...
}

And that more or less covers the details of how you would write a real runtime!

Once you compile a crate that contains a runtime as above, simply running cargo build will generate the wasm blobs and place them under ./target/release/wbuild, as explained here.

§Genesis Configuration

Every runtime specifies a number of runtime APIs that help the outer world (most notably, a node) know what is the genesis state of this runtime. These APIs are then used to generate what is known as a Chain Specification, or chain spec for short. A chain spec is the primary way to run a new chain.

These APIs are defined in sp_genesis_builder, and are re-exposed as a part of frame::runtime::apis. Therefore, the implementation blocks can be found inside of impl_runtime_apis! similar to:

impl_runtime_apis! {
 	impl apis::GenesisBuilder<Block> for Runtime {
			fn build_state(config: Vec<u8>) -> GenesisBuilderResult {
				build_state::<RuntimeGenesisConfig>(config)
			}

			fn get_preset(id: &Option<PresetId>) -> Option<Vec<u8>> {
				get_preset::<RuntimeGenesisConfig>(id, self::genesis_config_presets::get_preset)
			}

			fn preset_names() -> Vec<PresetId> {
				crate::genesis_config_presets::preset_names()
			}
		}

}

The implementation of these function can naturally vary from one runtime to the other, but the overall pattern is common. For the case of this runtime, we do the following:

  1. Expose one non-default preset, namely sp_genesis_builder::DEV_RUNTIME_PRESET. This means our runtime has two “presets” of genesis state in total: DEV_RUNTIME_PRESET and None.
pub fn preset_names() -> Vec<PresetId> {
	vec![PresetId::from(DEV_RUNTIME_PRESET)]
}

For build_state and get_preset, we use the helper functions provide by frame:

Indeed, our runtime needs to specify what its DEV_RUNTIME_PRESET genesis state should be like:

pub fn development_config_genesis() -> Value {
	let endowment = <MinimumBalance as Get<Balance>>::get().max(1) * 1000;
	build_struct_json_patch!(RuntimeGenesisConfig {
		balances: BalancesConfig {
			balances: AccountKeyring::iter()
				.map(|a| (a.to_account_id(), endowment))
				.collect::<Vec<_>>(),
		},
		sudo: SudoConfig { key: Some(AccountKeyring::Alice.to_account_id()) },
	})
}

For more in-depth information about GenesisConfig, ChainSpec, the GenesisBuilder API and chain-spec-builder, see crate::reference_docs::chain_spec_genesis.

§Next Step

See crate::guides::your_first_node.

§Further Reading

  1. To learn more about signed extensions, see crate::reference_docs::signed_extensions.
  2. AllPalletsWithSystem is also generated by construct_runtime, as explained in crate::reference_docs::frame_runtime_types.
  3. Executive supports more generics, most notably allowing the runtime to configure more runtime migrations, as explained in crate::reference_docs::frame_runtime_upgrades_and_migrations.
  4. Learn more about adding and implementing runtime apis in crate::reference_docs::custom_runtime_api_rpc.
  5. To see a complete example of a runtime+pallet that is similar to this guide, please see crate::polkadot_sdk::templates.