substrate_api_client/api/
api_client.rs

1/*
2   Copyright 2019 Supercomputing Systems AG
3   Licensed under the Apache License, Version 2.0 (the "License");
4   you may not use this file except in compliance with the License.
5   You may obtain a copy of the License at
6	   http://www.apache.org/licenses/LICENSE-2.0
7   Unless required by applicable law or agreed to in writing, software
8   distributed under the License is distributed on an "AS IS" BASIS,
9   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10   See the License for the specific language governing permissions and
11   limitations under the License.
12*/
13
14use crate::{
15	api::error::{Error, Result},
16	rpc::Request,
17	runtime_api::RuntimeApiClient,
18	GetAccountInformation,
19};
20use ac_compose_macros::rpc_params;
21use ac_node_api::metadata::Metadata;
22use ac_primitives::{Config, ExtrinsicParams, SignExtrinsic};
23#[cfg(all(not(feature = "sync-api"), not(feature = "std")))]
24use alloc::boxed::Box;
25use alloc::sync::Arc;
26use codec::Decode;
27use core::convert::TryFrom;
28use frame_metadata::RuntimeMetadataPrefixed;
29use log::{debug, info};
30use sp_core::Bytes;
31use sp_version::RuntimeVersion;
32
33pub const KSM_V14_METADATA_PATH: &str = "./../ksm_metadata_v14.bin";
34
35/// Api to talk with substrate-nodes
36///
37/// It is generic over the `Request` trait, so you can use any rpc-backend you like.
38#[derive(Clone)]
39pub struct Api<T: Config, Client> {
40	signer: Option<T::ExtrinsicSigner>,
41	genesis_hash: T::Hash,
42	metadata: Metadata,
43	runtime_version: RuntimeVersion,
44	client: Arc<Client>,
45	runtime_api: RuntimeApiClient<T, Client>,
46	additional_extrinsic_params:
47		Option<<T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::AdditionalParams>,
48}
49
50impl<T: Config, Client> Api<T, Client> {
51	/// Create a new api instance without any node interaction.
52	pub fn new_offline(
53		genesis_hash: T::Hash,
54		metadata: Metadata,
55		runtime_version: RuntimeVersion,
56		client: Client,
57	) -> Self {
58		let client = Arc::new(client);
59		let runtime_api = RuntimeApiClient::new(client.clone());
60		Self {
61			signer: None,
62			genesis_hash,
63			metadata,
64			runtime_version,
65			client,
66			runtime_api,
67			additional_extrinsic_params: None,
68		}
69	}
70
71	/// Set the api signer account.
72	pub fn set_signer(&mut self, signer: T::ExtrinsicSigner) {
73		self.signer = Some(signer);
74	}
75
76	/// Get the private key pair of the api signer.
77	pub fn signer(&self) -> Option<&T::ExtrinsicSigner> {
78		self.signer.as_ref()
79	}
80
81	/// Get the cached genesis hash of the substrate node.
82	pub fn genesis_hash(&self) -> T::Hash {
83		self.genesis_hash
84	}
85
86	/// Get the cached metadata of the substrate node.
87	pub fn metadata(&self) -> &Metadata {
88		&self.metadata
89	}
90
91	/// Get the cached runtime version of the substrate node.
92	pub fn runtime_version(&self) -> &RuntimeVersion {
93		&self.runtime_version
94	}
95
96	/// Get the cached spec version of the substrate node.
97	pub fn spec_version(&self) -> u32 {
98		self.runtime_version.spec_version
99	}
100
101	/// Get the rpc client.
102	pub fn client(&self) -> &Client {
103		&self.client
104	}
105
106	/// Set the additional params.
107	pub fn set_additional_params(
108		&mut self,
109		add_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::AdditionalParams,
110	) {
111		self.additional_extrinsic_params = Some(add_params);
112	}
113
114	/// Access the RuntimeApi.
115	pub fn runtime_api(&self) -> &RuntimeApiClient<T, Client> {
116		&self.runtime_api
117	}
118
119	/// Get the extrinsic params with the set additional params. If no additional params are set,
120	/// the default is taken.
121	pub fn extrinsic_params(&self, nonce: T::Index) -> T::ExtrinsicParams {
122		let additional_extrinsic_params =
123			self.additional_extrinsic_params.clone().unwrap_or_default();
124		T::ExtrinsicParams::new(
125			self.runtime_version.spec_version,
126			self.runtime_version.transaction_version,
127			nonce,
128			self.genesis_hash,
129			additional_extrinsic_params,
130		)
131	}
132}
133
134impl<T, Client> Api<T, Client>
135where
136	T: Config,
137	Client: Request,
138{
139	/// Create a new Api client with call to the node to retrieve metadata.
140	#[maybe_async::async_impl]
141	pub async fn new(client: Client) -> Result<Self> {
142		let genesis_hash_future = Self::get_genesis_hash(&client);
143		let metadata_future = Self::get_metadata(&client);
144		let runtime_version_future = Self::get_runtime_version(&client);
145
146		let (genesis_hash, metadata, runtime_version) = futures_util::future::try_join3(
147			genesis_hash_future,
148			metadata_future,
149			runtime_version_future,
150		)
151		.await?;
152		info!("Got genesis hash: {genesis_hash:?}");
153		debug!("Metadata: {metadata:?}");
154		info!("Runtime Version: {runtime_version:?}");
155		Ok(Self::new_offline(genesis_hash, metadata, runtime_version, client))
156	}
157
158	/// Create a new Api client with call to the node to retrieve metadata.
159	#[maybe_async::sync_impl]
160	pub fn new(client: Client) -> Result<Self> {
161		let genesis_hash = Self::get_genesis_hash(&client)?;
162		info!("Got genesis hash: {genesis_hash:?}");
163
164		let metadata = Self::get_metadata(&client)?;
165		debug!("Metadata: {metadata:?}");
166
167		let runtime_version = Self::get_runtime_version(&client)?;
168		info!("Runtime Version: {runtime_version:?}");
169
170		Ok(Self::new_offline(genesis_hash, metadata, runtime_version, client))
171	}
172}
173
174#[maybe_async::maybe_async(?Send)]
175pub trait UpdateRuntime {
176	/// Updates the runtime and metadata of the api via node query.
177	/// Ideally, this function is called if a substrate update runtime event is encountered.
178	async fn update_runtime(&mut self) -> Result<()>;
179}
180
181#[maybe_async::maybe_async(?Send)]
182impl<T, Client> UpdateRuntime for Api<T, Client>
183where
184	T: Config,
185	Client: Request,
186{
187	#[maybe_async::sync_impl]
188	fn update_runtime(&mut self) -> Result<()> {
189		let metadata = Self::get_metadata(&self.client)?;
190		let runtime_version = Self::get_runtime_version(&self.client)?;
191
192		debug!("Metadata: {metadata:?}");
193		info!("Runtime Version: {runtime_version:?}");
194
195		self.metadata = metadata;
196		self.runtime_version = runtime_version;
197		Ok(())
198	}
199
200	#[maybe_async::async_impl(?Send)]
201	async fn update_runtime(&mut self) -> Result<()> {
202		let metadata_future = Self::get_metadata(&self.client);
203		let runtime_version_future = Self::get_runtime_version(&self.client);
204
205		let (metadata, runtime_version) =
206			futures_util::future::try_join(metadata_future, runtime_version_future).await?;
207
208		debug!("Metadata: {:?}", metadata);
209		info!("Runtime Version: {:?}", runtime_version);
210
211		self.metadata = metadata;
212		self.runtime_version = runtime_version;
213		Ok(())
214	}
215}
216
217impl<T, Client> Api<T, Client>
218where
219	T: Config,
220	Client: Request,
221{
222	/// Get the public part of the api signer account.
223	pub fn signer_account(&self) -> Option<&T::AccountId> {
224		let pair = self.signer.as_ref()?;
225		Some(pair.public_account_id())
226	}
227
228	/// Get nonce of self signer account.
229	#[maybe_async::maybe_async(?Send)]
230	pub async fn get_nonce(&self) -> Result<T::Index> {
231		let account = self.signer_account().ok_or(Error::NoSigner)?;
232		self.get_account_nonce(account).await
233	}
234}
235
236/// Private node query methods. They should be used internally only, because the user should retrieve the data from the struct cache.
237/// If an up-to-date query is necessary, cache should be updated beforehand.
238impl<T, Client> Api<T, Client>
239where
240	T: Config,
241	Client: Request,
242{
243	/// Get the genesis hash from node via websocket query.
244	#[maybe_async::maybe_async(?Send)]
245	async fn get_genesis_hash(client: &Client) -> Result<T::Hash> {
246		let genesis: Option<T::Hash> =
247			client.request("chain_getBlockHash", rpc_params![Some(0)]).await?;
248		genesis.ok_or(Error::FetchGenesisHash)
249	}
250
251	/// Get runtime version from node via websocket query.
252	#[maybe_async::maybe_async(?Send)]
253	async fn get_runtime_version(client: &Client) -> Result<RuntimeVersion> {
254		let version: RuntimeVersion =
255			client.request("state_getRuntimeVersion", rpc_params![]).await?;
256		Ok(version)
257	}
258
259	/// Get metadata from node via websocket query.
260	#[maybe_async::maybe_async(?Send)]
261	async fn get_metadata(client: &Client) -> Result<Metadata> {
262		let metadata_bytes: Bytes = client.request("state_getMetadata", rpc_params![]).await?;
263
264		let metadata = RuntimeMetadataPrefixed::decode(&mut metadata_bytes.0.as_slice())?;
265		Metadata::try_from(metadata).map_err(|e| e.into())
266	}
267}
268#[cfg(test)]
269mod tests {
270	use super::*;
271	use crate::rpc::mocks::RpcClientMock;
272	use ac_primitives::{
273		DefaultRuntimeConfig, GenericAdditionalParams, GenericExtrinsicParams, PlainTip,
274	};
275	use frame_metadata::{v14::ExtrinsicMetadata, RuntimeMetadata};
276	use scale_info::form::PortableForm;
277	use sp_core::H256;
278	use std::{collections::HashMap, fs};
279
280	fn create_mock_api(
281		genesis_hash: H256,
282		runtime_version: RuntimeVersion,
283		metadata: Metadata,
284		data: HashMap<String, String>,
285	) -> Api<DefaultRuntimeConfig, RpcClientMock> {
286		let client = RpcClientMock::new(data);
287		Api::new_offline(genesis_hash, metadata, runtime_version, client)
288	}
289
290	#[test]
291	fn api_extrinsic_params_works() {
292		// Create new api.
293		let genesis_hash = H256::random();
294		let runtime_version = RuntimeVersion::default();
295		let encoded_metadata = fs::read(KSM_V14_METADATA_PATH).unwrap();
296		let metadata: RuntimeMetadataPrefixed =
297			Decode::decode(&mut encoded_metadata.as_slice()).unwrap();
298		let metadata = Metadata::try_from(metadata).unwrap();
299
300		let mut api =
301			create_mock_api(genesis_hash, runtime_version.clone(), metadata, Default::default());
302
303		// Information for Era for mortal transactions.
304		let additional_params = GenericAdditionalParams::new();
305		api.set_additional_params(additional_params);
306
307		let nonce = 6;
308		let retrieved_params = api.extrinsic_params(nonce);
309
310		let expected_params = GenericExtrinsicParams::<DefaultRuntimeConfig, PlainTip<u128>>::new(
311			runtime_version.spec_version,
312			runtime_version.transaction_version,
313			nonce,
314			genesis_hash,
315			Default::default(),
316		);
317
318		assert_eq!(expected_params, retrieved_params)
319	}
320
321	#[test]
322	fn api_runtime_update_works() {
323		let runtime_version = RuntimeVersion { spec_version: 10, ..Default::default() };
324		// Update metadata
325		let encoded_metadata: Bytes = fs::read(KSM_V14_METADATA_PATH).unwrap().into();
326		let runtime_metadata_prefixed: RuntimeMetadataPrefixed =
327			Decode::decode(&mut encoded_metadata.0.as_slice()).unwrap();
328		let mut runtime_metadata = match runtime_metadata_prefixed.1 {
329			RuntimeMetadata::V14(ref metadata) => metadata.clone(),
330			_ => unimplemented!(),
331		};
332
333		let metadata = Metadata::try_from(runtime_metadata_prefixed).unwrap();
334
335		runtime_metadata.extrinsic = ExtrinsicMetadata::<PortableForm> {
336			ty: runtime_metadata.extrinsic.ty,
337			version: 0,
338			signed_extensions: Vec::new(),
339		};
340		let changed_runtime_metadata_prefixed =
341			RuntimeMetadataPrefixed(1635018093, RuntimeMetadata::V14(runtime_metadata));
342		let changed_metadata = Metadata::try_from(changed_runtime_metadata_prefixed).unwrap();
343
344		let data = HashMap::<String, String>::from([
345			(
346				"chain_getBlockHash".to_owned(),
347				serde_json::to_string(&Some(H256::from([1u8; 32]))).unwrap(),
348			),
349			(
350				"state_getRuntimeVersion".to_owned(),
351				serde_json::to_string(&runtime_version).unwrap(),
352			),
353			("state_getMetadata".to_owned(), serde_json::to_string(&encoded_metadata).unwrap()),
354		]);
355		let mut api =
356			create_mock_api(Default::default(), Default::default(), changed_metadata, data);
357
358		// Ensure current metadata and runtime version are different.
359		assert_ne!(api.metadata.extrinsic(), metadata.extrinsic());
360		assert_ne!(api.runtime_version, runtime_version);
361
362		// Update runtime.
363		api.update_runtime().unwrap();
364
365		// Ensure metadata and runtime version have been updated.
366		assert_eq!(api.metadata.extrinsic(), metadata.extrinsic());
367		assert_eq!(api.runtime_version, runtime_version);
368	}
369}