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}