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:
- importing
frame
, [codec
], and [scale_info
] crates. - following the
std
feature-gating pattern.
But, more specifically, it also contains:
- a
build.rs
that usessubstrate_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: create_runtime_str!("first-runtime"),
impl_name: create_runtime_str!("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:
- 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
andNone
.
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;
let config = RuntimeGenesisConfig {
balances: BalancesConfig {
balances: AccountKeyring::iter()
.map(|a| (a.to_account_id(), endowment))
.collect::<Vec<_>>(),
},
sudo: SudoConfig { key: Some(AccountKeyring::Alice.to_account_id()) },
..Default::default()
};
serde_json::to_value(config).expect("Could not build genesis config.")
}
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
- To learn more about signed extensions, see
crate::reference_docs::signed_extensions
. AllPalletsWithSystem
is also generated byconstruct_runtime
, as explained incrate::reference_docs::frame_runtime_types
.Executive
supports more generics, most notably allowing the runtime to configure more runtime migrations, as explained incrate::reference_docs::frame_runtime_upgrades_and_migrations
.- Learn more about adding and implementing runtime apis in
crate::reference_docs::custom_runtime_api_rpc
. - To see a complete example of a runtime+pallet that is similar to this guide, please see
crate::polkadot_sdk::templates
.