#
pezkuwi_sdk_docs/reference_docs/
pezframe_benchmarking_weight.rs

    1//! # FRAME Benchmarking and Weights.
    2//!
    3//! This reference doc explores the concept of weights within Pezkuwi-SDK runtimes, and more
    4//! specifically how FRAME-based runtimes handle it.
    5//!
    6//! ## Metering
    7//!
    8//! The existence of "weight" as a concept in Pezkuwi-SDK is a direct consequence of the usage of
    9//! WASM as a virtual machine. Unlike a metered virtual machine like EVM, where every instruction
    10//! can have a (fairly) deterministic "cost" (also known as "gas price") associated with it, WASM is
    11//! a stack machine with more complex instruction set, and more unpredictable execution times. This
    12//! means that unlike EVM, it is not possible to implement a "metering" system in WASM. A metering
    13//! system is one in which instructions are executed one by one, and the cost/gas is stored in an
    14//! accumulator. The execution may then halt once a gas limit is reached.
    15//!
    16//! In Pezkuwi-SDK, the WASM runtime is not assumed to be metered.
    17//!
    18//! ## Trusted Code
    19//!
    20//! Another important difference is that EVM is mostly used to express smart contracts, which are
    21//! foreign and untrusted codes from the perspective of the blockchain executing them. In such
    22//! cases, metering is crucial, in order to ensure a malicious code cannot consume more gas than
    23//! expected.
    24//!
    25//! This assumption does not hold about the runtime of Pezkuwi-SDK-based blockchains. The runtime
    26//! is trusted code, and it is assumed to be written by the same team/developers who are running the
    27//! blockchain itself. Therefore, this assumption of "untrusted foreign code" does not hold.
    28//!
    29//! This is why the runtime can opt for a more performant, more flexible virtual machine like WASM,
    30//! and get away without having metering.
    31//!
    32//! ## Benchmarking
    33//!
    34//! With the matter of untrusted code execution out of the way, the need for strict metering goes
    35//! out of the way. Yet, it would still be very beneficial for block producers to be able to know an
    36//! upper bound on how much resources a operation is going to consume before actually executing that
    37//! operation. This is why FRAME has a toolkit for benchmarking pallets: So that this upper bound
    38//! can be empirically determined.
    39//!
    40//! > Note: Benchmarking is a static analysis: It is all about knowing the upper bound of how much
    41//! > resources an operation takes statically, without actually executing it. In the context of
    42//! > FRAME extrinsics, this static-ness is expressed by the keyword "pre-dispatch".
    43//!
    44//! To understand why this upper bound is needed, consider the following: A block producer knows
    45//! they have 20ms left to finish producing their block, and wishes to include more transactions in
    46//! the block. Yet, in a metered environment, it would not know which transaction is likely to fit
    47//! the 20ms. In a benchmarked environment, it can examine the transactions for their upper bound,
    48//! and include the ones that are known to fit based on the worst case.
    49//!
    50//! The benchmarking code can be written as a part of FRAME pezpallet, using the macros provided in
    51//! [`pezframe_benchmarking`]. See any of the existing pallets in `pezkuwi-sdk`, or the pallets in
    52//! our [`crate::pezkuwi_sdk::templates`] for examples.
    53//!
    54//! ## Weight
    55//!
    56//! Finally, [`pezsp_weights::Weight`] is the output of the benchmarking process. It is a
    57//! two-dimensional data structure that demonstrates the resources consumed by a given block of
    58//! code (for example, a transaction). The two dimensions are:
    59//!
    60//! * reference time: The time consumed in pico-seconds, on a reference hardware.
    61//! * proof size: The amount of storage proof necessary to re-execute the block of code. This is
    62//!   mainly needed for teyrchain <> relay-chain verification.
    63//!
    64//! ## How To Write Benchmarks: Worst Case
    65//!
    66//! The most important detail about writing benchmarking code is that it must be written such that
    67//! it captures the worst case execution of any block of code.
    68//!
    69//! Consider:
    70#![doc = docify::embed!("./src/reference_docs/pezframe_benchmarking_weight.rs", simple_transfer)]
    71//!
    72//! If this block of code is to be benchmarked, then the benchmarking code must be written such that
    73//! it captures the worst case.
    74//!
    75//! ## Gluing Pezpallet Benchmarking with Runtime
    76//!
    77//! FRAME pallets are mandated to provide their own benchmarking code. Runtimes contain the
    78//! boilerplate needed to run these benchmarking (see [Running Benchmarks
    79//! below](#running-benchmarks)). The outcome of running these benchmarks are meant to be fed back
    80//! into the pezpallet via a conventional `trait WeightInfo` on `Config`:
    81#![doc = docify::embed!("src/reference_docs/pezframe_benchmarking_weight.rs", WeightInfo)]
    82//!
    83//! Then, individual functions of this trait are the final values that we assigned to the
    84//! [`pezframe::pezpallet_macros::weight`] attribute:
    85#![doc = docify::embed!("./src/reference_docs/pezframe_benchmarking_weight.rs", simple_transfer_2)]
    86//!
    87//! ## Manual Refund
    88//!
    89//! Back to the assumption of writing benchmarks for worst case: Sometimes, the pre-dispatch weight
    90//! significantly differ from the post-dispatch actual weight consumed. This can be expressed with
    91//! the following FRAME syntax:
    92#![doc = docify::embed!("./src/reference_docs/pezframe_benchmarking_weight.rs", simple_transfer_3)]
    93//!
    94//! ## Running Benchmarks
    95//!
    96//! Two ways exist to run the benchmarks of a runtime.
    97//!
    98//! 1. The old school way: Most Pezkuwi-SDK based nodes (such as the ones integrated in
    99//!    [`templates`]) have a `benchmark` subcommand integrated into themselves.
    100//! 2. The more [`crate::reference_docs::omni_node`] compatible way of running the benchmarks would
    101//!    be using [`frame-omni-bencher`] CLI, which only relies on a runtime.
    102//!
    103//! Note that by convention, the runtime and pallets always have their benchmarking code feature
    104//! gated as behind `runtime-benchmarks`. So, the runtime should be compiled with `--features
    105//! runtime-benchmarks`.
    106//!
    107//! ## Automatic Refund of `proof_size`.
    108//!
    109//! A new feature in FRAME allows the runtime to be configured for "automatic refund" of the proof
    110//! size weight. This is very useful for maximizing the throughput of teyrchains. Please see:
    111//! [`crate::guides::enable_pov_reclaim`].
    112//!
    113//! ## Summary
    114//!
    115//! Pezkuwi-SDK runtimes use a more performant VM, namely WASM, which does not have metering. In
    116//! return they have to be benchmarked to provide an upper bound on the resources they consume. This
    117//! upper bound is represented as [`pezsp_weights::Weight`].
    118//!
    119//! ## Future: PolkaVM
    120//!
    121//! With the transition of Pezkuwi relay chain to [JAM], a set of new features are being
    122//! introduced, one of which being a new virtual machine named [PolkaVM] that is as flexible as
    123//! WASM, but also capable of metering. This might alter the future of benchmarking in FRAME and
    124//! Pezkuwi-SDK, rendering them not needed anymore once PolkaVM is fully integrated into
    125//! Pezkuwi-sdk. For a basic explanation of JAM and PolkaVM, see [here](https://blog.kianenigma.com/posts/tech/demystifying-jam/#pvm).
    126//!
    127//!
    128//! [`frame-omni-bencher`]: https://crates.io/crates/frame-omni-bencher
    129//! [`templates`]: crate::pezkuwi_sdk::templates
    130//! [PolkaVM]: https://github.com/koute/polkavm
    131//! [JAM]: https://graypaper.com
    132
    133#[pezframe::pezpallet(dev_mode)]
    134#[allow(unused_variables, unreachable_code, unused, clippy::diverging_sub_expression)]
    135pub mod pezpallet {
    136	use pezframe::prelude::*;
    137
    138	#[docify::export]
    139	pub trait WeightInfo {
    140		fn simple_transfer() -> Weight;
    141	}
    142
    143	#[pezpallet::config]
    144	pub trait Config: pezframe_system::Config {
    145		type WeightInfo: WeightInfo;
    146	}
    147
    148	#[pezpallet::pezpallet]
    149	pub struct Pezpallet<T>(_);
    150
    151	#[pezpallet::call]
    152	impl<T: Config> Pezpallet<T> {
    153		#[docify::export]
    154		#[pezpallet::weight(10_000)]
    155		pub fn simple_transfer(
    156			origin: OriginFor<T>,
    157			destination: T::AccountId,
    158			amount: u32,
    159		) -> DispatchResult {
    160			let destination_exists = todo!();
    161			if destination_exists {
    162				// simpler code path
    163			} else {
    164				// more complex code path
    165			}
    166			Ok(())
    167		}
    168
    169		#[docify::export]
    170		#[pezpallet::weight(T::WeightInfo::simple_transfer())]
    171		pub fn simple_transfer_2(
    172			origin: OriginFor<T>,
    173			destination: T::AccountId,
    174			amount: u32,
    175		) -> DispatchResult {
    176			let destination_exists = todo!();
    177			if destination_exists {
    178				// simpler code path
    179			} else {
    180				// more complex code path
    181			}
    182			Ok(())
    183		}
    184
    185		#[docify::export]
    186		// This is the worst-case, pre-dispatch weight.
    187		#[pezpallet::weight(T::WeightInfo::simple_transfer())]
    188		pub fn simple_transfer_3(
    189			origin: OriginFor<T>,
    190			destination: T::AccountId,
    191			amount: u32,
    192		) -> DispatchResultWithPostInfo {
    193			// ^^ Notice the new return type
    194			let destination_exists = todo!();
    195			if destination_exists {
    196				// simpler code path
    197				// Note that need for .into(), to convert `()` to `PostDispatchInfo`
    198				// See: https://docs.pezkuwichain.io/sdk/master/pezframe_support/dispatch/struct.PostDispatchInfo.html#impl-From%3C()%3E-for-PostDispatchInfo
    199				Ok(().into())
    200			} else {
    201				// more complex code path
    202				let actual_weight =
    203					todo!("this can likely come from another benchmark that is NOT the worst case");
    204				let pays_fee = todo!("You can set this to `Pays::Yes` or `Pays::No` to change if this transaction should pay fees");
    205				Ok(pezframe::deps::pezframe_support::dispatch::PostDispatchInfo {
    206					actual_weight: Some(actual_weight),
    207					pays_fee,
    208				})
    209			}
    210		}
    211	}
    212}