Learn
Substrate
Substrate runtime

Substrate runtime

The purpose of this document is to explain the Substrate runtime structure and the functionalities by a basic example.

The code examples that were used are sourced from the repository:
https://github.com/paritytech/polkadot-sdk (opens in a new tab) (branch: release-polkadot-v1.7.0)

General

Substrate Docs: Runtime (opens in a new tab)
Polkadot-SDK Docs (opens in a new tab)

Substrate runtime directory structure

  • → dev-project
    • ↳ ...
    • ↳ ...
    • ↳ runtime
      • ↳ src
        • → lib.rs
      • → build.rs
      • → Cargo.toml
    • ↳ ...
    • ↳ ...

Substrate runtime file structure

  • Cargo.toml: The Cargo.toml file in a Rust project is a manifest file used for Cargo, Rust's package manager and build system. This file defines various aspects of the project, such as dependencies, package information and build configurations. In the context of a Substrate runtime, the runtime/Cargo.toml provides specific information and configurations necessary to compile and manage the runtime component of the blockchain.

  • build.rs: The build.rs file in a Rust project is a build script that is executed by Cargo before the actual project is compiled. The Build script is used to perform complex build tasks such as code generation, compilation of non-Rust dependencies or automatic generation of resources. In Substrate projects, a build script is mainly used to compile the WebAssembly (WASM) version of the runtime.

  • lib.rs: The lib.rs file in a Rust project, is the main source for the project's or package's code. In a Substrate runtime, lib.rs is the central file that defines how the blockchain works by bringing together the various components (pallets) of the runtime and their configurations.

Explore the code structure

💡

The files Cargo.toml and build.rs are not analysed here, since they are intuitively understandable. Cargo.toml example (opens in a new tab)
build.rs example (opens in a new tab)

⚠️

It is important to note that the following section is a code example and that the runtime may look different in another environment.

The runtime is defined in the lib.rs file.
lib.rs example (opens in a new tab)

The runtime is designed in such a way that it can be split into different sections:

  • Preamble and configuration: Contains basic Rust attributes and settings for the entire runtime.
  • Conditional compilation and integration: Code or resources that are conditionally included based on the compilation environment.
  • Imports: Importing crates, pallets and functionalities that are used in runtime.
  • Type definitions for the runtime: Definition of global types that will be used in the runtime.
  • OPAQUE (Interoperability and abstraction): Definition of opaque types in the opaque module to simplify interaction with external systems.
  • Runtime configuration information: Specific information about the runtime, including versioning.
  • Constants and parameters for the runtime: Definition of fixed values and parameters that impact the configuration and behavior of the runtime.
  • Pallets configuration: Implementation of configuration traits for the pallets used in the runtime.
  • Composition of the runtime: Defining the runtime structure and to composition of the pallets.
  • Additional APIs and Benchmarks: Additional functions, APIs and user-defined logics that extend the functionality of the runtime.

Preamble and configuration

EXAMPLE! runtime/lib.rs
#![cfg_attr(not(feature = "std"), no_std)] 
#![recursion_limit = "256"]
  1. Allows the runtime to run in a std as well in a no_std restricted environment such as WebAssembly (WASM).
  2. construct_runtime does a lot of macro recursions and requires this spec to increase the limit (256).

Conditional compilation and integration

Conditional compilation directive, which only allows the following code to be compiled, if the std feature is activated.

EXAMPLE! runtime/lib.rs
#[cfg(feature = "std")]

The [cfg(...)] attribute in Rust allows to compile parts of the code only under certain conditions. In the context of Substrate, it is used to separate code that is specific to full Rust environments (std) or to WebAssembly environments (no_std).

The use of std is very useful for development and testing. Developers can access more comprehensive debugging tools, mocking frameworks and other tools provided by the Rust standard libraries. Therefore, tests in Substrate projects are often run in std mode to take advantage of these benefits.

On the other hand when a Substrate node is launched (e.g. a blockchain validator or a full node), it usually runs in a native environment on an operating system, which means that std features are available. The node itself, especially parts that are not running as WASM within the blockchain, uses std. This includes network communication, storing chain data, interacting with the file system and more.

The use of std in Substrate relates to both development and testing, as well as the operation of the blockchain node itself in a native execution environment. std provides access to OS-level functions and resources necessary for the effective operation and management of a node. However, the blockchain's runtime, which defines state transitions and the blockchain's core logic, is compiled for execution in the no_std WASM environment within the blockchain to ensure portability and security.

⚠️

In order to minimize the confusion for newbies:
The statement [cfg(feature = "std")] directly affects only the immediately following declaration, unless it is a block of statements enclosed in curly brackets {}.

Dynamically loads a file into the current source code at compile time:

EXAMPLE! runtime/lib.rs
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));

In Rust, especially in the context of Substrate projects, this approach plays a central role in the integration of the compiled WebAssembly (WASM) runtime into the blockchain node. This line enables a close link between the node software written in Rust and the WASM runtime, which controls the logic and state of the blockchain.

Imports

The imports in the runtime definition cover a wide range of functionalities that are necessary for the setup, configuration and functioning of a blockchain runtime.

  • Types for block structure, transactions and signatures.
  • Support functions and structures for runtime development.
  • Version information for the runtime.
  • Conditional compilation for development and testing.
  • Financial and proportional calculations within the runtime.
  • ...
EXAMPLE! runtime/lib.rs
...
use pallet_grandpa::AuthorityId as GrandpaId; 
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_core::{crypto::KeyTypeId, OpaqueMetadata};
use sp_api::impl_runtime_apis;
use sp_runtime::{
	create_runtime_str, generic, impl_opaque_keys,
	traits::{BlakeTwo256, Block as BlockT, IdentifyAccount, NumberFor, One, Verify},
	transaction_validity::{TransactionSource, TransactionValidity},
	ApplyExtrinsicResult, MultiSignature,
};
...

Overall, these imports provide the necessary building blocks and tools to develop a functional and customizable blockchain runtime that can interact with the Substrate framework.

Type definitions for the runtime

These type definitions form the basic data model for the interactions and operations that take place in the blockchain runtime. They define how blocks are numbered, how accounts and transactions are identified, how signatures and balances are handled, and how unique hashes are used for data integrity. These types are crucial for creating a consistent and secure blockchain runtime.

EXAMPLE! runtime/lib.rs
...
pub type BlockNumber = u32;
pub type Signature = MultiSignature;
pub type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
pub type Balance = u128;
pub type Nonce = u32;
pub type Hash = sp_core::H256;
...
  • BlockNumber = u32: A BlockNumber represents the number or height of a block within the blockchain. By using a u32 (a 32-bit unsigned integer type), a large but limited number of blocks is supported, which is enough for most blockchain applications.

  • Signature = MultiSignature: A signature represents a digital signature that is used to verify the authenticity and integrity of transactions or other messages within the blockchain. The MultiSignature type supports multiple signature schemes or a mechanism to authorize transactions from different actors (MultiSig).

  • AccountId = Signature as Verify>::Signer as IdentifyAccount>::AccountId: AccountId serves as a unique identifier for accounts within the blockchain. This definition shows that the AccountId is closely linked to the signature scheme. It is derived from the Signer of the signature verification trait (Verify), which means that the AccountId essentially corresponds to the public key used to create signatures.

  • Balance = u128: The balance specifies the balance or quantity of a currency unit assigned to an account. The use of u128 enables a very high maximum balance, which may be necessary for cryptocurrencies with a very small unit size or a very large total supply.

  • Nonce = u32: A nonce (number used once) is a one-time number that is used to ensure the uniqueness of transactions and prevent replay attacks. A u32 provides a reasonable range for most applications, allowing an account to perform many transactions before an overflow occurs.

  • Hash = sp_core::H256: A hash is a 256-bit hash value that is used to cryptographically encrypt data. H256 provides a sufficient length for a secure hash that is used in various contexts within the blockchain, such as for identifying blocks, transactions or for storing and verifying Merkle tree roots.

OPAQUE (Interoperability and abstraction)

Opaque types are used to simplify interaction with external systems and increase the level of abstraction by hiding the internal details of the runtime. They allow certain parts of the blockchain software, such as the command line interface (CLI) and external systems, to interact with the blockchain without knowing the specific implementation details.

EXAMPLE! runtime/lib.rs
pub mod opaque {
	use super::*;
    pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic;
	pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
	pub type Block = generic::Block<Header, UncheckedExtrinsic>;
	pub type BlockId = generic::BlockId<Block>;
	impl_opaque_keys! {
		pub struct SessionKeys {
			pub aura: Aura,
			pub grandpa: Grandpa,
		}
	}
}
  • OpaqueExtrinsic as UncheckedExtrinsic: Extrinsics are the external requests or transactions that are sent to the blockchain. OpaqueExtrinsic is an opaque type for extrinsics, which means that its internal structure is abstracted. This type is used to enable the processing of extrinsics without knowing their exact structure or content. The alias UncheckedExtrinsic indicates that these extrinsics have not yet been checked for validity.

  • Opaque Header: An opaque type for the block header that contains important information about the block such as the block number and parent hash, but in a way that hides the internal details.

  • Opaque Block: An opaque type for a complete block that includes both the header and the associated extrinsics (transactions).

  • Opaque BlockId: A type used to uniquely identify blocks, again without the need to know the specific details of the block content.

  • SessionKeys: SessionKeys is a structure that contains the keys for various consensus mechanisms (such as Aura and Grandpa) in an opaque manner. This allows the network and its participants to handle the keys for validation and block creation without having to disclose the specific cryptographic details of these consensus protocols.

Meaning and benefits:

  • Interoperability and abstraction: By using opaque types, external systems and tools, such as the CLI, we can interact with the blockchain without having to rely on the internal implementation or the specific data formats. This facilitates the integration and further development of the blockchain.

  • Future-proofing: Changes can be made to the core structures of the runtime without necessarily requiring changes to all tools or systems that interact with the blockchain. This contributes to the stability of the ecosystem and facilitates upgrades.

  • Security: The abstraction of internal details provides an additional layer of security as less information about the internal structure of the blockchain is revealed.

Runtime configuration information

This segment defines important configuration information for the runtime of a substrate blockchain, particularly with regard to versioning and identification. This information is crucial for the management of upgrades and interoperability within the Substrate framework.

EXAMPLE! runtime/lib.rs
#[sp_version::runtime_version]
pub const VERSION: RuntimeVersion = RuntimeVersion {
	spec_name: create_runtime_str!("dev-node"),
	impl_name: create_runtime_str!("dev-node"),
	authoring_version: 1,
	spec_version: 100,
	impl_version: 1,
	apis: RUNTIME_API_VERSIONS,
	transaction_version: 1,
	state_version: 1,
}
  • [sp_version::runtime_version]: Uses the RuntimeVersion structure of Substrate to specify a set of versioning and identification information for the runtime.

  • Runtime version fields:

    • spec_name: A string that specifies the name of the runtime specification.

    • impl_name: The name of the concrete implementation of the specification.

    • authoring_version: The version of the authoring format. This is important to ensure that the blocks generated by this runtime are compatible with the network infrastructure.

    • spec_version: The main version number of the runtime specification. A change here indicates an incompatible change that requires an upgrade of the WASM runtime running on the blockchain.

    • impl_version: The implementation version. This is used to indicate revisions in the implementation that do not result in incompatible changes.

    • apis: A list of supported API versions. This allows nodes to check if they are compatible with the runtime in terms of available functions.

    • transaction_version: Specifies the version of the transaction format. A change here would mean that the format of the transactions has changed.

    • state_version: The version of the state format. Changes here would indicate that the way in which state data is stored and retrieved has changed.

Careful versioning of the runtime and its components allows changes to the blockchain to be made in a controlled manner. Nodes in the network can use this information to determine if they are compatible and can successfully synchronize with the current runtime. In particular, the spec_version allows clear communication about upgrades that involve a change to the blockchain logic, ensuring network integrity and stability. The use of this versioning information also facilitates interaction with external tools and services, such as the Polkadot-JS app, which require specific versions of the runtime for correct display and interaction.

Constants and parameters for the runtime

This section defines constants and parameters essential for the configuration and behavior of the runtime in a Substrate blockchain. Each constant and each parameter has specific effects on the functioning of the blockchain.

EXAMPLE! runtime/lib.rs
pub const MILLISECS_PER_BLOCK: u64 = 6000;
pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
pub const HOURS: BlockNumber = MINUTES * 60;
pub const DAYS: BlockNumber = HOURS * 24;
 
pub fn native_version() -> NativeVersion {
	NativeVersion { runtime_version: VERSION, can_author_with: Default::default() }
}
const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
parameter_types! {
	pub const BlockHashCount: BlockNumber = 2400;
	pub const Version: RuntimeVersion = VERSION;
	pub BlockWeights: frame_system::limits::BlockWeights =
		frame_system::limits::BlockWeights::with_sensible_defaults(
			Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX),
			NORMAL_DISPATCH_RATIO,
		);
	pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength
		::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO);
	pub const SS58Prefix: u8 = 42;
}
  • Block Time und Slot Duration:

    • MILLISECS_PER_BLOCK: Defines the average target time for creating a block in milliseconds (6000 milliseconds (6 seconds). This value determines how often blocks are to be produced in the blockchain.

    • SLOT_DURATION: Corresponds to MILLISECS_PER_BLOCK and is used by pallet_timestamp (in section pallets configuration), which in turn is used by pallet_aura to implement the slot_duration() function. This ensures that the slot duration for block production is consistent with the desired block time. It is not possible to change the slot duration after the blockchain has been started. Such an attempt would interrupt the block production.

  • Time measurement in blocks: These constants (MINUTES, HOURS, DAYS) convert time specifications into the corresponding number of blocks, based on the defined MILLISECS_PER_BLOCK. This is useful for defining durations in the blockchain in terms of blocks instead of real time.

  • native_version function: This function returns a NativeVersion structure that contains specific information about the native version of the runtime. This information is particularly relevant if the blockchain node is executed in a native environment (outside of WASM), for example during the development process or if a node is running as a full node or validator.

    • runtime_version: Is set with the already defined VERSION constant of the runtime (from section runtime configuration information). This makes it possible to keep the versioning of the runtime consistent both in WASM and in the native version.

    • can_author_with: A field that specifies whether the runtime is able to create blocks based on the current configuration and the available functions. Default::default() usually sets a default value that is based on the configuration.

  • NORMAL_DISPATCH_RATIO: Determines the ratio of the maximum weight that may be used for normal dispatch calls (as opposed to operational or critical calls) in a block. Here it is set to 75%, which means that up to 75% of a block's resources can be used for processing normal transactions. This is important for resource management and the prioritization of transactions within the blockchain.

  • parameter_types macro: A macro that is used to define constants and parameters within the runtime. It enables a clear and concise definition of values that influence the configuration and behavior of the blockchain.

    • BlockHashCount: This constant defines how many of the last finalized block hashes should be stored by the system. It is an important parameter for managing the blockchain history and affects the system's ability to answer queries about historical blocks and support network security functions such as avoiding replay attacks. By storing a sufficient number of block hashes, the system can efficiently process queries regarding the existence or validity of previous transactions and blocks. The choice of value depends on various factors, including the expected block time and the need to keep historical data available for light clients or other network participants.

    • Version: This constant binds the previously defined VERSION structure, which contains specific information about the runtime version, to an easily accessible identifier within the runtime configuration. As already explained, VERSION contains details such as the name of the specification (spec_name), the name of the implementation (impl_name), version numbers (spec_version, impl_version), supported APIs and the versions of transactions and state. Assigning this structured versioning information to version allows the runtime and external systems to access version information accurately and efficiently, which is critical for upgrades, interoperability and compatibility checks within the Substrate.

    • BlockWeights and BlockLength: These parameters define the maximum weights and lengths of blocks in order to control the blockchain's resource utilization. They determine the maximum amount of computing time and storage space a block may use, which directly influences the performance and scalability of the blockchain.

      • BlockWeights: is set with reasonable default values that create a balance between throughput and block processing time.
      • BlockLength: defines the maximum size of a block in bytes in order to optimize the network bandwidth and memory requirements.
  • SS58Prefix: Determines the prefix for addresses in the Substrate network. This is useful for distinguishing addresses of different Substrate-based networks.

The definition of these constants and parameters is crucial for the basic design and performance of the blockchain. They influence how quickly and efficiently blocks are processed, how the network scales and how users interact with the blockchain.

Pallets configuration

This code section configures various pallets within a Substrate blockchain runtime. Each pallet requires an implementation of its configuration traits to be integrated into the runtime. This configuration defines how each pallet will behave, including the data types and logic used.

EXAMPLE! runtime/lib.rs
#[derive_impl(frame_system::config_preludes::SolochainDefaultConfig as frame_system::DefaultConfig)]
impl frame_system::Config for Runtime {
	type Block = Block;
	type BlockWeights = BlockWeights;
	type BlockLength = BlockLength;
	type AccountId = AccountId;
	type Nonce = Nonce;
	type Hash = Hash;
	type BlockHashCount = BlockHashCount;
	type DbWeight = RocksDbWeight;
	type Version = Version;
	type AccountData = pallet_balances::AccountData<Balance>;
	type SS58Prefix = SS58Prefix;
	type MaxConsumers = frame_support::traits::ConstU32<16>;
}
 
impl pallet_aura::Config for Runtime {
	type AuthorityId = AuraId;
	type DisabledValidators = ();
	type MaxAuthorities = ConstU32<32>;
	type AllowMultipleBlocksPerSlot = ConstBool<false>;
 
    #[cfg(feature = "experimental")]
	type SlotDuration = pallet_aura::MinimumPeriodTimesTwo<Runtime>;
}
 
impl pallet_grandpa::Config for Runtime {
	type RuntimeEvent = RuntimeEvent;
 
	type WeightInfo = ();
	type MaxAuthorities = ConstU32<32>;
	type MaxNominators = ConstU32<0>;
	type MaxSetIdSessionEntries = ConstU64<0>;
 
	type KeyOwnerProof = sp_core::Void;
	type EquivocationReportSystem = ();
}
 
impl pallet_timestamp::Config for Runtime {
	type Moment = u64;
	type OnTimestampSet = Aura;
	type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>;
	type WeightInfo = ();
}
 
pub const EXISTENTIAL_DEPOSIT: u128 = 500;
 
impl pallet_balances::Config for Runtime {
	type MaxLocks = ConstU32<50>;
	type MaxReserves = ();
	type ReserveIdentifier = [u8; 8];
	type Balance = Balance;
	type RuntimeEvent = RuntimeEvent;
	type DustRemoval = ();
	type ExistentialDeposit = ConstU128<EXISTENTIAL_DEPOSIT>;
	type AccountStore = System;
	type WeightInfo = pallet_balances::weights::SubstrateWeight<Runtime>;
	type FreezeIdentifier = ();
	type MaxFreezes = ();
	type RuntimeHoldReason = ();
	type RuntimeFreezeReason = ();
}
 
parameter_types! {
	pub FeeMultiplier: Multiplier = Multiplier::one();
}
 
impl pallet_transaction_payment::Config for Runtime {
	type RuntimeEvent = RuntimeEvent;
	type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
	type OperationalFeeMultiplier = ConstU8<5>;
	type WeightToFee = IdentityFee<Balance>;
	type LengthToFee = IdentityFee<Balance>;
	type FeeMultiplierUpdate = ConstFeeMultiplier<FeeMultiplier>;
}
 
impl pallet_sudo::Config for Runtime {
	type RuntimeEvent = RuntimeEvent;
	type RuntimeCall = RuntimeCall;
	type WeightInfo = pallet_sudo::weights::SubstrateWeight<Runtime>;
}
  • frame_system::Config: Defines basic system parameters that are required for the blockchain runtime, such as the block type, weights and lengths of blocks, account IDs, nonces, hash types and more. These settings are crucial for the basic functioning of the blockchain.

  • pallet_aura::Config: Configures the AURA consensus pallet for use with the runtime. This includes the definition of the AuthorityId type, the handling of deactivated validators, the maximum number of authorities and the slot duration for block production.

  • pallet_grandpa::Config: Provides the configuration for the GRANDPA consensus pallet, including the event type definition and settings for the consensus mechanisms such as the maximum number of authorities and nominators.

  • pallet_timestamp::Config: Defines the configuration for the timestamp pallet, which is responsible for managing blockchain time. This includes defining the time format (in milliseconds since the Unix epoch), the actions when updating the time and the minimum time interval between blocks.

  • pallet_balances::Config: Configures the balances pallet, which provides the core logic for holding and transferring token balances within the blockchain. Important configurations include the balance type, event type, dust removal, existential deposit and account information storage locations.

  • pallet_transaction_payment::Config: Defines the configuration for the transaction payment pallet, which contains the logic for transaction fees. The calculation of fees, the adjustment of fee multipliers and the operations for fee collection are defined here.

  • pallet_sudo::Config: Enables the configuration of the sudo pallet, which is responsible for providing superuser functionality within the blockchain. This allows certain transactions to be executed on behalf of the superuser.

  • EXISTENTIAL_DEPOSIT: Defines the minimum amount of tokens that an account must hold in order to exist in the blockchain state. Amounts below this value result in the account and its data being removed from the blockchain's memory (state) in order to save resources. By setting this value, the system simplifies the management of accounts by ensuring that only economically relevant accounts are kept in state. It also helps prevent blockchain database bloat by automatically removing "dust" accounts (accounts with tiny balances that are practically useless). This makes the runtime more efficient while simplifying resource management.

  • parameter_types! macro with FeeMultiplier: The parameter_types! macro makes it possible to define constant parameter values in the runtime configuration. FeeMultiplier is one such parameter that is used to control the adjustment of transaction fees based on the current network state or utilization. Using parameter_types! to define FeeMultiplier simplifies the customization and management of transaction fees. Developers can easily see and change how fees are adjusted in the network and control this logic centrally. This leads to a clearer and simpler configuration of the fee logic within the runtime.

Composition of the runtime

EXAMPLE! runtime/lib.rs
construct_runtime!(
	pub enum Runtime {
		System: frame_system,
		Timestamp: pallet_timestamp,
		Aura: pallet_aura,
		Grandpa: pallet_grandpa,
		Balances: pallet_balances,
		TransactionPayment: pallet_transaction_payment,
		Sudo: pallet_sudo,
	}
);
 
pub type Address = sp_runtime::MultiAddress<AccountId, ()>;
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
pub type SignedExtra = (
	frame_system::CheckNonZeroSender<Runtime>,
	frame_system::CheckSpecVersion<Runtime>,
	frame_system::CheckTxVersion<Runtime>,
	frame_system::CheckGenesis<Runtime>,
	frame_system::CheckEra<Runtime>,
	frame_system::CheckNonce<Runtime>,
	frame_system::CheckWeight<Runtime>,
	pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
);
 
#[allow(unused_parens)]
type Migrations = ();
 
pub type UncheckedExtrinsic =
	generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, SignedExtra>;
pub type SignedPayload = generic::SignedPayload<RuntimeCall, SignedExtra>;
pub type Executive = frame_executive::Executive<
	Runtime,
	Block,
	frame_system::ChainContext<Runtime>,
	Runtime,
	AllPalletsWithSystem,
	Migrations,
>;
  • construct_runtime! macro: The construct_runtime! macro is a central element in Substrate that is used to define the runtime. It composes the different pallets (modules) into a coherent runtime by specifying which pallets are included and how they are named. Each pallet extends the functionality of the blockchain with specific features, such as the system pallet for basic blockchain functions or the balances pallet for token balances and transfers.

  • Type definitions:

    • Address: Defines the address format type for accounts in the blockchain. sp_runtime::MultiAddress is a flexible address representation that supports different types of address identification.

    • Header: Specifies the type of block header that is expected for the runtime. generic::Header is a general implementation of a block header in Substrate that includes parameterization such as BlockNumber and the hashing algorithm (BlakeTwo256).

    • Block: Defines the type of a block as expected by the runtime. A block consists of a header and a series of transactions (extrinsics).

    • SignedExtra: A collection of checks (called "signed extensions") that are attached to transactions to provide additional validation logic before they are processed.

  • Migrations: Defines types for runtime upgrades outside the migrations declared by pallets. These can be used to implement customizable upgrade logics.

  • UncheckedExtrinsic Defines the type for transactions (extrinsics) that have not yet been fundamentally validated. This is the format in which transactions are typically received before they are checked.

  • SignedPayload: Specifies the type for the payload of a signed transaction. This includes the actual transaction data and the "Signed Extensions".

  • Executive: Executive is a type that is responsible for coordinating the various pallets and processing transactions and blocks within the runtime. It handles the dispatch to the modules, carries out initializations and completions of blocks and is responsible for the management of upgrades and configuration changes.

Together, these components form the structure and functional logic of the Substrate runtime. The construct_runtime! macro defines a modular and extensible blockchain that can be easily customized by adding or removing pallets. The type definitions and the executive structure enable the runtime to process transactions efficiently and manage the state of the blockchain consistently and securely.

Additional APIs and Benchmarks

This section is an example of the integration of additional APIs, benchmarks and other advanced functionalities into a Substrate runtime. These extensions enable deeper interaction with the runtime as well as the evaluation of its performance.

EXAMPLE! runtime/lib.rs
 
#[cfg(feature = "runtime-benchmarks")]
mod benches {
	frame_benchmarking::define_benchmarks!(
		[frame_benchmarking, BaselineBench::<Runtime>]
		[frame_system, SystemBench::<Runtime>]
		[pallet_balances, Balances]
		[pallet_timestamp, Timestamp]
		[pallet_sudo, Sudo]
	);
}
 
impl_runtime_apis! {
	impl sp_api::Core<Block> for Runtime {
    ...
    }
	impl sp_api::Metadata<Block> for Runtime {
    ...
    }
	impl sp_block_builder::BlockBuilder<Block> for Runtime {
    ...
    }
	impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
    ...
    }
	impl sp_offchain::OffchainWorkerApi<Block> for Runtime {
    ...
    }
	impl sp_consensus_aura::AuraApi<Block, AuraId> for Runtime {
    ...
    }
    impl sp_session::SessionKeys<Block> for Runtime {
    ...
    }
	impl sp_consensus_grandpa::GrandpaApi<Block> for Runtime {
    ...
    }
	impl frame_system_rpc_runtime_api::AccountNonceApi<Block, AccountId, Nonce> for Runtime {
    ...
    }
	impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance> for Runtime {
    ...
    }
	impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi<Block, Balance, RuntimeCall> {
    ...
    }    
    
    #[cfg(feature = "runtime-benchmarks")]
	impl frame_benchmarking::Benchmark<Block> for Runtime {
	...
    }
	
    #[cfg(feature = "try-runtime")]
	impl frame_try_runtime::TryRuntime<Block> for Runtime {
    ...
    
    }
    impl sp_genesis_builder::GenesisBuilder<Block> for Runtime {
    ...
    }	
}
  • Benchmark: By using [cfg(feature = "runtime-benchmarks")], this code section will only be compiled and integrated into the Runtime if the specific feature flag runtime-benchmarks is activated. It defines benchmarks for various pallets that can be used to evaluate the performance of the runtime for certain operations. These benchmarks are essential for optimizing and ensuring the efficiency of the blockchain.

  • Runtime APIs implementation: The impl_runtime_apis! macro is used to implement various APIs that are required for interaction with the runtime.

    • Core API: Enables basic functions such as retrieving the runtime version, executing and initializing blocks.
    • Metadata API: Provides access to runtime metadata, which is important for front-end applications and wallets to understand the available functions and structures.
    • BlockBuilder API: Used to add transactions to a block, finalize the block and generate inherent extrinsics.
    • Transaction Pool API: Allows transactions to be validated before being added to the pool.
    • Offchain Worker API: Defines how offchain workers act outside the blockchain, e.g. for executing tasks that do not need to be executed on-chain.
    • Consensus-specific APIs: Consensus-specific APIs such as AURA and GRANDPA, which are necessary for the consensus mechanism to function.
    • Session Keys API: Enables the generation and decoding of session keys for validator nodes.
    • [cfg(feature = "runtime-benchmarks")]: The impl frame_benchmarking::Benchmark<Block> for Runtime provides the necessary infrastructure and functions to perform the benchmarks (defined in mod benches) and work with their results.
    • [cfg(feature = "try-runtime")]: allows runtime upgrades to be tested in a secure environment before they are performed on the live network. This functionality is crucial for maintaining network integrity during the upgrade process.
    • GenesisBuilder: As a final step, impl sp_genesis_builder::GenesisBuilder<Block> for Runtime makes it possible to define and customize a standard configuration for Genesis block creation. This is important for setting up the blockchain with the correct start parameters.