From 98581810f31cf5210f5d0baa9cc87c277cd39989 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Mon, 17 Jul 2023 12:37:01 +0200 Subject: [PATCH] improve pallet hooks docs (#14578) * improve pallet hooks docs * Update frame/support/src/traits/hooks.rs Co-authored-by: Sam Johnson * Update frame/support/src/traits/hooks.rs Co-authored-by: Sam Johnson * Update frame/support/src/traits/hooks.rs Co-authored-by: Sam Johnson * fix mastekn removal * Apply suggestions from code review Co-authored-by: Juan * add diagram * fix all links * fix diagram * improve diagram with some notes * update --------- Co-authored-by: Sam Johnson Co-authored-by: parity-processbot <> Co-authored-by: Juan --- Cargo.lock | 34 ++++ frame/support/Cargo.toml | 2 + frame/support/src/traits/hooks.rs | 273 +++++++++++++++++------------- 3 files changed, 196 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b975b4c32b034..ba5b855db9a65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -273,6 +273,20 @@ dependencies = [ "num-traits", ] +[[package]] +name = "aquamarine" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df752953c49ce90719c7bf1fc587bc8227aed04732ea0c0f85e5397d7fdbd1a1" +dependencies = [ + "include_dir", + "itertools", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "arbitrary" version = "1.3.0" @@ -2801,6 +2815,7 @@ dependencies = [ name = "frame-support" version = "4.0.0-dev" dependencies = [ + "aquamarine", "array-bytes", "assert_matches", "bitflags", @@ -3694,6 +3709,25 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indexmap" version = "1.9.3" diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index bfcb821b8c8e4..f483c632a9698 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -41,6 +41,8 @@ sp-core-hashing-proc-macro = { version = "9.0.0", path = "../../primitives/core/ k256 = { version = "0.13.0", default-features = false, features = ["ecdsa"] } environmental = { version = "1.1.4", default-features = false } +aquamarine = { version = "0.3.2" } + [dev-dependencies] serde_json = "1.0.85" assert_matches = "1.3.0" diff --git a/frame/support/src/traits/hooks.rs b/frame/support/src/traits/hooks.rs index 58da1ad569929..b612363f42e3f 100644 --- a/frame/support/src/traits/hooks.rs +++ b/frame/support/src/traits/hooks.rs @@ -15,7 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Traits for hooking tasks to events in a blockchain's lifecycle. +//! Traits relating to pallet hooks. +//! +//! See [`Hooks`] as the main entry-point. + +#![deny(missing_docs)] use crate::weights::Weight; use impl_trait_for_tuples::impl_for_tuples; @@ -25,18 +29,9 @@ use sp_std::prelude::*; #[cfg(feature = "try-runtime")] use sp_runtime::TryRuntimeError; -/// The block initialization trait. -/// -/// Implementing this lets you express what should happen for your pallet when the block is -/// beginning (right before the first extrinsic is executed). +/// See [`Hooks::on_initialize`]. pub trait OnInitialize { - /// The block is being initialized. Implement to have something happen. - /// - /// Return the non-negotiable weight consumed in the block. - /// - /// NOTE: This function is called BEFORE ANY extrinsic in a block is applied, - /// including inherent extrinsics. Hence for instance, if you runtime includes - /// `pallet_timestamp`, the `timestamp` is not yet up to date at this point. + /// See [`Hooks::on_initialize`]. fn on_initialize(_n: BlockNumber) -> Weight { Weight::zero() } @@ -53,32 +48,18 @@ impl OnInitialize for Tuple { } } -/// The block finalization trait. -/// -/// Implementing this lets you express what should happen for your pallet when the block is ending. +/// See [`Hooks::on_finalize`]. #[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] #[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] #[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] pub trait OnFinalize { - /// The block is being finalized. Implement to have something happen. - /// - /// NOTE: This function is called AFTER ALL extrinsics in a block are applied, - /// including inherent extrinsics. + /// See [`Hooks::on_finalize`]. fn on_finalize(_n: BlockNumber) {} } -/// The block's on idle trait. -/// -/// Implementing this lets you express what should happen for your pallet before -/// block finalization (see `on_finalize` hook) in case any remaining weight is left. +/// See [`Hooks::on_idle`]. pub trait OnIdle { - /// The block is being finalized. - /// Implement to have something happen in case there is leftover weight. - /// Check the passed `remaining_weight` to make sure it is high enough to allow for - /// your pallet's extra computation. - /// - /// NOTE: This function is called AFTER ALL extrinsics - including inherent extrinsics - - /// in a block are applied but before `on_finalize` is executed. + /// See [`Hooks::on_idle`]. fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight { Weight::zero() } @@ -118,26 +99,14 @@ pub trait OnGenesis { fn on_genesis() {} } -/// The runtime upgrade trait. -/// -/// Implementing this lets you express what should happen when the runtime upgrades, -/// and changes may need to occur to your module. +/// See [`Hooks::on_runtime_upgrade`]. pub trait OnRuntimeUpgrade { - /// Perform a module upgrade. - /// - /// # Warning - /// - /// This function will be called before we initialized any runtime state, aka `on_initialize` - /// wasn't called yet. So, information like the block number and any other - /// block local data are not accessible. - /// - /// Return the non-negotiable weight consumed for runtime upgrade. + /// See [`Hooks::on_runtime_upgrade`]. fn on_runtime_upgrade() -> Weight { Weight::zero() } - /// Same as `on_runtime_upgrade`, but perform the optional `pre_upgrade` and `post_upgrade` as - /// well. + /// See [`Hooks::on_runtime_upgrade`]. #[cfg(feature = "try-runtime")] fn try_on_runtime_upgrade(checks: bool) -> Result { let maybe_state = if checks { @@ -159,31 +128,13 @@ pub trait OnRuntimeUpgrade { Ok(weight) } - /// Execute some pre-checks prior to a runtime upgrade. - /// - /// Return a `Vec` that can contain arbitrary encoded data (usually some pre-upgrade state), - /// which will be passed to `post_upgrade` after upgrading for post-check. An empty vector - /// should be returned if there is no such need. - /// - /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. - /// - /// This hook must not write to any state, as it would make the main `on_runtime_upgrade` path - /// inaccurate. + /// See [`Hooks::on_runtime_upgrade`]. #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { Ok(Vec::new()) } - /// Execute some post-checks after a runtime upgrade. - /// - /// The `state` parameter is the `Vec` returned by `pre_upgrade` before upgrading, which - /// can be used for post-check. NOTE: if `pre_upgrade` is not implemented an empty vector will - /// be passed in, in such case `post_upgrade` should ignore it. - /// - /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. - /// - /// This hook must not write to any state, as it would make the main `on_runtime_upgrade` path - /// inaccurate. + /// See [`Hooks::on_runtime_upgrade`]. #[cfg(feature = "try-runtime")] fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { Ok(()) @@ -200,9 +151,9 @@ impl OnRuntimeUpgrade for Tuple { weight } - /// We are executing pre- and post-checks sequentially in order to be able to test several - /// consecutive migrations for the same pallet without errors. Therefore pre and post upgrade - /// hooks for tuples are a noop. + // We are executing pre- and post-checks sequentially in order to be able to test several + // consecutive migrations for the same pallet without errors. Therefore pre and post upgrade + // hooks for tuples are a noop. #[cfg(feature = "try-runtime")] fn try_on_runtime_upgrade(checks: bool) -> Result { let mut weight = Weight::zero(); @@ -239,64 +190,150 @@ impl OnRuntimeUpgrade for Tuple { } } -/// Type that provide some integrity tests. -/// -/// This implemented for modules by `decl_module`. +/// See [`Hooks::integrity_test`]. #[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] #[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] #[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] pub trait IntegrityTest { - /// Run integrity test. - /// - /// The test is not executed in a externalities provided environment. + /// See [`Hooks::integrity_test`]. fn integrity_test() {} } -/// The pallet hooks trait. Implementing this lets you express some logic to execute. +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The pallet hooks trait. This is merely an umbrella trait for: +/// +/// - [`OnInitialize`] +/// - [`OnFinalize`] +/// - [`OnRuntimeUpgrade`] +/// - [`crate::traits::misc::OffchainWorker`] +/// - [`OnIdle`] +/// - [`IntegrityTest`] +/// +/// ## Ordering +/// +/// For all hooks, except [`OnIdle`] the order of execution is derived from how the pallets are +/// ordered in [`crate::construct_runtime`]. +/// +/// ## Summary +/// +/// In short, the following diagram shows the flow of hooks in a pallet +/// +/// ```mermaid +/// graph LR +/// Optional --> BeforeExtrinsics +/// BeforeExtrinsics --> Extrinsics +/// Extrinsics --> AfterExtrinsics +/// subgraph Optional +/// OnRuntimeUpgrade +/// end +/// +/// subgraph BeforeExtrinsics +/// OnInitialize +/// end +/// +/// subgraph Extrinsics +/// direction TB +/// Inherent1 +/// Inherent2 +/// Extrinsic1 +/// Extrinsic2 +/// +/// Inherent1 --> Inherent2 +/// Inherent2 --> Extrinsic1 +/// Extrinsic1 --> Extrinsic2 +/// end +/// +/// subgraph AfterExtrinsics +/// OnIdle +/// OnFinalize +/// +/// OnIdle --> OnFinalize +/// end +/// ``` +/// +/// * `OnRuntimeUpgrade` is only executed before everything else if a code +/// * `OnRuntimeUpgrade` is mandatorily at the beginning of the block body (extrinsics) being +/// processed. change is detected. +/// * Extrinsics start with inherents, and continue with other signed or unsigned extrinsics. +/// * `OnIdle` optionally comes after extrinsics. +/// `OnFinalize` mandatorily comes after `OnIdle`. +/// +/// > `OffchainWorker` is not part of this flow, as it is not really part of the consensus/main +/// > block import path, and is called optionally, and in other circumstances. See +/// > [`crate::traits::misc::OffchainWorker`] for more information. +/// +/// To learn more about the execution of hooks see `frame-executive` as this component is is charge +/// of dispatching extrinsics and placing the hooks in the correct order. pub trait Hooks { - /// The block is being finalized. Implement to have something happen. + /// Block initialization hook. This is called at the very beginning of block execution. + /// + /// Must return the non-negotiable weight of both itself and whatever [`Hooks::on_finalize`] + /// wishes to consume. + /// + /// The weight returned by this is treated as `DispatchClass::Mandatory`, meaning that + /// it MUST BE EXECUTED. If this is not the case, consider using [`Hooks::on_idle`] instead. + /// + /// NOTE: This function is called BEFORE ANY extrinsic in a block is applied, including inherent + /// extrinsics. Hence for instance, if you runtime includes `pallet-timestamp`, the `timestamp` + /// is not yet up to date at this point. + fn on_initialize(_n: BlockNumber) -> Weight { + Weight::zero() + } + + /// Block finalization hook. This is called at the very end of block execution. + /// + /// Note that this has nothing to do with finality in the "consensus" sense. + /// + /// Note that the non-negotiable weight for this has must have already been returned by + /// [`Hooks::on_initialize`]. It usage alone is not permitted. + /// + /// Similar to [`Hooks::on_initialize`] it should only be used when execution is absolutely + /// necessary. In other cases, consider using [`Hooks::on_idle`] instead. fn on_finalize(_n: BlockNumber) {} - /// This will be run when the block is being finalized (before `on_finalize`). + /// Hook to consume a block's idle time. This will run when the block is being finalized (before + /// [`Hooks::on_finalize`]). /// - /// Implement to have something happen using the remaining weight. Will not fire if the - /// remaining weight is 0. + /// Given that all dispatchables are already executed and noted (and the weight for + /// [`Hooks::on_finalize`], which comes next, is also already accounted for via + /// `on_initialize`), this hook consumes anything that is leftover. /// /// Each pallet's `on_idle` is chosen to be the first to execute in a round-robin fashion /// indexed by the block number. /// /// Return the weight used, the caller will use this to calculate the remaining weight and then /// call the next pallet `on_idle` hook if there is still weight left. + /// + /// Any implementation should always respect `_remaining_weight` and never consume (and + /// therefore return) more than this amount. fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight { Weight::zero() } - /// The block is being initialized. Implement to have something happen. + /// Hook executed when a code change (aka. a "runtime upgrade") is detected by FRAME. /// - /// Return the non-negotiable weight consumed in the block. - fn on_initialize(_n: BlockNumber) -> Weight { - Weight::zero() - } - - /// Perform a module upgrade. + /// Be aware that this is called before [`Hooks::on_initialize`] of any pallet; therefore, a lot + /// of the critical storage items such as `block_number` in system pallet might have not been + /// set. /// - /// NOTE: this doesn't include all pallet logic triggered on runtime upgrade. For instance it - /// doesn't include the write of the pallet version in storage. The final complete logic - /// triggered on runtime upgrade is given by implementation of `OnRuntimeUpgrade` trait by - /// `Pallet`. + /// Vert similar to [`Hooks::on_initialize`], any code in this block is mandatory and MUST + /// execute. Use with care. /// - /// # Warning + /// ## Implementation Note: Versioning /// - /// This function will be called before we initialized any runtime state, aka `on_initialize` - /// wasn't called yet. So, information like the block number and any other block local data are - /// not accessible. + /// 1. An implementation of this should typically follow a pattern where the version of the + /// pallet is checked against the onchain version, and a decision is made about what needs to be + /// done. This is helpful to prevent accidental repetitive execution of this hook, which can be + /// catastrophic. /// - /// Return the non-negotiable weight consumed for runtime upgrade. + /// Alternatively, `migrations::VersionedRuntimeUpgrade` can be used to assist with + /// this. /// - /// While this function can be freely implemented, using `on_runtime_upgrade` from inside the - /// pallet is discouraged and might get deprecated in the future. Alternatively, export the same - /// logic as a free-function from your pallet, and pass it to `type Executive` from the - /// top-level runtime. + /// ## Implementation Note: Runtime Level Migration + /// + /// Additional "upgrade hooks" can be created by pallets by a manual implementation of + /// [`Hooks::on_runtime_upgrade`] which can be passed on to `Executive` at the top level + /// runtime. fn on_runtime_upgrade() -> Weight { Weight::zero() } @@ -336,26 +373,36 @@ pub trait Hooks { Ok(()) } - /// Implementing this function on a module allows you to perform long-running tasks - /// that make (by default) validators generate transactions that feed results - /// of those long-running computations back on chain. + /// Implementing this function on a pallet allows you to perform long-running tasks that are + /// dispatched as separate threads, and entirely independent of the main wasm runtime. /// - /// NOTE: This function runs off-chain, so it can access the block state, - /// but cannot preform any alterations. More specifically alterations are - /// not forbidden, but they are not persisted in any way after the worker - /// has finished. + /// This function can freely read from the state, but any change it makes to the state is + /// meaningless. Writes can be pushed back to the chain by submitting extrinsics from the + /// offchain worker to the transaction pool. See `pallet-example-offchain-worker` for more + /// details on this. /// - /// This function is being called after every block import (when fully synced). + /// Moreover, the code in this function has access to a wider range of host functions in + /// [`sp-io`], namely [`sp_io::offchain`]. This includes exotic operations such as HTTP calls + /// that are not really possible in the rest of the runtime code. /// - /// Implement this and use any of the `Offchain` `sp_io` set of APIs - /// to perform off-chain computations, calls and submit transactions - /// with results to trigger any on-chain changes. - /// Any state alterations are lost and are not persisted. + /// The execution of this hook is entirely optional and is left at the discretion of the + /// node-side software and its configuration. In a normal substrate-cli, look for the CLI + /// flags related to offchain-workers to learn more. fn offchain_worker(_n: BlockNumber) {} - /// Run integrity test. + /// Check the integrity of this pallet's configuration. + /// + /// Any code located in this hook is placed in an auto-generated test, and generated as a part + /// of [`crate::construct_runtime`]'s expansion. Look for a test case with a name along the + /// lines of: `__construct_runtime_integrity_test`. + /// + /// This hook is the location where the values/types provided to the `Config` trait + /// of the pallet can be tested for correctness. For example, if two `type Foo: Get` and + /// `type Bar: Get` where `Foo::get()` must always be greater than `Bar::get()`, such + /// checks can be asserted upon here. /// - /// The test is not executed in a externalities provided environment. + /// Note that this hook is not executed in an externality environment, so if access to state is + /// needed, the code should be wrapped in `sp_io::TestExternalities`. fn integrity_test() {} }