substrate_api_client/api/
api_client.rs1use 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#[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 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 pub fn set_signer(&mut self, signer: T::ExtrinsicSigner) {
73 self.signer = Some(signer);
74 }
75
76 pub fn signer(&self) -> Option<&T::ExtrinsicSigner> {
78 self.signer.as_ref()
79 }
80
81 pub fn genesis_hash(&self) -> T::Hash {
83 self.genesis_hash
84 }
85
86 pub fn metadata(&self) -> &Metadata {
88 &self.metadata
89 }
90
91 pub fn runtime_version(&self) -> &RuntimeVersion {
93 &self.runtime_version
94 }
95
96 pub fn spec_version(&self) -> u32 {
98 self.runtime_version.spec_version
99 }
100
101 pub fn client(&self) -> &Client {
103 &self.client
104 }
105
106 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 pub fn runtime_api(&self) -> &RuntimeApiClient<T, Client> {
116 &self.runtime_api
117 }
118
119 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 #[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 #[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 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 pub fn signer_account(&self) -> Option<&T::AccountId> {
224 let pair = self.signer.as_ref()?;
225 Some(pair.public_account_id())
226 }
227
228 #[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
236impl<T, Client> Api<T, Client>
239where
240 T: Config,
241 Client: Request,
242{
243 #[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 #[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 #[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 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 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 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 assert_ne!(api.metadata.extrinsic(), metadata.extrinsic());
360 assert_ne!(api.runtime_version, runtime_version);
361
362 api.update_runtime().unwrap();
364
365 assert_eq!(api.metadata.extrinsic(), metadata.extrinsic());
367 assert_eq!(api.runtime_version, runtime_version);
368 }
369}