referrerpolicy=no-referrer-when-downgrade

sc_rpc_spec_v2/transaction/
event.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! The transaction's event returned as json compatible object.
20
21use serde::{Deserialize, Serialize};
22
23/// The transaction was included in a block of the chain.
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct TransactionBlock<Hash> {
27	/// The hash of the block the transaction was included into.
28	pub hash: Hash,
29	/// The index (zero-based) of the transaction within the body of the block.
30	pub index: usize,
31}
32
33/// The transaction could not be processed due to an error.
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct TransactionError {
37	/// Reason of the error.
38	pub error: String,
39}
40
41/// The transaction was dropped because of exceeding limits.
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct TransactionDropped {
45	/// Reason of the event.
46	pub error: String,
47}
48
49/// Possible transaction status events.
50///
51/// The status events can be grouped based on their kinds as:
52///
53/// 1. Runtime validated the transaction and it entered the pool:
54/// 		- `Validated`
55///
56/// 2. Leaving the pool:
57/// 		- `BestChainBlockIncluded`
58/// 		- `Invalid`
59///
60/// 3. Block finalized:
61/// 		- `Finalized`
62///
63/// 4. At any time:
64/// 		- `Dropped`
65/// 		- `Error`
66///
67/// The subscription's stream is considered finished whenever the following events are
68/// received: `Finalized`, `Error`, `Invalid` or `Dropped`. However, the user is allowed
69/// to unsubscribe at any moment.
70#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
71// We need to manually specify the trait bounds for the `Hash` trait to ensure `into` and
72// `from` still work.
73#[serde(bound(
74	serialize = "Hash: Serialize + Clone",
75	deserialize = "Hash: Deserialize<'de> + Clone"
76))]
77#[serde(into = "TransactionEventIR<Hash>", from = "TransactionEventIR<Hash>")]
78pub enum TransactionEvent<Hash> {
79	/// The transaction was validated by the runtime.
80	Validated,
81	/// The transaction was included in a best block of the chain.
82	///
83	/// # Note
84	///
85	/// This may contain `None` if the block is no longer a best
86	/// block of the chain.
87	BestChainBlockIncluded(Option<TransactionBlock<Hash>>),
88	/// The transaction was included in a finalized block.
89	Finalized(TransactionBlock<Hash>),
90	/// The transaction could not be processed due to an error.
91	Error(TransactionError),
92	/// The transaction is marked as invalid.
93	Invalid(TransactionError),
94	/// The client was not capable of keeping track of this transaction.
95	Dropped(TransactionDropped),
96}
97
98impl<Hash> TransactionEvent<Hash> {
99	/// Returns true if this is the last event emitted by the RPC subscription.
100	pub fn is_final(&self) -> bool {
101		matches!(
102			&self,
103			TransactionEvent::Finalized(_) |
104				TransactionEvent::Error(_) |
105				TransactionEvent::Invalid(_) |
106				TransactionEvent::Dropped(_)
107		)
108	}
109}
110
111/// Intermediate representation (IR) for the transaction events
112/// that handles block events only.
113///
114/// The block events require a JSON compatible interpretation similar to:
115///
116/// ```json
117/// { event: "EVENT", block: { hash: "0xFF", index: 0 } }
118/// ```
119///
120/// This IR is introduced to circumvent that the block events need to
121/// be serialized/deserialized with "tag" and "content", while other
122/// events only require "tag".
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125#[serde(tag = "event", content = "block")]
126enum TransactionEventBlockIR<Hash> {
127	/// The transaction was included in the best block of the chain.
128	BestChainBlockIncluded(Option<TransactionBlock<Hash>>),
129	/// The transaction was included in a finalized block of the chain.
130	Finalized(TransactionBlock<Hash>),
131}
132
133/// Intermediate representation (IR) for the transaction events
134/// that handles non-block events only.
135///
136/// The non-block events require a JSON compatible interpretation similar to:
137///
138/// ```json
139/// { event: "EVENT", num_peers: 0 }
140/// ```
141///
142/// This IR is introduced to circumvent that the block events need to
143/// be serialized/deserialized with "tag" and "content", while other
144/// events only require "tag".
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146#[serde(rename_all = "camelCase")]
147#[serde(tag = "event")]
148enum TransactionEventNonBlockIR {
149	Validated,
150	Error(TransactionError),
151	Invalid(TransactionError),
152	Dropped(TransactionDropped),
153}
154
155/// Intermediate representation (IR) used for serialization/deserialization of the
156/// [`TransactionEvent`] in a JSON compatible format.
157///
158/// Serde cannot mix `#[serde(tag = "event")]` with `#[serde(tag = "event", content = "block")]`
159/// for specific enum variants. Therefore, this IR is introduced to circumvent this
160/// restriction, while exposing a simplified [`TransactionEvent`] for users of the
161/// rust ecosystem.
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163#[serde(bound(serialize = "Hash: Serialize", deserialize = "Hash: Deserialize<'de>"))]
164#[serde(rename_all = "camelCase")]
165#[serde(untagged)]
166enum TransactionEventIR<Hash> {
167	Block(TransactionEventBlockIR<Hash>),
168	NonBlock(TransactionEventNonBlockIR),
169}
170
171impl<Hash> From<TransactionEvent<Hash>> for TransactionEventIR<Hash> {
172	fn from(value: TransactionEvent<Hash>) -> Self {
173		match value {
174			TransactionEvent::Validated => {
175				TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Validated)
176			},
177			TransactionEvent::BestChainBlockIncluded(event) => {
178				TransactionEventIR::Block(TransactionEventBlockIR::BestChainBlockIncluded(event))
179			},
180			TransactionEvent::Finalized(event) => {
181				TransactionEventIR::Block(TransactionEventBlockIR::Finalized(event))
182			},
183			TransactionEvent::Error(event) => {
184				TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Error(event))
185			},
186			TransactionEvent::Invalid(event) => {
187				TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Invalid(event))
188			},
189			TransactionEvent::Dropped(event) => {
190				TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Dropped(event))
191			},
192		}
193	}
194}
195
196impl<Hash> From<TransactionEventIR<Hash>> for TransactionEvent<Hash> {
197	fn from(value: TransactionEventIR<Hash>) -> Self {
198		match value {
199			TransactionEventIR::NonBlock(status) => match status {
200				TransactionEventNonBlockIR::Validated => TransactionEvent::Validated,
201				TransactionEventNonBlockIR::Error(event) => TransactionEvent::Error(event),
202				TransactionEventNonBlockIR::Invalid(event) => TransactionEvent::Invalid(event),
203				TransactionEventNonBlockIR::Dropped(event) => TransactionEvent::Dropped(event),
204			},
205			TransactionEventIR::Block(block) => match block {
206				TransactionEventBlockIR::Finalized(event) => TransactionEvent::Finalized(event),
207				TransactionEventBlockIR::BestChainBlockIncluded(event) => {
208					TransactionEvent::BestChainBlockIncluded(event)
209				},
210			},
211		}
212	}
213}
214
215#[cfg(test)]
216mod tests {
217	use super::*;
218	use sp_core::H256;
219
220	#[test]
221	fn validated_event() {
222		let event: TransactionEvent<()> = TransactionEvent::Validated;
223		let ser = serde_json::to_string(&event).unwrap();
224
225		let exp = r#"{"event":"validated"}"#;
226		assert_eq!(ser, exp);
227
228		let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap();
229		assert_eq!(event_dec, event);
230	}
231
232	#[test]
233	fn best_chain_event() {
234		let event: TransactionEvent<()> = TransactionEvent::BestChainBlockIncluded(None);
235		let ser = serde_json::to_string(&event).unwrap();
236
237		let exp = r#"{"event":"bestChainBlockIncluded","block":null}"#;
238		assert_eq!(ser, exp);
239
240		let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap();
241		assert_eq!(event_dec, event);
242
243		let event: TransactionEvent<H256> =
244			TransactionEvent::BestChainBlockIncluded(Some(TransactionBlock {
245				hash: H256::from_low_u64_be(1),
246				index: 2,
247			}));
248		let ser = serde_json::to_string(&event).unwrap();
249
250		let exp = r#"{"event":"bestChainBlockIncluded","block":{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","index":2}}"#;
251		assert_eq!(ser, exp);
252
253		let event_dec: TransactionEvent<H256> = serde_json::from_str(exp).unwrap();
254		assert_eq!(event_dec, event);
255	}
256
257	#[test]
258	fn finalized_event() {
259		let event: TransactionEvent<H256> = TransactionEvent::Finalized(TransactionBlock {
260			hash: H256::from_low_u64_be(1),
261			index: 10,
262		});
263		let ser = serde_json::to_string(&event).unwrap();
264
265		let exp = r#"{"event":"finalized","block":{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","index":10}}"#;
266		assert_eq!(ser, exp);
267
268		let event_dec: TransactionEvent<H256> = serde_json::from_str(exp).unwrap();
269		assert_eq!(event_dec, event);
270	}
271
272	#[test]
273	fn error_event() {
274		let event: TransactionEvent<()> =
275			TransactionEvent::Error(TransactionError { error: "abc".to_string() });
276		let ser = serde_json::to_string(&event).unwrap();
277
278		let exp = r#"{"event":"error","error":"abc"}"#;
279		assert_eq!(ser, exp);
280
281		let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap();
282		assert_eq!(event_dec, event);
283	}
284
285	#[test]
286	fn invalid_event() {
287		let event: TransactionEvent<()> =
288			TransactionEvent::Invalid(TransactionError { error: "abc".to_string() });
289		let ser = serde_json::to_string(&event).unwrap();
290
291		let exp = r#"{"event":"invalid","error":"abc"}"#;
292		assert_eq!(ser, exp);
293
294		let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap();
295		assert_eq!(event_dec, event);
296	}
297
298	#[test]
299	fn dropped_event() {
300		let event: TransactionEvent<()> =
301			TransactionEvent::Dropped(TransactionDropped { error: "abc".to_string() });
302		let ser = serde_json::to_string(&event).unwrap();
303
304		let exp = r#"{"event":"dropped","error":"abc"}"#;
305		assert_eq!(ser, exp);
306
307		let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap();
308		assert_eq!(event_dec, event);
309	}
310}