substrate_api_client/api/rpc_api/
author.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
14//! Interface to common author rpc functions and helpers thereof.
15
16use crate::{
17	api::{rpc_api::events::FetchEvents, Error, Result},
18	rpc::{HandleSubscription, Request, Subscribe},
19	Api, ExtrinsicReport, TransactionStatus, XtStatus,
20};
21use ac_compose_macros::rpc_params;
22use ac_primitives::{config::Config, UncheckedExtrinsic};
23#[cfg(all(not(feature = "sync-api"), not(feature = "std")))]
24use alloc::boxed::Box;
25use codec::{Decode, Encode};
26use log::*;
27use serde::de::DeserializeOwned;
28use sp_core::Bytes;
29use sp_runtime::traits::Hash as HashTrait;
30
31pub type TransactionSubscriptionFor<Client, Hash> =
32	<Client as Subscribe>::Subscription<TransactionStatus<Hash, Hash>>;
33
34/// Simple extrinsic submission without any subscription.
35#[maybe_async::maybe_async(?Send)]
36pub trait SubmitExtrinsic {
37	type Hash;
38
39	/// Submit an encodable extrinsic to the substrate node.
40	/// Returns the extrinsic hash.
41	async fn submit_extrinsic<Address, Call, Signature, TransactionExtension>(
42		&self,
43		extrinsic: UncheckedExtrinsic<Address, Call, Signature, TransactionExtension>,
44	) -> Result<Self::Hash>
45	where
46		Address: Encode,
47		Call: Encode,
48		Signature: Encode,
49		TransactionExtension: Encode;
50
51	/// Submit an encoded, opaque extrinsic to the substrate node.
52	/// Returns the extrinsic hash.
53	async fn submit_opaque_extrinsic(&self, encoded_extrinsic: &Bytes) -> Result<Self::Hash>;
54}
55
56#[maybe_async::maybe_async(?Send)]
57impl<T, Client> SubmitExtrinsic for Api<T, Client>
58where
59	T: Config,
60	Client: Request,
61{
62	type Hash = T::Hash;
63
64	async fn submit_extrinsic<Address, Call, Signature, TransactionExtension>(
65		&self,
66		extrinsic: UncheckedExtrinsic<Address, Call, Signature, TransactionExtension>,
67	) -> Result<Self::Hash>
68	where
69		Address: Encode,
70		Call: Encode,
71		Signature: Encode,
72		TransactionExtension: Encode,
73	{
74		self.submit_opaque_extrinsic(&extrinsic.encode().into()).await
75	}
76
77	async fn submit_opaque_extrinsic(&self, encoded_extrinsic: &Bytes) -> Result<Self::Hash> {
78		let hex_encoded_xt = rpc_params![encoded_extrinsic];
79		debug!("sending extrinsic: {hex_encoded_xt:?}");
80		let xt_hash = self.client().request("author_submitExtrinsic", hex_encoded_xt).await?;
81		Ok(xt_hash)
82	}
83}
84
85#[maybe_async::maybe_async(?Send)]
86pub trait SubmitAndWatch {
87	type Client: Subscribe;
88	type Hash: DeserializeOwned + Decode + Encode;
89
90	/// Submit an extrinsic an return a Subscription
91	/// to watch the extrinsic progress.
92	///
93	/// This method is blocking if the sync-api feature is activated
94	async fn submit_and_watch_extrinsic<Address, Call, Signature, TransactionExtension>(
95		&self,
96		extrinsic: UncheckedExtrinsic<Address, Call, Signature, TransactionExtension>,
97	) -> Result<TransactionSubscriptionFor<Self::Client, Self::Hash>>
98	where
99		Address: Encode,
100		Call: Encode,
101		Signature: Encode,
102		TransactionExtension: Encode;
103
104	/// Submit an encoded, opaque extrinsic an return a Subscription to
105	/// watch the extrinsic progress.
106	///
107	/// This method is blocking if the sync-api feature is activated
108	async fn submit_and_watch_opaque_extrinsic(
109		&self,
110		encoded_extrinsic: &Bytes,
111	) -> Result<TransactionSubscriptionFor<Self::Client, Self::Hash>>;
112
113	/// Submit an extrinsic and watch it until the desired status
114	/// is reached, if no error is encountered previously.
115	///
116	/// If watched until `InBlock` or `Finalized`, this function will
117	/// return an error if the extrinsic was not successfully executed.
118	/// If it was successful, a report containing the following is returned:
119	/// - extrinsic hash
120	/// - hash of the block the extrinsic was included in
121	/// - last known extrinsic (transaction) status
122	/// - associated events of the extrinsic
123	///
124	/// If not watched until at least `InBlock`, this function will not know if the extrinsic
125	/// has been executed on chain or not and will therefore not return an error if execution fails.
126	/// An error will be returned if the extrinsic has failed to be sent or if it has not been
127	/// included into the transaction pool of the node.
128	/// If no error occurs, a report containing the following is returned:
129	/// - extrinsic hash
130	/// - last known extrinsic (transaction) status
131	///
132	/// This method is blocking if the sync-api feature is activated
133	async fn submit_and_watch_extrinsic_until<Address, Call, Signature, TransactionExtension>(
134		&self,
135		extrinsic: UncheckedExtrinsic<Address, Call, Signature, TransactionExtension>,
136		watch_until: XtStatus,
137	) -> Result<ExtrinsicReport<Self::Hash>>
138	where
139		Address: Encode,
140		Call: Encode,
141		Signature: Encode,
142		TransactionExtension: Encode;
143
144	/// Submit an encoded, opaque extrinsic until the desired status
145	/// is reached, if no error is encountered previously.
146	///
147	/// If watched until `InBlock` or `Finalized`, this function will
148	/// return an error if the extrinsic was not successfully executed.
149	/// If it was successful, a report containing the following is returned:
150	/// - extrinsic hash
151	/// - hash of the block the extrinsic was included in
152	/// - last known extrinsic (transaction) status
153	/// - associated events of the extrinsic (only for InBlock or Finalized)
154	///
155	/// If not watched until at least `InBlock`, this function will not know if the extrinsic
156	/// has been executed on chain or not and will therefore not return an error if execution fails..
157	/// An error will be returned, if the extrinsic has failed to be sent or if it has not been
158	/// included into the transaction pool of the node.
159	/// If no error occurs, a report containing the following is returned:
160	/// - extrinsic hash
161	/// - last known extrinsic (transaction) status
162	///
163	/// This method is blocking if the sync-api feature is activated
164	async fn submit_and_watch_opaque_extrinsic_until(
165		&self,
166		encoded_extrinsic: &Bytes,
167		watch_until: XtStatus,
168	) -> Result<ExtrinsicReport<Self::Hash>>;
169
170	/// Submit an extrinsic and watch it until the desired status
171	/// is reached, if no error is encountered previously.
172	/// The events are not fetched. So no events are listed in the report.
173	/// To fetch the triggered events, please use submit_and_watch_extrinsic_until.
174	/// Upon success, a report containing the following information is returned:
175	/// - extrinsic hash
176	/// - if watched until at least `InBlock`:
177	///   hash of the block the extrinsic was included in
178	/// - last known extrinsic (transaction) status
179	///
180	/// This method is blocking if the sync-api feature is activated
181	async fn submit_and_watch_extrinsic_until_without_events<
182		Address,
183		Call,
184		Signature,
185		TransactionExtension,
186	>(
187		&self,
188		extrinsic: UncheckedExtrinsic<Address, Call, Signature, TransactionExtension>,
189		watch_until: XtStatus,
190	) -> Result<ExtrinsicReport<Self::Hash>>
191	where
192		Address: Encode,
193		Call: Encode,
194		Signature: Encode,
195		TransactionExtension: Encode;
196
197	/// Submit an encoded, opaque extrinsic and watch it until the desired status
198	/// is reached, if no error is encountered previously.
199	/// The events are not fetched. So no events are listed in the report.
200	/// To fetch the triggered events, please use submit_and_watch_opaque_extrinsic_until.
201	/// Upon success, a report containing the following information is returned:
202	/// - extrinsic hash
203	/// - if watched until at least `InBlock`:
204	///   hash of the block the extrinsic was included in
205	/// - last known extrinsic (transaction) status
206	///
207	/// This method is blocking if the sync-api feature is activated
208	async fn submit_and_watch_opaque_extrinsic_until_without_events(
209		&self,
210		encoded_extrinsic: &Bytes,
211		watch_until: XtStatus,
212	) -> Result<ExtrinsicReport<Self::Hash>>;
213
214	/// Query the events for the specified `report` and attaches them to the mutable report.
215	/// If the function fails events might still be added to the report.
216	///
217	/// This method is blocking if the sync-api feature is activated
218	async fn populate_events(&self, report: &mut ExtrinsicReport<Self::Hash>) -> Result<()>;
219}
220
221#[maybe_async::maybe_async(?Send)]
222impl<T, Client> SubmitAndWatch for Api<T, Client>
223where
224	T: Config,
225	Client: Subscribe + Request,
226{
227	type Client = Client;
228	type Hash = T::Hash;
229
230	async fn submit_and_watch_extrinsic<Address, Call, Signature, TransactionExtension>(
231		&self,
232		extrinsic: UncheckedExtrinsic<Address, Call, Signature, TransactionExtension>,
233	) -> Result<TransactionSubscriptionFor<Self::Client, Self::Hash>>
234	where
235		Address: Encode,
236		Call: Encode,
237		Signature: Encode,
238		TransactionExtension: Encode,
239	{
240		self.submit_and_watch_opaque_extrinsic(&extrinsic.encode().into()).await
241	}
242
243	async fn submit_and_watch_opaque_extrinsic(
244		&self,
245		encoded_extrinsic: &Bytes,
246	) -> Result<TransactionSubscriptionFor<Self::Client, Self::Hash>> {
247		self.client()
248			.subscribe(
249				"author_submitAndWatchExtrinsic",
250				rpc_params![encoded_extrinsic],
251				"author_unsubmitAndWatchExtrinsic",
252			)
253			.await
254			.map_err(|e| e.into())
255	}
256
257	async fn submit_and_watch_extrinsic_until<Address, Call, Signature, TransactionExtension>(
258		&self,
259		extrinsic: UncheckedExtrinsic<Address, Call, Signature, TransactionExtension>,
260		watch_until: XtStatus,
261	) -> Result<ExtrinsicReport<Self::Hash>>
262	where
263		Address: Encode,
264		Call: Encode,
265		Signature: Encode,
266		TransactionExtension: Encode,
267	{
268		self.submit_and_watch_opaque_extrinsic_until(&extrinsic.encode().into(), watch_until)
269			.await
270	}
271
272	async fn submit_and_watch_opaque_extrinsic_until(
273		&self,
274		encoded_extrinsic: &Bytes,
275		watch_until: XtStatus,
276	) -> Result<ExtrinsicReport<Self::Hash>> {
277		let mut report = self
278			.submit_and_watch_opaque_extrinsic_until_without_events(encoded_extrinsic, watch_until)
279			.await?;
280
281		if watch_until < XtStatus::InBlock {
282			return Ok(report)
283		}
284		self.populate_events(&mut report).await?;
285		report.check_events_for_dispatch_error(self.metadata())?;
286		Ok(report)
287	}
288
289	async fn populate_events(&self, report: &mut ExtrinsicReport<Self::Hash>) -> Result<()> {
290		if report.events.is_some() {
291			return Err(Error::EventsAlreadyPresent)
292		}
293		let block_hash = report.block_hash.ok_or(Error::BlockHashNotFound)?;
294		let extrinsic_events =
295			self.fetch_events_for_extrinsic(block_hash, report.extrinsic_hash).await?;
296		report.add_events(extrinsic_events);
297		Ok(())
298	}
299
300	async fn submit_and_watch_extrinsic_until_without_events<
301		Address,
302		Call,
303		Signature,
304		TransactionExtension,
305	>(
306		&self,
307		extrinsic: UncheckedExtrinsic<Address, Call, Signature, TransactionExtension>,
308		watch_until: XtStatus,
309	) -> Result<ExtrinsicReport<Self::Hash>>
310	where
311		Address: Encode,
312		Call: Encode,
313		Signature: Encode,
314		TransactionExtension: Encode,
315	{
316		self.submit_and_watch_opaque_extrinsic_until_without_events(
317			&extrinsic.encode().into(),
318			watch_until,
319		)
320		.await
321	}
322
323	async fn submit_and_watch_opaque_extrinsic_until_without_events(
324		&self,
325		encoded_extrinsic: &Bytes,
326		watch_until: XtStatus,
327	) -> Result<ExtrinsicReport<Self::Hash>> {
328		let tx_hash = T::Hasher::hash(encoded_extrinsic);
329		let mut subscription: TransactionSubscriptionFor<Self::Client, Self::Hash> =
330			self.submit_and_watch_opaque_extrinsic(encoded_extrinsic).await?;
331
332		while let Some(transaction_status) = subscription.next().await {
333			let transaction_status = transaction_status?;
334			match transaction_status.is_expected() {
335				Ok(_) =>
336					if transaction_status.reached_status(watch_until) {
337						subscription.unsubscribe().await?;
338						let block_hash = transaction_status.get_maybe_block_hash();
339						return Ok(ExtrinsicReport::new(
340							tx_hash,
341							block_hash.copied(),
342							transaction_status,
343							None,
344						))
345					},
346				Err(e) => {
347					subscription.unsubscribe().await?;
348					return Err(e)
349				},
350			}
351		}
352		Err(Error::NoStream)
353	}
354}