referrerpolicy=no-referrer-when-downgrade

substrate_frame_rpc_support/
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//! Combines [sc_rpc_api::state::StateApiClient] with [frame_support::storage::generator] traits
19//! to provide strongly typed chain state queries over rpc.
20
21#![warn(missing_docs)]
22
23use codec::{DecodeAll, FullCodec, FullEncode};
24use core::marker::PhantomData;
25use frame_support::storage::generator::{StorageDoubleMap, StorageMap, StorageValue};
26use jsonrpsee::core::ClientError as RpcError;
27use sc_rpc_api::state::StateApiClient;
28use serde::{de::DeserializeOwned, Serialize};
29use sp_storage::{StorageData, StorageKey};
30
31/// A typed query on chain state usable from an RPC client.
32///
33/// ```no_run
34/// # use jsonrpsee::core::ClientError as RpcError;
35/// # use jsonrpsee::ws_client::WsClientBuilder;
36/// # use codec::Encode;
37/// # use frame_support::{construct_runtime, derive_impl, traits::ConstU32};
38/// # use substrate_frame_rpc_support::StorageQuery;
39/// # use sc_rpc_api::state::StateApiClient;
40/// # use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header};
41/// #
42/// # construct_runtime!(
43/// # 	pub enum TestRuntime
44/// # 	{
45/// # 		System: frame_system,
46/// # 		Test: pallet_test,
47/// # 	}
48/// # );
49/// #
50/// # type Hash = sp_core::H256;
51/// #
52/// # #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
53/// # impl frame_system::Config for TestRuntime {
54/// # 	type BaseCallFilter = ();
55/// # 	type BlockWeights = ();
56/// # 	type BlockLength = ();
57/// # 	type RuntimeOrigin = RuntimeOrigin;
58/// # 	type RuntimeCall = RuntimeCall;
59/// # 	type Nonce = u64;
60/// # 	type Hash = Hash;
61/// # 	type Hashing = BlakeTwo256;
62/// # 	type AccountId = u64;
63/// # 	type Lookup = IdentityLookup<Self::AccountId>;
64/// # 	type Block = frame_system::mocking::MockBlock<TestRuntime>;
65/// # 	type RuntimeEvent = RuntimeEvent;
66/// # 	type RuntimeTask = RuntimeTask;
67/// # 	type BlockHashCount = ();
68/// # 	type DbWeight = ();
69/// # 	type Version = ();
70/// # 	type PalletInfo = PalletInfo;
71/// # 	type AccountData = ();
72/// # 	type OnNewAccount = ();
73/// # 	type OnKilledAccount = ();
74/// # 	type SystemWeightInfo = ();
75/// # 	type SS58Prefix = ();
76/// # 	type OnSetCode = ();
77/// # 	type MaxConsumers = ConstU32<16>;
78/// # }
79/// #
80/// # impl pallet_test::Config for TestRuntime {}
81/// #
82///
83/// pub type Loc = (i64, i64, i64);
84/// pub type Block = u8;
85///
86/// // Note that all fields are marked pub.
87/// pub use self::pallet_test::*;
88///
89/// #[frame_support::pallet]
90/// mod pallet_test {
91/// 	use super::*;
92/// 	use frame_support::pallet_prelude::*;
93///
94/// 	#[pallet::pallet]
95/// 	pub struct Pallet<T>(_);
96///
97/// 	#[pallet::config]
98/// 	pub trait Config: frame_system::Config {}
99///
100/// 	#[pallet::storage]
101/// 	pub type LastActionId<T> = StorageValue<_, u64, ValueQuery>;
102///
103/// 	#[pallet::storage]
104/// 	pub type Voxels<T> = StorageMap<_, Blake2_128Concat, Loc, Block>;
105///
106/// 	#[pallet::storage]
107/// 	pub type Actions<T> = StorageMap<_, Blake2_128Concat, u64, Loc>;
108///
109/// 	#[pallet::storage]
110/// 	pub type Prefab<T> = StorageDoubleMap<
111/// 		_,
112/// 		Blake2_128Concat, u128,
113/// 		Blake2_128Concat, (i8, i8, i8), Block
114/// 	>;
115/// }
116///
117/// #[tokio::main]
118/// async fn main() -> Result<(), RpcError> {
119///     let cl = WsClientBuilder::default().build("ws://[::1]:9944").await?;
120///
121///     let q = StorageQuery::value::<LastActionId<TestRuntime>>();
122///     let hash = None::<Hash>;
123///     let _: Option<u64> = q.get(&cl, hash).await?;
124///
125///     let q = StorageQuery::map::<Voxels<TestRuntime>, _>((0, 0, 0));
126///     let _: Option<Block> = q.get(&cl, hash).await?;
127///
128///     let q = StorageQuery::map::<Actions<TestRuntime>, _>(12);
129///     let _: Option<Loc> = q.get(&cl, hash).await?;
130///
131///     let q = StorageQuery::double_map::<Prefab<TestRuntime>, _, _>(3, (0, 0, 0));
132///     let _: Option<Block> = q.get(&cl, hash).await?;
133///
134///     Ok(())
135/// }
136/// ```
137#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
138pub struct StorageQuery<V> {
139	key: StorageKey,
140	_spook: PhantomData<V>,
141}
142
143impl<V: FullCodec> StorageQuery<V> {
144	/// Create a storage query for a StorageValue.
145	pub fn value<St: StorageValue<V>>() -> Self {
146		Self { key: StorageKey(St::storage_value_final_key().to_vec()), _spook: PhantomData }
147	}
148
149	/// Create a storage query for a value in a StorageMap.
150	pub fn map<St: StorageMap<K, V>, K: FullEncode>(key: K) -> Self {
151		Self { key: StorageKey(St::storage_map_final_key(key)), _spook: PhantomData }
152	}
153
154	/// Create a storage query for a value in a StorageDoubleMap.
155	pub fn double_map<St: StorageDoubleMap<K1, K2, V>, K1: FullEncode, K2: FullEncode>(
156		key1: K1,
157		key2: K2,
158	) -> Self {
159		Self { key: StorageKey(St::storage_double_map_final_key(key1, key2)), _spook: PhantomData }
160	}
161
162	/// Send this query over RPC, await the typed result.
163	///
164	/// Hash should be `<YourRuntime as frame_system::Config>::Hash`.
165	///
166	/// # Arguments
167	///
168	/// state_client represents a connection to the RPC server.
169	///
170	/// block_index indicates the block for which state will be queried. A value of None indicates
171	/// the latest block.
172	pub async fn get<Hash, StateClient>(
173		self,
174		state_client: &StateClient,
175		block_index: Option<Hash>,
176	) -> Result<Option<V>, RpcError>
177	where
178		Hash: Send + Sync + 'static + DeserializeOwned + Serialize,
179		StateClient: StateApiClient<Hash> + Sync,
180	{
181		let opt: Option<StorageData> = state_client.storage(self.key, block_index).await?;
182		opt.map(|encoded| V::decode_all(&mut &encoded.0[..]))
183			.transpose()
184			.map_err(|decode_err| RpcError::Custom(decode_err.to_string()))
185	}
186}