referrerpolicy=no-referrer-when-downgrade

frame_storage_access_test_runtime/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Test runtime to benchmark storage access on block validation
19
20#![cfg_attr(not(feature = "std"), no_std)]
21
22extern crate alloc;
23
24use alloc::vec::Vec;
25use codec::{Decode, Encode};
26use sp_core::storage::ChildInfo;
27use sp_runtime::traits;
28use sp_trie::StorageProof;
29
30#[cfg(all(not(feature = "std"), feature = "runtime-benchmarks"))]
31use {
32	cumulus_pallet_parachain_system::validate_block::{
33		trie_cache::CacheProvider, trie_recorder::SizeOnlyRecorderProvider,
34	},
35	sp_core::storage::StateVersion,
36	sp_runtime::{generic, OpaqueExtrinsic},
37	sp_state_machine::{Backend, TrieBackendBuilder},
38};
39
40// Include the WASM binary
41#[cfg(feature = "std")]
42include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
43
44/// Parameters for benchmarking storage access on block validation.
45///
46/// On dry-run, the storage access is not performed to measure the cost of the runtime call.
47#[derive(Decode, Clone)]
48#[cfg_attr(feature = "std", derive(Encode))]
49pub struct StorageAccessParams<B: traits::Block> {
50	pub state_root: B::Hash,
51	pub storage_proof: StorageProof,
52	pub payload: StorageAccessPayload,
53	/// On dry-run, we don't read/write to the storage.
54	pub is_dry_run: bool,
55}
56
57/// Payload for benchmarking read and write operations on block validation.
58#[derive(Debug, Clone, Decode, Encode)]
59pub enum StorageAccessPayload {
60	// Storage keys with optional child info.
61	Read(Vec<(Vec<u8>, Option<ChildInfo>)>),
62	// Storage key-value pairs with optional child info.
63	Write((Vec<(Vec<u8>, Vec<u8>)>, Option<ChildInfo>)),
64}
65
66impl<B: traits::Block> StorageAccessParams<B> {
67	/// Create a new params for reading from the storage.
68	pub fn new_read(
69		state_root: B::Hash,
70		storage_proof: StorageProof,
71		payload: Vec<(Vec<u8>, Option<ChildInfo>)>,
72	) -> Self {
73		Self {
74			state_root,
75			storage_proof,
76			payload: StorageAccessPayload::Read(payload),
77			is_dry_run: false,
78		}
79	}
80
81	/// Create a new params for writing to the storage.
82	pub fn new_write(
83		state_root: B::Hash,
84		storage_proof: StorageProof,
85		payload: (Vec<(Vec<u8>, Vec<u8>)>, Option<ChildInfo>),
86	) -> Self {
87		Self {
88			state_root,
89			storage_proof,
90			payload: StorageAccessPayload::Write(payload),
91			is_dry_run: false,
92		}
93	}
94
95	/// Create a dry-run version of the params.
96	pub fn as_dry_run(&self) -> Self {
97		Self {
98			state_root: self.state_root,
99			storage_proof: self.storage_proof.clone(),
100			payload: self.payload.clone(),
101			is_dry_run: true,
102		}
103	}
104}
105
106/// Imitates `cumulus_pallet_parachain_system::validate_block::implementation::validate_block`
107///
108/// Only performs the storage access, this is used to benchmark the storage access cost.
109#[doc(hidden)]
110#[cfg(all(not(feature = "std"), feature = "runtime-benchmarks"))]
111pub fn proceed_storage_access<B: traits::Block>(mut params: &[u8]) {
112	let StorageAccessParams { state_root, storage_proof, payload, is_dry_run } =
113		StorageAccessParams::<B>::decode(&mut params)
114			.expect("Invalid arguments to `validate_block`.");
115
116	let db = storage_proof.into_memory_db();
117	let recorder = SizeOnlyRecorderProvider::<traits::HashingFor<B>>::default();
118	let cache_provider = CacheProvider::new();
119	let backend = TrieBackendBuilder::new_with_cache(db, state_root, cache_provider)
120		.with_recorder(recorder)
121		.build();
122
123	if is_dry_run {
124		return;
125	}
126
127	match payload {
128		StorageAccessPayload::Read(keys) =>
129			for (key, maybe_child_info) in keys {
130				match maybe_child_info {
131					Some(child_info) => {
132						let _ = backend
133							.child_storage(&child_info, key.as_ref())
134							.expect("Key not found")
135							.ok_or("Value unexpectedly empty");
136					},
137					None => {
138						let _ = backend
139							.storage(key.as_ref())
140							.expect("Key not found")
141							.ok_or("Value unexpectedly empty");
142					},
143				}
144			},
145		StorageAccessPayload::Write((changes, maybe_child_info)) => {
146			let delta = changes.iter().map(|(key, value)| (key.as_ref(), Some(value.as_ref())));
147			match maybe_child_info {
148				Some(child_info) => {
149					backend.child_storage_root(&child_info, delta, StateVersion::V1);
150				},
151				None => {
152					backend.storage_root(delta, StateVersion::V1);
153				},
154			}
155		},
156	}
157}
158
159/// Wasm binary unwrapped. If built with `SKIP_WASM_BUILD`, the function panics.
160#[cfg(feature = "std")]
161pub fn wasm_binary_unwrap() -> &'static [u8] {
162	WASM_BINARY.expect(
163		"Development wasm binary is not available. Unset SKIP_WASM_BUILD and compile the runtime again.",
164	)
165}
166
167#[cfg(enable_alloc_error_handler)]
168#[alloc_error_handler]
169#[no_mangle]
170pub fn oom(_: core::alloc::Layout) -> ! {
171	core::intrinsics::abort();
172}
173
174#[cfg(all(not(feature = "std"), feature = "runtime-benchmarks"))]
175#[no_mangle]
176pub extern "C" fn validate_block(params: *const u8, len: usize) -> u64 {
177	type Block = generic::Block<generic::Header<u32, traits::BlakeTwo256>, OpaqueExtrinsic>;
178	let params = unsafe { alloc::slice::from_raw_parts(params, len) };
179	proceed_storage_access::<Block>(params);
180	1
181}