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