Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Add special tag to exclude runtime storage items from benchmarking #12205

Merged
merged 72 commits into from
Sep 16, 2022

Conversation

sam0x17
Copy link
Contributor

@sam0x17 sam0x17 commented Sep 7, 2022

Description

The Substrate runtime takes advantage of a storage cache where the first read of a storage item places an item in the cache, and subsequent reads of that item happen in memory.

There are some storage items which we know will be accessed every block, and thus we ignore reads to these items already in our benchmarking: https://github.com/paritytech/substrate/blob/master/bin/node/runtime/src/lib.rs#L1683

let whitelist: Vec<TrackedStorageKey> = vec![
    // Block Number
    hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(),
    // Total Issuance
    hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(),
   ...
];

This PR adds a #[pallet::whitelist_storage] attribute macro that can be applied to storage item declarations. Doing so will result in that storage item's storage key being automatically added to the whitelist shown above, meaning they will be excluded from benchmarking calculations. The PR also replaces the existing hard-coded whitelist with #[pallet::whitelist_storage] calls that will instead generate the list organically.

This is accomplished by aggregating the #[pallet::whitelist_storage] attribute flags during pallet macro generation time, reading the storage keys for each storage item, and auto-implementing the trait WhitelistedStorageKeys which adds a function to each implementing pallet returning a Vec<TrackedStorageKey> listing the storage keys within that pallet that have been marked. This vec is then used in place of the hard-coded whitelist shown above.

Note that the actual attribute macro does nothing other than verify that it has been attached to a storage item declaration. The actual meat of this functionality all occurs in the pallet macro generation code.

A moonshot goal here that we may or may not be able to accomplish in this PR would be to panic if we can statically detect that the storage item the macro is being attached to is never read (or, even harder to detect, if it is infrequently read). This would prevent situations where the macro is applied to something it shouldn't be. That functionality probably won't be in scope for this PR, but noting here.

Notes

  • this was formerly called #[benchmarking(cached)]

Follow-up Work

  • the whole family of #[pallet:: attribute macros are currently parsed and have their tokens entirely removed by the main pallet proc macro. As a result there is actually no attribute macro definition for the individual variants of pallet::, making it very hard to find docs or look up these macros in docs.rs, etc. In the future we should add blank macro stubs for all the pallet:: macros that lack stubs and attach their documentation to these stubs instead of burying it in the frame_support main doc page. This could be a future PR.
  • In the future we should add another macro called #[pallet::cache_storage] which will result in the storage key automatically being read at the beginning of every block. This will also automatically whitelist the storage.

Related Issues

@github-actions github-actions bot added the A3-in_progress Pull request is in progress. No review needed at this stage. label Sep 7, 2022
@sam0x17 sam0x17 self-assigned this Sep 7, 2022
frame/system/src/lib.rs Outdated Show resolved Hide resolved
@shawntabrizi shawntabrizi self-requested a review September 8, 2022 10:35
@sam0x17
Copy link
Contributor Author

sam0x17 commented Sep 8, 2022 via email

@sam0x17
Copy link
Contributor Author

sam0x17 commented Sep 8, 2022

So my newest problem is I have my macro gen code in expand_pallet_struct() doing an impl like the following:

	let whitelisted_storage_keys_impl = quote::quote![
		impl<#type_use_gen> frame_support::traits::WhitelistedStorageKeys for #pallet_ident<#type_use_gen> {
			fn whitelisted_storage_keys() -> #frame_support::sp_std::vec::Vec<#frame_support::traits::TrackedStorageKey> {
				let info = Pallet::<T>::storage_info();
				#frame_support::sp_std::vec::Vec::new()
			}
		}
	];

I'm trying to get an access to Pallet::storage_info() to succeed without a compiler error, as I need to read the storage info at runtime to find the storage keys I believe. Problem is I keep keeting this sort of error:

error[E0599]: the function or associated item `storage_info` exists for struct `pallet::Pallet<T>`, but its trait bounds were not satisfied
   --> frame/system/src/lib.rs:197:1
    |
197 | #[frame_support::pallet]
    | ^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item cannot be called on `pallet::Pallet<T>` due to unsatisfied trait bounds
...
360 |     pub struct Pallet<T>(_);
    |     ------------------------
    |     |
    |     function or associated item `storage_info` not found for this
    |     doesn't satisfy `_: frame_support::traits::StorageInfoTrait`
    |
note: trait bound `T: pallet::Config` was not satisfied
   --> frame/system/src/lib.rs:197:1
    |
197 |   #[frame_support::pallet]
    |   ^
...
358 |       #[pallet::pallet]
    |  _______________^
359 | |     #[pallet::generate_store(pub (super) trait Store)]
360 | |     pub struct Pallet<T>(_);
    | |_____________________^
    = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider restricting the type parameter to satisfy the trait bound
    |
360 |     pub struct Pallet<T>(_) where T: pallet::Config;
    |                             +++++++++++++++++++++++

Anyone know how to deal with this and/or know of a better way to read the storage keys from within my impl method?

@sam0x17
Copy link
Contributor Author

sam0x17 commented Sep 9, 2022

Solved using a setup like below. Turns out I was misusing #type_use_gen vs #type_impl_gen:

	let whitelisted_storage_keys_impl = quote::quote![
		use #frame_support::traits::StorageInfoTrait;
		impl<#type_impl_gen> #frame_support::traits::WhitelistedStorageKeys for #pallet_ident<#type_use_gen> {
			fn whitelisted_storage_keys() -> #frame_support::sp_std::vec::Vec<#frame_support::traits::TrackedStorageKey> {
				let info = #pallet_ident::<#type_use_gen>::storage_info();
				#frame_support::sp_std::vec::Vec::new()
			}
		}
	];

Hopefully this will let me do something useful with the StorageInfo object to get individual storage keys at runtime

@sam0x17 sam0x17 force-pushed the cache-runtime-storage-items-macro branch from 7c0103d to f31c859 Compare September 10, 2022 04:35
Copy link
Contributor

@KiChjang KiChjang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good after fixing my comment

Co-authored-by: Keith Yeung <[email protected]>
Copy link
Member

@bkchr bkchr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just some last nitpicks.

frame/support/procedural/src/pallet/parse/mod.rs Outdated Show resolved Hide resolved
frame/support/src/lib.rs Outdated Show resolved Hide resolved
@sam0x17
Copy link
Contributor Author

sam0x17 commented Sep 16, 2022

bot merge

@paritytech-processbot paritytech-processbot bot merged commit 4d04aba into master Sep 16, 2022
@paritytech-processbot paritytech-processbot bot deleted the cache-runtime-storage-items-macro branch September 16, 2022 21:45
ark0f pushed a commit to gear-tech/substrate that referenced this pull request Feb 27, 2023
…aritytech#12205)

* initial setup

* add WhitelistedStorageKeys trait

* add (A, B) tuple implementation for whitelisted_storage_keys()

* fix formatting

* implement WhitelistedStorageKeys for all tuple combinations

* impl_for_tuples up to 128 for WhitelistedStorageKeys

* refactor to #[benchmarking(cached)]

* tweak error message and mark BlockNumber as cached

* add benchmarking(cached) to the other default types

* add docs for benchmarking(cached)

* properly parse storage type declaration

* make storage_alias structs public so we can use them in this macro

* use BTreeMap since TrackedStorageKey missing Ord outside of std

* make WhitelistedStorageKeys accessible

* basic detection of benchmarking(cached) 💥

* proper parsing of #[benchmarking(cached)] from pallet parse macro

* store presence of #[benchmarking(cached)] macro on StorageDef

* will be used for later expansion

* compiling blank impl for WhitelistedStorageKeys

* move impl to expand_pallet_struct

* use frame_support::sp_std::vec::Vec properly

* successfully compiling with storage info loaded into a variable 💥

* plausible implementation for whitelisted_storage_keys()

* depends on the assumption that storage_info.encode() can be loaded
  into TrackedStorageKey::new(..)

* use Pallet::whitelisted_storage_keys() instead of hard-coded list

* AllPallets::whitelisted_storage_keys() properly working 💥

* collect storage names

* whitelisted_storage_keys() impl working 💥

* clean up

* fix compiler error

* just one compiler error

* fix doc compiler error

* use better import path

* fix comment

* whoops

* whoops again

* fix macro import issue

* cargo fmt

* mark example as ignore

* use keyword tokens instead of string parsing

* fix keyword-based parsing of benchmarking(cached)

* preliminary spec for check_whitelist()

* add additional test for benchmarking whitelist

* add TODO note

* remove irrelevant line from example

* use filter_map instead of filter and map

* simplify syntax

Co-authored-by: Keith Yeung <[email protected]>

* clean up

* fix test

* fix tests

* use keyword parsing instead of string parsing

* use collect() instead of a for loop

Co-authored-by: Kian Paimani <[email protected]>

* fix compiler error

* clean up benchmarking(cached) marking code

* use cloned()

* refactor to not use panic! and remove need for pub types in storage_alias

* remove unneeded use

Co-authored-by: Bastian Köcher <[email protected]>

* remove unneeded visibility changes

* don't manually hard code hash for treasury account as hex

* proper Ord, PartialOrd, and Hash impls for TrackedStorageKey

* now based just on key, and available in no-std

* use BTreeSet instead of BTreeMap

* fix comments

* cargo fmt

* switch to pallet::whitelist and re-do it basti's way :D

* make PartialOrd for TrackedStorageKey consistent with Ord

* more correct implementation of hash-related traits for TrackedStorageKey

* fix integration test

* update TODO

* remove unused keyword

* remove more unused keywords

* use into_iter()

Co-authored-by: Keith Yeung <[email protected]>

* Update frame/support/procedural/src/pallet/parse/mod.rs

Co-authored-by: Bastian Köcher <[email protected]>

* add docs for whitelisted

* fix comment

Co-authored-by: Keith Yeung <[email protected]>
Co-authored-by: Kian Paimani <[email protected]>
Co-authored-by: Bastian Köcher <[email protected]>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A0-please_review Pull request needs code review. C1-low PR touches the given topic and has a low impact on builders. D3-trivial 🧸 PR contains trivial changes in a runtime directory that do not require an audit
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Add special tag to cache specific runtime storage items.
6 participants