Skip to main content

soil_rpc/v2/transaction/
event.rs

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