Skip to main content

avail_rust_client/block/
events.rs

1use crate::{Client, Error, UserError, block::shared::BlockContext};
2use avail_rust_core::{
3	HasHeader, RpcError, TransactionEventDecodable, avail,
4	rpc::{self, BlockPhaseEvent, PhaseEvent},
5	types::{HashStringNumber, RuntimePhase, substrate::Weight},
6};
7
8/// Helper for retrieving events scoped to a specific block.
9pub struct BlockEventsQuery {
10	ctx: BlockContext,
11}
12
13impl BlockEventsQuery {
14	/// Creates an event view for the given block.
15	///
16	/// # Parameters
17	/// - `client`: RPC client used to fetch event data.
18	/// - `block_id`: Identifier convertible into `HashStringNumber`.
19	///
20	/// # Returns
21	/// - `Self`: Helper for retrieving events scoped to the block.
22	pub fn new(client: Client, block_id: impl Into<HashStringNumber>) -> Self {
23		BlockEventsQuery { ctx: BlockContext::new(client, block_id.into()) }
24	}
25
26	/// Returns events emitted by a specific extrinsic index.
27	///
28	/// # Parameters
29	/// - `tx_index`: Index of the extrinsic whose events should be returned.
30	///
31	/// # Returns
32	/// - `Ok(AllEvents)`: Wrapper around events emitted by the extrinsic (may be empty).
33	/// - `Err(Error)`: RPC retrieval or event decoding failed.
34	///
35	/// # Side Effects
36	/// - Issues RPC requests for event data and may retry as configured.
37	pub async fn extrinsic(&self, tx_index: u32) -> Result<BlockEvents, Error> {
38		let events = self.all(tx_index.into()).await?;
39		Ok(BlockEvents::new(events))
40	}
41
42	/// Returns system-level events that are not tied to extrinsics.
43	///
44	/// # Returns
45	/// - `Ok(AllEvents)`: Wrapper around system events (may be empty).
46	/// - `Err(Error)`: RPC retrieval or event decoding failed.
47	///
48	/// # Side Effects
49	/// - Issues RPC requests for event data and may retry as configured.
50	pub async fn system(&self) -> Result<BlockEvents, Error> {
51		let events = self.all(rpc::EventFilter::OnlyNonExtrinsics).await?;
52		let events: Vec<BlockEvent> = events
53			.into_iter()
54			.filter(|x| x.phase.extrinsic_index().is_none())
55			.collect();
56
57		Ok(BlockEvents::new(events))
58	}
59
60	/// Fetches all events for the block using the given filter.
61	///
62	/// # Parameters
63	/// - `filter`: Filter describing which phases or extrinsics to include.
64	///
65	/// # Returns
66	/// - `Ok(Vec<Event>)`: Zero or more events matching the filter.
67	/// - `Err(Error)`: RPC retrieval or event decoding failed.
68	///
69	/// # Side Effects
70	/// - Issues RPC requests for event data and may retry as configured.
71	pub async fn all(&self, filter: rpc::EventFilter) -> Result<Vec<BlockEvent>, Error> {
72		let opts = rpc::EventOpts {
73			filter: Some(filter),
74			enable_encoding: Some(true),
75			enable_decoding: Some(false),
76		};
77
78		let block_id = self.ctx.block_id.clone();
79		let chain = self.ctx.chain();
80		let block_phase_events = chain.system_fetch_events(block_id, opts).await?;
81
82		let mut result: Vec<BlockEvent> = Vec::new();
83		for block_phase_event in block_phase_events {
84			let phase = block_phase_event.phase;
85
86			for phase_event in block_phase_event.events {
87				result.push(BlockEvent::from_phase_event(phase_event, phase)?);
88			}
89		}
90
91		Ok(result)
92	}
93
94	/// Fetches raw event data with full RPC control.
95	///
96	/// # Parameters
97	/// - `opts`: RPC options specifying filters and encoding preferences.
98	///
99	/// # Returns
100	/// - `Ok(Vec<BlockPhaseEvent>)`: Raw events grouped by block phase.
101	/// - `Err(Error)`: RPC retrieval failed.
102	///
103	/// # Side Effects
104	/// - Issues RPC requests for event data and may retry as configured.
105	pub async fn raw(&self, opts: rpc::EventOpts) -> Result<Vec<BlockPhaseEvent>, Error> {
106		let block_id = self.ctx.block_id.clone();
107		let chain = self.ctx.chain();
108
109		chain.system_fetch_events(block_id, opts).await
110	}
111
112	/// Overrides retry behaviour for event lookups.
113	///
114	/// # Parameters
115	/// - `value`: Retry override (`Some(true)` to force retries, `Some(false)` to disable, `None` to inherit).
116	///
117	/// # Returns
118	/// - `()`: The new retry preference is stored.
119	///
120	/// # Side Effects
121	/// - Updates internal state so future RPC requests honour the override.
122	pub fn set_retry_on_error(&mut self, value: Option<bool>) {
123		self.ctx.set_retry_on_error(value);
124	}
125
126	/// Reports whether event queries retry after RPC errors.
127	///
128	/// # Returns
129	/// - `true`: Retries are enabled either explicitly or via the client default.
130	/// - `false`: Retries are disabled.
131	pub fn should_retry_on_error(&self) -> bool {
132		self.ctx.should_retry_on_error()
133	}
134
135	/// Aggregates weight consumed by extrinsics using emitted events.
136	///
137	/// # Returns
138	/// - `Ok(Weight)`: Summed weights derived from `ExtrinsicSuccess` and `ExtrinsicFailed` events.
139	/// - `Err(Error)`: Event retrieval or decoding failed.
140	///
141	/// # Side Effects
142	/// - Issues RPC requests for event data and may retry as configured.
143	pub async fn extrinsic_weight(&self) -> Result<Weight, Error> {
144		use avail::system::events::{ExtrinsicFailed, ExtrinsicSuccess};
145
146		let mut weight = Weight::default();
147		let events = self.all(rpc::EventFilter::OnlyExtrinsics).await?;
148		for event in events {
149			if event.phase.extrinsic_index().is_none() {
150				continue;
151			}
152
153			let header = (event.pallet_id, event.variant_id);
154			if header == ExtrinsicSuccess::HEADER_INDEX {
155				let e = ExtrinsicSuccess::from_event(event.data).map_err(Error::Other)?;
156				weight.ref_time += e.dispatch_info.weight.ref_time;
157				weight.proof_size += e.dispatch_info.weight.proof_size;
158			} else if header == ExtrinsicFailed::HEADER_INDEX {
159				let e = ExtrinsicFailed::from_event(event.data).map_err(Error::Other)?;
160				weight.ref_time += e.dispatch_info.weight.ref_time;
161				weight.proof_size += e.dispatch_info.weight.proof_size;
162			}
163		}
164
165		Ok(weight)
166	}
167
168	/// Counts events emitted by this block.
169	///
170	/// # Returns
171	/// - `Ok(u32)`: Number of events emitted in the block.
172	/// - `Err(Error)`: RPC retrieval failed.
173	///
174	/// # Side Effects
175	/// - Issues an RPC request and may retry as configured.
176	pub async fn event_count(&self) -> Result<usize, Error> {
177		self.ctx.event_count().await
178	}
179}
180
181/// Event emitted during block execution with contextual metadata.
182#[derive(Debug, Clone)]
183pub struct BlockEvent {
184	/// Phase of block execution in which the event occurred.
185	pub phase: RuntimePhase,
186	/// Sequential index of the event within the phase.
187	pub index: u32,
188	/// Identifier of the emitting pallet.
189	pub pallet_id: u8,
190	/// Identifier of the variant inside the pallet.
191	pub variant_id: u8,
192	/// SCALE-encoded payload containing event data.
193	pub data: String,
194}
195
196impl BlockEvent {
197	/// Converts a raw phase event into a typed [`BlockEvent`].
198	///
199	/// # Arguments
200	/// * `event` - Raw event fetched from the node.
201	/// * `phase` - Runtime phase during which the event occurred.
202	///
203	/// # Returns
204	/// Returns the converted event or an error when encoded data is missing.
205	pub fn from_phase_event(mut event: PhaseEvent, phase: RuntimePhase) -> Result<Self, Error> {
206		let Some(data) = event.encoded_data.take() else {
207			return Err(RpcError::ExpectedData("The node did not return encoded data for this event.".into()).into());
208		};
209
210		let e = BlockEvent {
211			index: event.index,
212			pallet_id: event.pallet_id,
213			variant_id: event.variant_id,
214			data: data.clone(),
215			phase,
216		};
217
218		Ok(e)
219	}
220}
221
222/// Collection of block events with helpers for querying by header.
223#[derive(Debug, Clone)]
224pub struct BlockEvents {
225	/// Collection of decoded events preserved in original order.
226	pub events: Vec<BlockEvent>,
227}
228
229impl BlockEvents {
230	/// Wraps decoded events.
231	///
232	/// # Parameters
233	/// - `events`: Collection of decoded events to wrap.
234	///
235	/// # Returns
236	/// - `Self`: Wrapper exposing helper methods for event queries.
237	pub fn new(events: Vec<BlockEvent>) -> Self {
238		Self { events }
239	}
240
241	/// Returns the first event matching the requested type.
242	///
243	/// # Returns
244	/// - `Some(T)`: First event decoded as the requested type.
245	/// - `None`: No matching event was found or decoding failed.
246	pub fn first<T: HasHeader + codec::Decode>(&self) -> Option<T> {
247		let event = self
248			.events
249			.iter()
250			.find(|x| x.pallet_id == T::HEADER_INDEX.0 && x.variant_id == T::HEADER_INDEX.1);
251		let event = event?;
252
253		T::from_event(&event.data).ok()
254	}
255
256	/// Returns the last event matching the requested type.
257	///
258	/// # Returns
259	/// - `Some(T)`: Last event decoded as the requested type.
260	/// - `None`: No matching event was found or decoding failed.
261	pub fn last<T: HasHeader + codec::Decode>(&self) -> Option<T> {
262		let event = self
263			.events
264			.iter()
265			.rev()
266			.find(|x| x.pallet_id == T::HEADER_INDEX.0 && x.variant_id == T::HEADER_INDEX.1);
267		let event = event?;
268
269		T::from_event(&event.data).ok()
270	}
271
272	/// Returns every event matching the requested type.
273	///
274	/// # Returns
275	/// - `Ok(Vec<T>)`: Zero or more events decoded as the requested type.
276	/// - `Err(Error)`: Event decoding failed.
277	pub fn all<T: HasHeader + codec::Decode>(&self) -> Result<Vec<T>, Error> {
278		let mut result = Vec::new();
279		for event in &self.events {
280			if event.pallet_id != T::HEADER_INDEX.0 || event.variant_id != T::HEADER_INDEX.1 {
281				continue;
282			}
283
284			let decoded = T::from_event(event.data.as_str()).map_err(|x| Error::User(UserError::Decoding(x)))?;
285			result.push(decoded);
286		}
287
288		Ok(result)
289	}
290
291	/// Checks if an `ExtrinsicSuccess` event exists.
292	///
293	/// # Returns
294	/// - `true`: At least one `ExtrinsicSuccess` event is present.
295	/// - `false`: No such events were recorded.
296	pub fn is_extrinsic_success_present(&self) -> bool {
297		self.is_present::<avail::system::events::ExtrinsicSuccess>()
298	}
299
300	/// Checks if an `ExtrinsicFailed` event exists.
301	///
302	/// # Returns
303	/// - `true`: At least one `ExtrinsicFailed` event is present.
304	/// - `false`: No such events were recorded.
305	pub fn is_extrinsic_failed_present(&self) -> bool {
306		self.is_present::<avail::system::events::ExtrinsicFailed>()
307	}
308
309	/// Returns whether a proxy call succeeded, when present.
310	///
311	/// # Returns
312	/// - `Some(true)`: A proxy call executed successfully.
313	/// - `Some(false)`: A proxy call executed but failed.
314	/// - `None`: No proxy execution event was recorded.
315	pub fn proxy_executed_successfully(&self) -> Option<bool> {
316		let executed = self.first::<avail::proxy::events::ProxyExecuted>()?;
317		Some(executed.result.is_ok())
318	}
319
320	/// Returns whether a multisig call succeeded, when present.
321	///
322	/// # Returns
323	/// - `Some(true)`: A multisig call executed successfully.
324	/// - `Some(false)`: A multisig call executed but failed.
325	/// - `None`: No multisig execution event was recorded.
326	pub fn multisig_executed_successfully(&self) -> Option<bool> {
327		let executed = self.first::<avail::multisig::events::MultisigExecuted>()?;
328		Some(executed.result.is_ok())
329	}
330
331	/// Returns true when at least one event of the given type exists.
332	///
333	/// # Returns
334	/// - `true`: At least one matching event exists.
335	/// - `false`: No matching events were recorded.
336	pub fn is_present<T: HasHeader>(&self) -> bool {
337		self.count::<T>() > 0
338	}
339
340	/// Returns true when the given pallet and variant combination appears.
341	///
342	/// # Parameters
343	/// - `pallet_id`: Target pallet identifier.
344	/// - `variant_id`: Target variant identifier.
345	///
346	/// # Returns
347	/// - `true`: At least one matching event exists.
348	/// - `false`: No matching events were recorded.
349	pub fn is_present_parts(&self, pallet_id: u8, variant_id: u8) -> bool {
350		self.count_parts(pallet_id, variant_id) > 0
351	}
352
353	/// Counts how many times the given event type appears.
354	///
355	/// # Returns
356	/// - `u32`: Number of matching events recorded.
357	pub fn count<T: HasHeader>(&self) -> u32 {
358		self.count_parts(T::HEADER_INDEX.0, T::HEADER_INDEX.1)
359	}
360
361	/// Counts how many events match the pallet and variant combo.
362	///
363	/// # Parameters
364	/// - `pallet_id`: Target pallet identifier.
365	/// - `variant_id`: Target variant identifier.
366	///
367	/// # Returns
368	/// - `u32`: Number of matching events recorded.
369	pub fn count_parts(&self, pallet_id: u8, variant_id: u8) -> u32 {
370		let mut count = 0;
371		self.events.iter().for_each(|x| {
372			if x.pallet_id == pallet_id && x.variant_id == variant_id {
373				count += 1
374			}
375		});
376
377		count
378	}
379
380	/// Returns the number of cached events.
381	///
382	/// # Returns
383	/// - `usize`: Total events stored in the wrapper.
384	pub fn len(&self) -> usize {
385		self.events.len()
386	}
387
388	/// Reports whether any events are cached.
389	///
390	/// # Returns
391	/// - `true`: The wrapper contains no events.
392	/// - `false`: At least one event is stored.
393	pub fn is_empty(&self) -> bool {
394		self.events.is_empty()
395	}
396}