Skip to main content

Working with substrate pallet hooks

Substrate pallet hooks are a powerful set of tools substrate provides that facilitates automatic runtime execution when some condition is met.

Substrate pallet hooks enable runtime engineers to implement automatic runtime execution of arbitrary processes under deterministic conditions and with a verifiable guarantee of runtime safety.

In this guide, we will break down important concepts about substrate pallet hooks and dive into how hooks can be implemented and executed on a runtime.

Help us measure our progress and improve Substrate in Bits content by filling out our living feedback form. Thank you!

Reproducing setup

Environment and project setup

To follow this tutorial, ensure you have the Rust toolchain installed.

git clone https://github.com/cenwadike/double-auction-pallet
  • Navigate into the project’s directory.
cd double-auction
  • Run the command below to build the pallet.
cargo build --release

Getting some context

The setup below is a substrate pallet that implements double-auction for electrical energy.

At the end of an auction, the seller gets matched (and their electricity is sold) to the highest bidder.

Auctions to be executed are stored in AuctionsExecutionQueue. When an auction ends, it is taken from the AuctionsExecutionQueue and matched to the highest bidder.

Using the on_finalize hook, the runtime checks if any auction is over and executes the auction by calling on_auction_ended.

on_auction_ended is executed after all runtime extrinsic have been executed.

on_auction_ended is also executed within the constraints of the Weight assigned to it in on_initialize hook.

Understanding substrate pallet hooks

Substrate provides a highly extensible interface with a comprehensive set of runtime states to which arbitrary execution could be anchored.

This Hooks interface is implemented like so:

  pub trait Hooks<BlockNumber> {
// called at the very beginning of block execution.
fn on_initialize(_n: BlockNumber) -> Weight { ... }

// called at the very end of block execution.
// after all extrinsics have been executed.
// requires on_initialize to assign weight to dispatch.
fn on_finalize(_n: BlockNumber) { ... }

// consume a block’s idle time.
// after all extrinsics have been executed.
// run when the block is being finalized, before on_finalize.
fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight { ... }

// hooks for runtime upgrades.
fn on_runtime_upgrade() -> Weight { ... }
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> { ... }
fn post_upgrade(_state: Vec<u8>) -> Result<(), TryRuntimeError> { ... }

// hook sanity checks of a pallet, per block.
fn try_state(_n: BlockNumber) -> Result<(), TryRuntimeError> { ... }

// useful for anchoring off-chain workers.
fn offchain_worker(_n: BlockNumber) { ... }

// to check the integrity of a pallet’s configuration.
fn integrity_test() { ... }
}

For full Trait documentation, check the docs

We coupled substrate Hooks trait into our example pallet and implemented the on_initialize and on_finalize like so:

  #[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
fn on_initialize(_now: T::BlockNumber) -> Weight {
// Return weight required for on_finalize dispatch
T::WeightInfo::on_finalize(AuctionsExecutionQueue::<T>::iter_prefix(now).count() as u32)
}

fn on_finalize(now: T::BlockNumber) {
// Get auction ready for execution
for (auction_id, _) in AuctionsExecutionQueue::<T>::drain_prefix(now) {
if let Some(auction) = Auctions::<T>::take(auction_id) {
// handle auction execution
Self::on_auction_ended(auction.auction_id);
}
}
}
}

To view the full implementation details of substrate Hooks trait, check the docs

Going in-depth

This is merely an umbrella trait for traits that define each method in the Hooks trait. You can have a mental picture of this like so:

mod Hooks {
pub trait OnInitialize<BlockNumber> {
// Provided method
fn on_initialize(_n: BlockNumber) -> Weight { ... }
}

pub trait OnFinalize<BlockNumber> {
// Provided method
fn on_finalize(_n: BlockNumber) { ... }
}

// -------- snip -----------
}

When we implement a pallet hooks as shown below, we are leveraging substrate to hide the complexity of implementing multiple traits for our pallet:

  #[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
// -------- snip -----------
}

You could also use only the traits relevant to your pallet like so:

  #[pallet::hooks]
impl<T: Config> OnInitialize<T::BlockNumber> for Pallet<T> {
// -------- snip -----------
}

At execution, substrate runtime orchestrator implemented as frame_executive correctly dispatch a pallet's hook and execute any code.

frame_executive works in conjunction with the FRAME System module and serves as the main entry point for certain runtime actions including:

  • Check transaction validity.
  • Initialize a block.
  • Apply extrinsics.
  • Execute a block.
  • Finalize a block.
  • Start an off-chain worker.

In a nutshell, the frame_executive oversees the execution of hooks defined in our pallets.

Summary

We used a double auction pallet to demonstrate how to couple substrate hooks to a pallet. We explored substrate pallet hooks and developed an understanding of:

  • different methods exposed by substrate Hooks trait.
  • how to use substrate Hooks in a pallet.
  • how hooks are executed by frame-executive.

Substrate pallet hooks are a powerful and useful set of tools that can add dynamic runtime execution. Hooks are highly extensible for different use cases.

To learn more about substrate hooks, check out these resources:

Help us measure our progress and improve Substrate in Bits content by filling out our living feedback form. Thank you!

grillchat icon