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.
-
Visit the substrate official documentation page for the installation processes.
-
Clone the project repository.
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!