Substrate

construct_runtime!

Constructing a Runtime!

The construct_runtime! macro is where you declare all the runtime modules you would like to include into your blockchain's runtime. This includes any modules from the Substrate Runtime Module Library (SRML) or even custom modules you may have written.

Here is an example of a construct_runtime! definition from the substrate-node-template:

construct_runtime!(
    pub enum Runtime with Log(InternalLog: DigestItem<Hash, Ed25519AuthorityId>) where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        System: system::{default, Log(ChangesTrieRoot)},
        Timestamp: timestamp::{Module, Call, Storage, Config<T>, Inherent},
        Consensus: consensus::{Module, Call, Storage, Config<T>, Log(AuthoritiesChange), Inherent},
        Aura: aura::{Module},
        Indices: indices,
        Balances: balances,
        Sudo: sudo,
    }
);

Defining the Modules Used

In the example above, we only use modules in the SRML, but you can see that there are different syntactical ways that you can define the use of a module and its types. These different patterns are managed by the macro definition.

Naming Your Module

Each line in your construct_runtime! macro defines a module you are using in your runtime.

System: system::{default, Log(ChangesTrieRoot)},

The lowercase system points to the Rust module where you have defined your runtime logic (following the standard Rust style guidelines). The uppercase System is a friendly name that you can define for your module which gets exposed through the runtime APIs.

Supported Types

The construct_runtime! macro provides support for the following types in a module:

No Types or default

In the definition of the Balances or Sudo module, you see that we do not specify any of the types used within the module. In this case, the macro automatically expands this definition to include the following:

Module, Call, Storage, Event<T>, Config<T>

So these two lines are equivalent:

Balances: balances,
// Same as...
Balances: balances::{Module, Call, Storage, Event<T>, Config<T>},

If you want to include these default types in addition to other types you define, you can use the default keyword as shown in the System module. Thus these two lines are equivalent as well:

System: system::{default, Log(ChangesTrieRoot)},
// Same as...
System: system::{Module, Call, Storage, Event<T>, Config<T>, Log(ChangesTrieRoot)},

When To Use Different Types

As you can see, the types exposed by your various modules are what ultimately power the runtime. Most of these types are automatically generated by other macros used in the runtime development.

Module

The Module type is required by all modules in your runtime. This type gets generated through the decl_module! macro. This is where all the public functions your module exposes are defined. For example, in the Sudo module which controls the management of "admin" access to your chain:

decl_module! {
    // Simple declaration of the `Module` type. Lets the macro know what its working on.
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        fn deposit_event<T>() = default;

        fn sudo(origin, proposal: Box<T::Proposal>) {
            // This is a public call, so we ensure that the origin is some signed account.
            let sender = ensure_signed(origin)?;
            ensure!(sender == Self::key(), "only the current sudo key can sudo");

            let ok = proposal.dispatch(system::RawOrigin::Root.into()).is_ok();
            Self::deposit_event(RawEvent::Sudid(ok));
        }

        fn set_key(origin, new: <T::Lookup as StaticLookup>::Source) {
            // This is a public call, so we ensure that the origin is some signed account.
            let sender = ensure_signed(origin)?;
            ensure!(sender == Self::key(), "only the current sudo key can change the sudo key");
            let new = T::Lookup::lookup(new)?;

            Self::deposit_event(RawEvent::KeyChanged(Self::key()));
            <Key<T>>::put(new);
        }
    }
}

You can learn more about decl_module! here.

Call

The Call type is an enum generated by the decl_module! macro which contains a list of all the callable functions and their arguments. For example, in the Sudo module above, we should expect an enum similar to:

enum Call<T> {
    sudo(proposal: Box<T::Proposal>),
    set_key(new: <T::Lookup as StaticLookup>::Source),
}

We will dig deeper into this topic [here] (Coming Soon).

Storage

The Storage type is exposed whenever your runtime uses the decl_storage! macro. This macro is used to save data used by your runtime module into the chain state.

We can look at the Sudo module to see an example:

decl_storage! {
    trait Store for Module<T: Trait> as Sudo {
        Key get(key) config(): T::AccountId;
    }
}

You can learn more about decl_storage!

Event

The Event type needs to be exposed whenever your module deposits events using the decl_event! module. Events can be used to easily report changes or conditions in your runtime to external entities like users, chain explorers, or dApps.

If you want to expose generic types defined by your module, you will need to make your type generic (Event<T>), as shown here:

/// An event in this module.
decl_event!(
    pub enum Event<T> where AccountId = <T as system::Trait>::AccountId {
        /// A sudo just took place.
        Sudid(bool),
        /// The sudoer just switched identity; the old key is supplied.
        KeyChanged(AccountId),
    }
);

Otherwise, you can simply use Event for non-generic types. You can learn more about decl_event! [here] (Coming Soon).

Origin

The Origin type is needed whenever your module declares a custom Origin enum to be used within your module.

Every function call to your runtime has an origin which specifies where the extrinsic was generated from. In the case of a signed extrinsic (transaction), the origin contains an identifier for the caller. The origin can be empty in the case of an inherent extrinsic.

You can see an example of a customized Origin in the council motion module:

/// Origin for the council module.
#[derive(PartialEq, Eq, Clone)]
#[cfg_attr(feature = "std", derive(Debug))]
pub enum Origin {
    /// It has been condoned by a given number of council members.
    Members(u32),
}

We will dig deeper into this topic [here] (Coming Soon).

Config

The Config type is needed whenever your module defines an initial state in the genesis configuration. By default, all storage variables are left in an 'unassigned' state, but using the config() keyword while declaring the storage variable allows you to initialize that state.

An example of that initialization can be found in the substrate-node-template, where Alice is set as the upgrade Key for Sudo. You can see from the Storage section that Key does have the config() parameter, and the value for it gets set in the GenesisConfig within chain_spec.rs:

GenesisConfig {
    ...
    sudo: Some(SudoConfig {
        key: root_key,
    }),
}

We will dig deeper into this topic [here] (Coming Soon).

Log

The Log type is needed if your module generates logs entries for the runtime. Within the module, you must expose both a Log type and a RawLog enum which defines what the module is logging. Logs must satisfy the following requirements:

1) The binary representation of all supported 'system' log items should stay the same. Otherwise, the native code will be unable to read log items generated by previous runtime versions.

2) The support of 'system' log items should never be dropped by runtime. Otherwise, native code will lose its ability to read items of this type even if they were generated by the versions which have supported these items.

The Consensus module shows an example of how to implement a log:

pub type Log<T> = RawLog<
    <T as Trait>::SessionKey,
>;

/// Add logs in this module.
#[cfg_attr(feature = "std", derive(Serialize, Debug))]
#[derive(Encode, Decode, PartialEq, Eq, Clone)]
pub enum RawLog<SessionKey> {
    /// Authorities set has been changed. Contains the new set of authorities.
    AuthoritiesChange(Vec<SessionKey>),
}

impl<SessionKey: Member> RawLog<SessionKey> {
    /// Try to cast the log entry as AuthoritiesChange log entry.
    pub fn as_authorities_change(&self) -> Option<&[SessionKey]> {
        match *self {
            RawLog::AuthoritiesChange(ref item) => Some(item),
        }
    }
}

It is customary to expose a deposit_log() function within your module which creates a log:

/// Deposit one of this module's logs.
fn deposit_log(log: Log<T>) {
    <system::Module<T>>::deposit_log(<T as Trait>::Log::from(log).into());
}

We will dig deeper into this topic [here] (Coming Soon).

Inherent

The Inherent type is needed when your module defines an implementation of the ProvideInherent trait.

This custom implementation is needed whenever your module wants to provide an inherent extrinsic and/or wants to verify an inherent extrinsic. If your module requires extra data for creating the inherent extrinsic, the data needs to be passed as InherentData into the runtime.

For example, in the Timestamp module, an inherent data is used to set the timestamp for a given block by creating an extrinsic. As a peer authority of the block author, we confirm that the time proposed in the inherent extrinsic is within an acceptable period from our clock.

impl<T: Trait> ProvideInherent for Module<T> {
    type Inherent = T::Moment;
    type Call = Call<T>;

    fn create_inherent_extrinsics(data: Self::Inherent) -> Vec<(u32, Self::Call)> {
        let next_time = ::rstd::cmp::max(data, Self::now() + Self::block_period());
        vec![(T::TIMESTAMP_SET_POSITION, Call::set(next_time.into()))]
    }

    fn check_inherent<Block: BlockT, F: Fn(&Block::Extrinsic) -> Option<&Self::Call>>(
            block: &Block, data: Self::Inherent, extract_function: &F
    ) -> result::Result<(), CheckInherentError> {
        const MAX_TIMESTAMP_DRIFT: u64 = 60;

        let xt = block.extrinsics().get(T::TIMESTAMP_SET_POSITION as usize)
            .ok_or_else(|| CheckInherentError::Other("No valid timestamp inherent in block".into()))?;

        let t = match (xt.is_signed(), extract_function(&xt)) {
            (Some(false), Some(Call::set(ref t))) => t.clone(),
            _ => return Err(CheckInherentError::Other("No valid timestamp inherent in block".into())),
        }.into().as_();

        let minimum = (Self::now() + Self::block_period()).as_();
        if t > data.as_() + MAX_TIMESTAMP_DRIFT {
            Err(CheckInherentError::Other("Timestamp too far in future to accept".into()))
        } else if t < minimum {
            Err(CheckInherentError::ValidAtTimestamp(minimum))
        } else {
            Ok(())
        }
    }
}

We will dig deeper into this topic [here] (Coming Soon).

construct_runtime!


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.