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}