substrate_api_client/api/
mod.rs

1/*
2   Copyright 2019 Supercomputing Systems AG
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8	   http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15
16*/
17
18use crate::error::FailedExtrinsicError;
19use ac_node_api::{events::RawEventDetails, EventDetails, Metadata};
20use alloc::{string::String, vec::Vec};
21use codec::{Decode, Encode};
22use serde::{Deserialize, Serialize};
23use sp_core::Bytes;
24
25pub use api_client::Api;
26pub use error::{Error, Result};
27pub use rpc_api::{
28	FetchEvents, GetAccountInformation, GetBalance, GetChainInfo, GetStorage,
29	GetTransactionPayment, SubmitAndWatch, SubmitExtrinsic, SubscribeChain, SubscribeEvents,
30	SystemApi,
31};
32
33pub mod api_client;
34pub mod error;
35pub mod rpc_api;
36pub mod runtime_api;
37
38/// Extrinsic report returned upon a submit_and_watch request.
39/// Holds as much information as available.
40#[derive(Debug, Clone, Encode, Decode, PartialEq)]
41pub struct ExtrinsicReport<Hash: Encode + Decode> {
42	// Hash of the extrinsic.
43	pub extrinsic_hash: Hash,
44	// Block hash of the block the extrinsic was included in.
45	// Only available if watched until at least `InBlock`.
46	pub block_hash: Option<Hash>,
47	// Last known Transaction Status.
48	pub status: TransactionStatus<Hash, Hash>,
49	// Events associated to the extrinsic.
50	// Only available if explicitly stated, because
51	// extra node queries are necessary to fetch the events.
52	pub events: Option<Vec<RawEventDetails<Hash>>>,
53}
54
55impl<Hash: Encode + Decode> ExtrinsicReport<Hash> {
56	pub fn new(
57		extrinsic_hash: Hash,
58		block_hash: Option<Hash>,
59		status: TransactionStatus<Hash, Hash>,
60		events: Option<Vec<RawEventDetails<Hash>>>,
61	) -> Self {
62		Self { extrinsic_hash, block_hash, status, events }
63	}
64
65	pub fn add_events(&mut self, events: Vec<EventDetails<Hash>>) {
66		self.events = Some(events.into_iter().map(|event| event.to_raw()).collect());
67	}
68
69	/// Checks the status of the extrinsic by evaluating the events attached to the report.
70	/// Returns an error if the events are missing or if one of the events indicates a problem.
71	pub fn check_events_for_dispatch_error(&self, metadata: &Metadata) -> Result<()> {
72		if self.events.is_none() {
73			return Err(Error::EventsMissing)
74		}
75		// Check if the extrinsic was successful or not.
76		let events = self.events.as_ref().unwrap();
77		for event in events {
78			if let Some(dispatch_error) = event.get_associated_dispatch_error(metadata) {
79				return Err(Error::FailedExtrinsic(FailedExtrinsicError::new(
80					dispatch_error,
81					self.encode(),
82				)))
83			}
84		}
85		Ok(())
86	}
87}
88
89/// Simplified TransactionStatus to allow the user to choose until when to watch
90/// an extrinsic.
91// Indexes must match the TransactionStatus::as_u8 from below.
92#[derive(Debug, PartialEq, PartialOrd, Eq, Copy, Clone)]
93pub enum XtStatus {
94	Ready = 1,
95	Broadcast = 2,
96	InBlock = 3,
97	Retracted = 4,
98	Finalized = 6,
99}
100
101/// TxStatus that is not expected during the watch process. Will be returned
102/// as unexpected error if encountered due to the potential danger of endless loops.
103#[derive(Debug, PartialEq, Eq, Copy, Clone)]
104pub enum UnexpectedTxStatus {
105	Future,
106	FinalityTimeout,
107	Usurped,
108	Dropped,
109	Invalid,
110}
111
112/// Possible transaction status events.
113// Copied from `sc-transaction-pool`
114// (https://github.com/paritytech/substrate/blob/dddfed3d9260cf03244f15ba3db4edf9af7467e9/client/transaction-pool/api/src/lib.rs)
115// as the library is not no-std compatible
116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)]
117#[serde(rename_all = "camelCase")]
118pub enum TransactionStatus<Hash: Encode + Decode, BlockHash: Encode + Decode> {
119	/// Transaction is part of the future queue.
120	Future,
121	/// Transaction is part of the ready queue.
122	Ready,
123	/// The transaction has been broadcast to the given peers.
124	Broadcast(Vec<String>),
125	/// Transaction has been included in block with given hash.
126	InBlock(BlockHash),
127	/// The block this transaction was included in has been retracted.
128	Retracted(BlockHash),
129	/// Maximum number of finality watchers has been reached,
130	/// old watchers are being removed.
131	FinalityTimeout(BlockHash),
132	/// Transaction has been finalized by a finality-gadget, e.g GRANDPA
133	Finalized(BlockHash),
134	/// Transaction has been replaced in the pool, by another transaction
135	/// that provides the same tags. (e.g. same (sender, nonce)).
136	Usurped(Hash),
137	/// Transaction has been dropped from the pool because of the limit.
138	Dropped,
139	/// Transaction is no longer valid in the current state.
140	Invalid,
141}
142
143impl<Hash: Encode + Decode, BlockHash: Encode + Decode> TransactionStatus<Hash, BlockHash> {
144	pub fn as_u8(&self) -> u8 {
145		match self {
146			Self::Future => 0,
147			Self::Ready => 1,
148			Self::Broadcast(_) => 2,
149			Self::InBlock(_) => 3,
150			Self::Retracted(_) => 4,
151			Self::FinalityTimeout(_) => 5,
152			Self::Finalized(_) => 6,
153			Self::Usurped(_) => 7,
154			Self::Dropped => 8,
155			Self::Invalid => 9,
156		}
157	}
158
159	pub fn is_expected(&self) -> Result<()> {
160		match self {
161			Self::Ready
162			| Self::Broadcast(_)
163			| Self::InBlock(_)
164			| Self::Retracted(_)
165			| Self::Finalized(_) => Ok(()),
166			Self::Future => Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Future)),
167			Self::FinalityTimeout(_) =>
168				Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::FinalityTimeout)),
169			Self::Usurped(_) => Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Usurped)),
170			Self::Dropped => Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Dropped)),
171			Self::Invalid => Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Invalid)),
172		}
173	}
174
175	/// Returns true if the input status has been reached (or overreached)
176	/// and false in case the status is not yet on the expected level.
177	pub fn reached_status(&self, status: XtStatus) -> bool {
178		self.as_u8() >= status as u8
179	}
180
181	pub fn get_maybe_block_hash(&self) -> Option<&BlockHash> {
182		match self {
183			Self::InBlock(block_hash) => Some(block_hash),
184			Self::Retracted(block_hash) => Some(block_hash),
185			Self::FinalityTimeout(block_hash) => Some(block_hash),
186			Self::Finalized(block_hash) => Some(block_hash),
187			_ => None,
188		}
189	}
190
191	/// Returns true if the Transaction reached its final Status
192	// See https://github.com/paritytech/polkadot-sdk/blob/289f5bbf7a45dc0380904a435464b15ec711ed03/substrate/client/transaction-pool/api/src/lib.rs#L161
193	pub fn is_final(&self) -> bool {
194		matches!(
195			self,
196			Self::Usurped(_)
197				| Self::Finalized(_)
198				| Self::FinalityTimeout(_)
199				| Self::Invalid
200				| Self::Dropped
201		)
202	}
203}
204
205// Exact structure from
206// https://github.com/paritytech/substrate/blob/master/client/rpc-api/src/state/helpers.rs
207// Adding manually so we don't need sc-rpc-api, which brings in async dependencies
208#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub struct ReadProof<Hash> {
211	/// Block hash used to generate the proof
212	pub at: Hash,
213	/// A proof used to prove that storage entries are included in the storage trie
214	pub proof: Vec<Bytes>,
215}
216
217#[cfg(test)]
218mod tests {
219	use super::{TransactionStatus as GenericTransactionStatus, *};
220	use sp_core::H256;
221
222	type TransactionStatus = GenericTransactionStatus<H256, H256>;
223
224	#[test]
225	fn test_xt_status_as_u8() {
226		assert_eq!(1, XtStatus::Ready as u8);
227		assert_eq!(2, XtStatus::Broadcast as u8);
228		assert_eq!(3, XtStatus::InBlock as u8);
229		assert_eq!(6, XtStatus::Finalized as u8);
230	}
231
232	#[test]
233	fn test_transaction_status_as_u8() {
234		assert_eq!(0, TransactionStatus::Future.as_u8());
235		assert_eq!(1, TransactionStatus::Ready.as_u8());
236		assert_eq!(2, TransactionStatus::Broadcast(vec![]).as_u8());
237		assert_eq!(3, TransactionStatus::InBlock(H256::random()).as_u8());
238		assert_eq!(4, TransactionStatus::Retracted(H256::random()).as_u8());
239		assert_eq!(5, TransactionStatus::FinalityTimeout(H256::random()).as_u8());
240		assert_eq!(6, TransactionStatus::Finalized(H256::random()).as_u8());
241		assert_eq!(7, TransactionStatus::Usurped(H256::random()).as_u8());
242		assert_eq!(8, TransactionStatus::Dropped.as_u8());
243		assert_eq!(9, TransactionStatus::Invalid.as_u8());
244	}
245
246	#[test]
247	fn test_transaction_status_is_expected() {
248		// Supported.
249		assert!(TransactionStatus::Ready.is_expected().is_ok());
250		assert!(TransactionStatus::Broadcast(vec![]).is_expected().is_ok());
251		assert!(TransactionStatus::InBlock(H256::random()).is_expected().is_ok());
252		assert!(TransactionStatus::Retracted(H256::random()).is_expected().is_ok());
253		assert!(TransactionStatus::Finalized(H256::random()).is_expected().is_ok());
254
255		// Not supported.
256		assert!(TransactionStatus::Future.is_expected().is_err());
257		assert!(TransactionStatus::FinalityTimeout(H256::random()).is_expected().is_err());
258		assert!(TransactionStatus::Usurped(H256::random()).is_expected().is_err());
259		assert!(TransactionStatus::Dropped.is_expected().is_err());
260		assert!(TransactionStatus::Invalid.is_expected().is_err());
261	}
262
263	#[test]
264	fn test_reached_xt_status_for_ready() {
265		let status = XtStatus::Ready;
266
267		// Has not yet reached XtStatus.
268		assert!(!TransactionStatus::Future.reached_status(status));
269
270		// Reached XtStatus.
271		assert!(TransactionStatus::Ready.reached_status(status));
272		assert!(TransactionStatus::Broadcast(vec![]).reached_status(status));
273		assert!(TransactionStatus::InBlock(H256::random()).reached_status(status));
274		assert!(TransactionStatus::FinalityTimeout(H256::random()).reached_status(status));
275		assert!(TransactionStatus::Finalized(H256::random()).reached_status(status));
276		assert!(TransactionStatus::Retracted(H256::random()).reached_status(status));
277		assert!(TransactionStatus::Usurped(H256::random()).reached_status(status));
278		assert!(TransactionStatus::Dropped.reached_status(status));
279		assert!(TransactionStatus::Invalid.reached_status(status));
280	}
281
282	#[test]
283	fn test_reached_xt_status_for_broadcast() {
284		let status = XtStatus::Broadcast;
285
286		// Has not yet reached XtStatus.
287		assert!(!TransactionStatus::Future.reached_status(status));
288		assert!(!TransactionStatus::Ready.reached_status(status));
289
290		// Reached XtStatus.
291		assert!(TransactionStatus::Broadcast(vec![]).reached_status(status));
292		assert!(TransactionStatus::InBlock(H256::random()).reached_status(status));
293		assert!(TransactionStatus::FinalityTimeout(H256::random()).reached_status(status));
294		assert!(TransactionStatus::Finalized(H256::random()).reached_status(status));
295		assert!(TransactionStatus::Retracted(H256::random()).reached_status(status));
296		assert!(TransactionStatus::Usurped(H256::random()).reached_status(status));
297		assert!(TransactionStatus::Dropped.reached_status(status));
298		assert!(TransactionStatus::Invalid.reached_status(status));
299	}
300
301	#[test]
302	fn test_reached_xt_status_for_in_block() {
303		let status = XtStatus::InBlock;
304
305		// Has not yet reached XtStatus.
306		assert!(!TransactionStatus::Future.reached_status(status));
307		assert!(!TransactionStatus::Ready.reached_status(status));
308		assert!(!TransactionStatus::Broadcast(vec![]).reached_status(status));
309
310		// Reached XtStatus.
311		assert!(TransactionStatus::InBlock(H256::random()).reached_status(status));
312		assert!(TransactionStatus::FinalityTimeout(H256::random()).reached_status(status));
313		assert!(TransactionStatus::Finalized(H256::random()).reached_status(status));
314		assert!(TransactionStatus::Retracted(H256::random()).reached_status(status));
315		assert!(TransactionStatus::Usurped(H256::random()).reached_status(status));
316		assert!(TransactionStatus::Dropped.reached_status(status));
317		assert!(TransactionStatus::Invalid.reached_status(status));
318	}
319
320	#[test]
321	fn test_reached_xt_status_for_finalized() {
322		let status = XtStatus::Finalized;
323
324		// Has not yet reached XtStatus.
325		assert!(!TransactionStatus::Future.reached_status(status));
326		assert!(!TransactionStatus::Ready.reached_status(status));
327		assert!(!TransactionStatus::Broadcast(vec![]).reached_status(status));
328		assert!(!TransactionStatus::InBlock(H256::random()).reached_status(status));
329		assert!(!TransactionStatus::Retracted(H256::random()).reached_status(status));
330		assert!(!TransactionStatus::FinalityTimeout(H256::random()).reached_status(status));
331
332		// Reached XtStatus.
333		assert!(TransactionStatus::Finalized(H256::random()).reached_status(status));
334		assert!(TransactionStatus::Usurped(H256::random()).reached_status(status));
335		assert!(TransactionStatus::Dropped.reached_status(status));
336		assert!(TransactionStatus::Invalid.reached_status(status));
337	}
338
339	#[test]
340	fn encode_decode_extrinsic_report() {
341		let hash = H256::random();
342		let block_hash = H256::random();
343		let status = TransactionStatus::InBlock(block_hash);
344		// RawEventDetails Encoding / Decoding is already tested separately, so we don't need to retest here.
345		let report = ExtrinsicReport::new(hash, Some(block_hash), status, None);
346
347		let encoded = report.encode();
348		let decoded = ExtrinsicReport::<H256>::decode(&mut encoded.as_slice()).unwrap();
349
350		assert_eq!(report, decoded);
351	}
352}