Files
pezkuwi-subxt/substrate/frame/benchmarking
Andrew Jones 49b6dfd2e5 Enrich metadata with type information (#8615)
* Cargo.lock after merge

* Restore scale-info feature

* Fully qualify TypeInfo derive

* Skip PendingSwap T

* Add missing skip_type_params attr

* metadata docs features

* Reduce pallet event attribute to struct

* Cargo.lock

* Update frame/balances/src/tests_composite.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Line widths check

* Cargo.lock

* Add scale-info/std

* Update frame/system/src/lib.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Use `skip_type_params` to remove `TypeInfo` requirements on checks

* Revert "Remove unused Call metadata stuff"

This reverts commit 41311f85

* Skip BalanceSwapAction type parameter

* Remove unused event metadata macro

* Update frame-metadata

* Update primitives/npos-elections/compact/src/codec.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Manual TypeInfo for Header

* Remove TypeInfo requirement for consts in BoundedVec etc.

* Another TypeInfo bound removed

* review: fix indentation

* TypeInfo impls for Identity types

* Add some todos to add custom TypeInfo impls

* Update frame/support/procedural/src/pallet/expand/pallet_struct.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Add some todos to add custom TypeInfo impls

* Add a test for manual Data TypeInfo impl

* Add custom TypeInfo impl for Vote

* Era custom TypeInfo crimes

* Revert finality-grandpa version to 0.14.z

* review: renamed module to pallet_constants_metadata

* New line at end of file

* Add missing scale-info/std

* Update frame/support/src/storage/types/mod.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Remove StorageEntryType::Map unused flag

* Add missing scale-info dependency after merge

* SignedExtension::AdditionalSigned metadata

* Update frame-metadata, use abbreviated docs and args fields

* Update frame/example/Cargo.toml

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* Add scale_info/std and remove unused scale-info dependency

* Remove scale-info dependency

* Remove treasury pallet::metadata

* Remove redundant Event test

* Add back scale-info as dev dependency

* fix error metadata when no error defined in decl_module

* Add Module3 to tests

* Fix metadata test

* Add docs feature to frame-support test

* WIP fixing pallet metadata test

* Remove redundant FunctionMetadata, FunctionArgumentMetadata as per https://github.com/paritytech/frame-metadata/pull/20

* Use main branch of frame-metadata

* Use patch of scale-info for latest changes

* Use latest patched scale-info

* Manual TypeInfo for DigestItem

* Manual TypeInfo for DigestItem

* Update scale-info

* Skip __Ignore variants for Error, depends on https://github.com/paritytech/scale-info/pull/117

* Named fields for FRAME v2 pallet Call variants

* Named fields for FRAME v1 pallet Call variants

* Add missing scale-info dependency

* WIP expand benchmark call variant

* fix benchmark with new function

create a new function for each variant of a pallet call.
This function is called by benchmarking macro in order not to break call
creation with unnamed argument

* fix tests

* more fix

* Fix staking tests

* Fix offchain workers calls

* Cherry pick rustfmt.toml from master

* cargo +nightly-2021-06-22 fmt --all

* Update to new call variant structs

* More call variant struct updates

* Remove unused import

* More call variant structs

* More call variant structs

* Even more call variant structs

* Mooar variant structs

* Evermore variant structs

* Call variant structs ad infinitum

* Fmt

* More call variants

* Last call variant

* Call variants all done?

* Fix SS58Prefix type

* Potential workaround for BitFlags<IdentityFields> TypeInfo

* Enable docs capturing for Call, Event, and Error types

* Fix IdentityFields TypeInfo

* Remove metadata-docs feature

* Add capture_docs = true for legacy Call, Event and Error types

* Fmt

* Fix metadata test type

* Update benchmarks with call struct variants

* Fmt

* More test fixes

* Fmt

* Fix benches

* Use latest capture_docs attr

* Latest scale_info

* Fmt

* review: change &Vec to &[]

* Remove pallet metadata attr

* review: remove commented out test code

* review: skip_type_params trailing comma suggestion

* Update to scale-info 0.10.0

* Update construct_runtime ui tests, different because of metadata TypeInfo impls

* Add some TypeInfo derives for UI tests

* Update storage ensure span ui stderrs

* Update call argument bound ui tests

Possibly changed because change from tuple to struct variants?

* Add scale-info dev dependency

* Update to latest finality-grandpa release

* review: missing newline

* review: missing scale-info/std

* review: remove duplicate scale-info/std

* review: remove fully qualified TypeInfo

* review: add missing scale-info/std

* review: remove unnecessary imports.

* Fmt

* Use crates.io RC version of frame-metadata

* Remove scale-info/std because it is a dev dependency

* Add missing scale_info dev-dependency for test

* Delete empty metadata folder

* Fix sp_std import

* review: improve manual UncheckedExtrinsic TypeInfo impl

* review: use full scale-info for dev-dependency

* Remove DefaultByteGetter impl

* review: derive TypeInfo for generic header

* Fmt

* Update primitives/runtime/src/generic/unchecked_extrinsic.rs

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* Update primitives/runtime/src/generic/unchecked_extrinsic.rs

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* Update bin/node/executor/Cargo.toml

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update frame/identity/src/types.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update frame/support/src/dispatch.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Remove redundant derive

* Simplify scale-info dependency

* Strip underscore prefix from call variant struct names

* Another underscore field

* More underscore fields

* Another underscore field

* Update to frame-metadata 14.0.0-rc.2 with combined StorageEntryType::Map

* Fmt

* Revert weights formatting

* Fix up some tests

* Fix up some tests for StorageEntryTypeMetadata

* scale-info dev dependency

* Fix test error

* Add missing TypeInfo derives

* Add back missing scale-info dependency

* Add back missing scale-info dependency

* Fix npos compact impls

* Cargo.lock

* Fmt

* Fix errors

* Fmt

* Fix renamed raw_solution field

* Fix error

* Fmt

* Fix some benchmarks

* Fmt

* Stray R

* Fix

* Add missing TypeInfos

* ui test fix

* Fix line widths

* Revert "ui test fix"

This reverts commit 2d15ec058a216e3f92d713f1174603a2bb1eac65.

* Upgrade to scale-info 0.11.0

* Revert "Upgrade to scale-info 0.11.0"

This reverts commit 047bb179085a0059c36cd20ab405f55cf0867e28.

* Add Runtime type

* Update to scale-info 0.12

* Update to scale-info 1.0

* Update frame-metadata to version 14.0.0

* Patch finality-grandpa until release available

* Fix metadata tests

* Fix metadata tests

* Fmt

* Remove patched finality-grandpa

* Fix tests, use scale_info imports

* Fix pallet tests

* Add BlockNumber TypeInfo bound

* ui test fix

* Cargo.lock

* Remove pallet metadata

* Cargo.lock

* Add missing scale-info dependency

* Remove pallet event metadata

* Fix error

* Fix collective errors

* Semicolol

* Fmt

* Remove another metadata attribute

* Add new variant to custom digest TypeInfo

* Fmt

* Cargo.lock from master

* Remove comma lol

* Fix example call error

* Fix example call error properly

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
2021-09-15 11:40:41 +00:00
..
2020-11-05 19:18:55 +01:00

Substrate Runtime Benchmarking Framework

This crate contains a set of utilities that can be used to benchmark and weigh FRAME pallets that you develop for your Substrate Runtime.

Overview

Substrate's FRAME framework allows you to develop custom logic for your blockchain that can be included in your runtime. This flexibility is key to help you design complex and interactive pallets, but without accurate weights assigned to dispatchables, your blockchain may become vulnerable to denial of service (DoS) attacks by malicious actors.

The Substrate Runtime Benchmarking Framework is a tool you can use to mitigate DoS attacks against your blockchain network by benchmarking the computational resources required to execute different functions in the runtime, for example extrinsics, on_initialize, verify_unsigned, etc...

The general philosophy behind the benchmarking system is: If your node can know ahead of time how long it will take to execute an extrinsic, it can safely make decisions to include or exclude that extrinsic based on its available resources. By doing this, it can keep the block production and import process running smoothly.

To achieve this, we need to model how long it takes to run each function in the runtime by:

  • Creating custom benchmarking logic that executes a specific code path of a function.
  • Executing the benchmark in the Wasm execution environment, on a specific set of hardware, with a custom runtime configuration, etc...
  • Executing the benchmark across controlled ranges of possible values that may affect the result of the benchmark (called "components").
  • Executing the benchmark multiple times at each point in order to isolate and remove outliers.
  • Using the results of the benchmark to create a linear model of the function across its components.

With this linear model, we are able to estimate ahead of time how long it takes to execute some logic, and thus make informed decisions without actually spending any significant resources at runtime.

Note that we assume that all extrinsics are assumed to be of linear complexity, which is why we are able to always fit them to a linear model. Quadratic or higher complexity functions are, in general, considered to be dangerous to the runtime as the weight of these functions may explode as the runtime state or input becomes too complex.

The benchmarking framework comes with the following tools:

The end-to-end benchmarking pipeline is disabled by default when compiling a node. If you want to run benchmarks, you need to enable it by compiling with a Rust feature flag runtime-benchmarks. More details about this below.

Weight

Substrate represents computational resources using a generic unit of measurement called "Weight". It defines 10^12 Weight as 1 second of computation on the physical machine used for benchmarking. This means that the weight of a function may change based on the specific hardware used to benchmark the runtime functions.

By modeling the expected weight of each runtime function, the blockchain is able to calculate how many transactions or system level functions it will be able to execute within a certain period of time. Often, the limiting factor for a blockchain is the fixed block production time for the network.

Within FRAME, each dispatchable function must have a #[weight] annotation with a function that can return the expected weight for the worst case scenario execution of that function given its inputs. This benchmarking framework will result in a file that automatically generates those formulas for you, which you can then use in your pallet.

Writing Benchmarks

Writing a runtime benchmark is much like writing a unit test for your pallet. It needs to be carefully crafted to execute a certain logical path in your code. In tests you want to check for various success and failure conditions, but with benchmarks you specifically look for the most computationally heavy path, a.k.a the "worst case scenario".

This means that if there are certain storage items or runtime state that may affect the complexity of the function, for example triggering more iterations in a for loop, to get an accurate result, you must set up your benchmark to trigger this.

It may be that there are multiple paths your function can go down, and it is not clear which one is the heaviest. In this case, you should just create a benchmark for each scenario! You may find that there are paths in your code where complexity may become unbounded depending on user input. This may be a hint that you should enforce sane boundaries for how a user can use your pallet. For example: limiting the number of elements in a vector, limiting the number of iterations in a for loop, etc...

Examples of end-to-end benchmarks can be found in the pallets provided by Substrate, and the specific details on how to use the benchmarks! macro can be found in its documentation.

Testing Benchmarks

You can test your benchmarks using the same test runtime that you created for your pallet's unit tests. By creating your benchmarks in the benchmarks! macro, it automatically generates test functions for you:

fn test_benchmark_[benchmark_name]<T>::() -> Result<(), &'static str>

Simply add these functions to a unit test and ensure that the result of the function is Ok(()).

Note: If your test runtime and production runtime have different configurations, you may get different results when testing your benchmark and actually running it.

In general, benchmarks returning Ok(()) is all you need to check for since it signals the executed extrinsic has completed successfully. However, you can optionally include a verify block with your benchmark, which can additionally verify any final conditions, such as the final state of your runtime.

These additional verify blocks will not affect the results of your final benchmarking process.

To run the tests, you need to enable the runtime-benchmarks feature flag. This may also mean you need to move into your node's binary folder. For example, with the Substrate repository, this is how you would test the Balances pallet's benchmarks:

cargo test -p pallet-balances --features runtime-benchmarks

NOTE: Substrate uses a virtual workspace which does not allow you to compile with feature flags.

error: --features is not allowed in the root of a virtual workspace`

To solve this, navigate to the folder of the node (cd bin/node/cli) or pallet (cd frame/pallet) and run the command there.

Adding Benchmarks

The benchmarks included with each pallet are not automatically added to your node. To actually execute these benchmarks, you need to implement the frame_benchmarking::Benchmark trait. You can see an example of how to do this in the included Substrate node.

Assuming there are already some benchmarks set up on your node, you just need to add another instance of the add_benchmark! macro:

///  configuration for running benchmarks
///               |    name of your pallet's crate (as imported)
///               v                   v
add_benchmark!(params, batches, pallet_balances, Balances);
///                       ^                          ^
///    where all benchmark results are saved         |
///            the `struct` created for your pallet by `construct_runtime!`

Once you have done this, you will need to compile your node binary with the runtime-benchmarks feature flag:

cd bin/node/cli
cargo build --release --features runtime-benchmarks

Running Benchmarks

Finally, once you have a node binary with benchmarks enabled, you need to execute your various benchmarks.

You can get a list of the available benchmarks by running:

./target/release/substrate benchmark --chain dev --pallet "*" --extrinsic "*" --repeat 0

Then you can run a benchmark like so:

./target/release/substrate benchmark \
    --chain dev \                  # Configurable Chain Spec
    --execution=wasm \             # Always test with Wasm
    --wasm-execution=compiled \    # Always used `wasm-time`
    --pallet pallet_balances \     # Select the pallet
    --extrinsic transfer \         # Select the extrinsic
    --steps 50 \                   # Number of samples across component ranges
    --repeat 20 \                  # Number of times we repeat a benchmark
    --output <path> \              # Output benchmark results into a folder or file

This will output a file pallet_name.rs which implements the WeightInfo trait you should include in your pallet. Each blockchain should generate their own benchmark file with their custom implementation of the WeightInfo trait. This means that you will be able to use these modular Substrate pallets while still keeping your network safe for your specific configuration and requirements.

The benchmarking CLI uses a Handlebars template to format the final output file. You can optionally pass the flag --template pointing to a custom template that can be used instead. Within the template, you have access to all the data provided by the TemplateData struct in the benchmarking CLI writer. You can find the default template used here.

There are some custom Handlebars helpers included with our output generation:

  • underscore: Add an underscore to every 3rd character from the right of a string. Primarily to be used for delimiting large numbers.
  • join: Join an array of strings into a space-separated string for the template. Primarily to be used for joining all the arguments passed to the CLI.

To get a full list of available options when running benchmarks, run:

./target/release/substrate benchmark --help

License: Apache-2.0