Skip to main content

avail_rust_client/block/
mod.rs

1pub mod encoded;
2pub mod events;
3pub mod extrinsic;
4pub mod extrinsic_options;
5pub mod shared;
6pub mod signed;
7
8pub use encoded::{BlockEncodedExtrinsic, BlockEncodedExtrinsicsQuery};
9pub use events::{BlockEvent, BlockEvents, BlockEventsQuery};
10pub use extrinsic::{BlockExtrinsic, BlockExtrinsicsQuery};
11pub use shared::BlockExtrinsicMetadata;
12pub use signed::BlockSignedExtrinsic;
13
14use crate::{
15	Client, Error,
16	block::{extrinsic_options::Options, shared::BlockContext},
17};
18use avail_rust_core::{
19	AccountId, AvailHeader, BlockInfo, HashNumber, avail,
20	grandpa::GrandpaJustification,
21	rpc::{self},
22	types::{
23		HashStringNumber,
24		substrate::{PerDispatchClassWeight, Weight},
25	},
26};
27
28/// High-level handle bound to a specific block id (height or hash).
29#[derive(Clone)]
30pub struct Block {
31	ctx: BlockContext,
32}
33
34impl Block {
35	/// Constructs a view over the block identified by `block_id`.
36	///
37	/// # Parameters
38	/// - `client`: RPC client used for follow-up queries.
39	/// - `block_id`: Block number, hash, or string convertible into `HashStringNumber`.
40	///
41	/// # Returns
42	/// - `Self`: Block helper bound to the supplied identifier.
43	pub fn new(client: Client, block_id: impl Into<HashStringNumber>) -> Self {
44		Block { ctx: BlockContext::new(client, block_id.into()) }
45	}
46
47	/// Returns a helper for retrieving encoded extrinsic payloads in this block.
48	///
49	/// # Returns
50	/// - `EncodedExtrinsics`: View over encoded extrinsic payloads and metadata.
51	pub fn encoded(&self) -> encoded::BlockEncodedExtrinsicsQuery {
52		encoded::BlockEncodedExtrinsicsQuery::new(self.ctx.client.clone(), self.ctx.block_id.clone())
53	}
54
55	/// Returns a helper for decoding extrinsics contained in this block.
56	///
57	/// # Returns
58	/// - `Extrinsics`: View that decodes raw extrinsics into runtime calls.
59	pub fn extrinsics(&self) -> extrinsic::BlockExtrinsicsQuery {
60		extrinsic::BlockExtrinsicsQuery::new(self.ctx.client.clone(), self.ctx.block_id.clone())
61	}
62
63	// /// Returns a helper focused on signed extrinsics contained in this block.
64	// ///
65	// /// # Returns
66	// /// - `SignedExtrinsics`: View that exposes signed extrinsics for this block.
67	// pub fn signed(&self) -> signed::BlockSignedExtrinsicsQuery {
68	// 	signed::BlockSignedExtrinsicsQuery::new(self.ctx.client.clone(), self.ctx.block_id.clone())
69	// }
70
71	/// Fetches raw extrinsic metadata using the supplied filters.
72	///
73	/// # Arguments
74	/// * `opts` - Filters controlling which extrinsics are returned and how they are encoded.
75	///
76	/// # Returns
77	/// Returns the list of matching extrinsic metadata entries.
78	pub async fn extrinsic_infos(&self, opts: rpc::ExtrinsicOpts) -> Result<Vec<rpc::ExtrinsicInfo>, Error> {
79		let chain = self.ctx.chain();
80		chain.system_fetch_extrinsics(self.ctx.block_id.clone(), opts).await
81	}
82
83	/// Returns an event helper scoped to this block.
84	///
85	/// # Returns
86	/// - `Events`: View that fetches events emitted by the block.
87	pub fn events(&self) -> events::BlockEventsQuery {
88		events::BlockEventsQuery::new(self.ctx.client.clone(), self.ctx.block_id.clone())
89	}
90
91	/// Overrides the retry behaviour for future RPC calls made through this helper.
92	///
93	/// # Parameters
94	/// - `value`: `Some(true)` to force retries, `Some(false)` to disable retries, `None` to inherit the client default.
95	///
96	/// # Returns
97	/// - `()`: The override is stored for subsequent operations.
98	///
99	/// # Side Effects
100	/// - Updates the internal retry setting used by follow-up RPC calls.
101	pub fn set_retry_on_error(&mut self, value: Option<bool>) {
102		self.ctx.set_retry_on_error(value);
103	}
104
105	/// Fetches the GRANDPA justification associated with this block, if any.
106	///
107	/// # Returns
108	/// - `Ok(Some(GrandpaJustification))`: The runtime provided a justification.
109	/// - `Ok(None)`: No justification exists for the requested block.
110	/// - `Err(Error)`: Resolving the block identifier or performing the RPC call failed.
111	///
112	/// # Side Effects
113	/// - Performs RPC calls to resolve the block and download the justification.
114	pub async fn justification(&self) -> Result<Option<GrandpaJustification>, Error> {
115		let block_id: HashNumber = self.ctx.hash_number()?;
116		let chain = self.ctx.chain();
117		let at = match block_id {
118			HashNumber::Hash(h) => chain
119				.block_height(h)
120				.await?
121				.ok_or(Error::Other("Failed to find block from the provided hash".into()))?,
122			HashNumber::Number(n) => n,
123		};
124
125		chain.grandpa_block_justification(at).await.map_err(|e| e.into())
126	}
127
128	/// Reports whether this helper retries RPC failures.
129	///
130	/// # Returns
131	/// - `true`: Retries are enabled either explicitly or via the client default.
132	/// - `false`: Retries are disabled.
133	pub fn should_retry_on_error(&self) -> bool {
134		self.ctx.should_retry_on_error()
135	}
136
137	/// Retrieves the UNIX timestamp stored in this block's runtime `timestamp.set` extrinsic.
138	///
139	/// # Returns
140	/// - `Ok(u64)`: Timestamp provided by the block's timestamp extrinsic.
141	/// - `Err(Error)`: The timestamp extrinsic was missing or the RPC lookup failed.
142	///
143	/// # Side Effects
144	/// - Fetches extrinsic data over RPC, honouring the retry configuration.
145	pub async fn timestamp(&self) -> Result<u64, Error> {
146		let query = self.extrinsics();
147		let timestamp = query.first::<avail::timestamp::tx::Set>(Default::default()).await?;
148		let Some(timestamp) = timestamp else {
149			return Err(Error::Other(std::format!("No timestamp transaction found in block: {:?}", self.ctx.block_id)));
150		};
151
152		Ok(timestamp.call.now)
153	}
154
155	/// Fetches high-level metadata (number, hash, parent) for this block.
156	///
157	/// # Returns
158	/// - `Ok(BlockInfo)`: Metadata describing the block.
159	/// - `Err(Error)`: Resolving the block identifier or making the RPC call failed.
160	///
161	/// # Side Effects
162	/// - Performs an RPC call and may retry according to the retry policy.
163	pub async fn info(&self) -> Result<BlockInfo, Error> {
164		let chain = self.ctx.chain();
165		chain.block_info_from(self.ctx.block_id.clone()).await
166	}
167
168	/// Fetches the header associated with this block.
169	///
170	/// # Returns
171	/// - `Ok(AvailHeader)`: Header returned by the node.
172	/// - `Err(Error)`: Resolving the block identifier or performing the RPC call failed.
173	///
174	/// # Side Effects
175	/// - Performs an RPC call and may retry according to the retry policy.
176	pub async fn header(&self) -> Result<AvailHeader, Error> {
177		self.ctx.header().await
178	}
179
180	/// Fetches the author recorded for this block.
181	///
182	/// # Returns
183	/// - `Ok(AccountId)`: Account identifier attributed as the block author.
184	/// - `Err(Error)`: Resolving the block identifier or performing the RPC call failed.
185	///
186	/// # Side Effects
187	/// - Performs an RPC call and may retry according to the retry policy.
188	pub async fn author(&self) -> Result<AccountId, Error> {
189		let chain = self.ctx.chain();
190		chain.block_author(self.ctx.block_id.clone()).await
191	}
192
193	/// Counts how many extrinsics the block contains.
194	///
195	/// # Returns
196	/// - `Ok(u32)`: Number of extrinsics recorded in the block.
197	/// - `Err(Error)`: Enumerating the extrinsics failed.
198	///
199	/// # Side Effects
200	/// - Fetches extrinsic metadata over RPC, honouring the retry configuration.
201	pub async fn extrinsic_count(&self) -> Result<usize, Error> {
202		let mut encoded = self.encoded();
203		encoded.set_retry_on_error(Some(self.ctx.should_retry_on_error()));
204		encoded.count(Options::new()).await
205	}
206
207	/// Counts how many events were emitted in this block.
208	///
209	/// # Returns
210	/// - `Ok(u32)`: Number of events exposed by the node.
211	/// - `Err(Error)`: The RPC request failed.
212	///
213	/// # Side Effects
214	/// - Performs an RPC call and may retry according to the retry policy.
215	pub async fn event_count(&self) -> Result<usize, Error> {
216		self.ctx.event_count().await
217	}
218
219	/// Retrieves the dispatch-class weight totals reported for this block.
220	///
221	/// # Returns
222	/// - `Ok(PerDispatchClassWeight)`: Weight data grouped by dispatch class.
223	/// - `Err(Error)`: Fetching the weight via RPC failed.
224	///
225	/// # Side Effects
226	/// - Performs an RPC call and may retry according to the retry policy.
227	pub async fn weight(&self) -> Result<PerDispatchClassWeight, Error> {
228		let chain = self.ctx.chain();
229		chain.block_weight(self.ctx.block_id.clone()).await
230	}
231
232	/// Aggregates the weight consumed by extrinsics, based on success and failure events.
233	///
234	/// # Returns
235	/// - `Ok(Weight)`: Sum of weights observed in extrinsic success or failure events.
236	/// - `Err(Error)`: Fetching or decoding the event data failed.
237	///
238	/// # Side Effects
239	/// - Fetches block events over RPC, honouring the retry configuration.
240	pub async fn extrinsic_weight(&self) -> Result<Weight, Error> {
241		self.events().extrinsic_weight().await
242	}
243}
244
245#[cfg(test)]
246pub mod test {
247	use avail_rust_core::{EncodeSelector, HasHeader, avail, rpc::ExtrinsicOpts};
248
249	use crate::{Client, TURING_ENDPOINT};
250
251	#[tokio::test]
252	async fn block_weight_test() {
253		let client = Client::new(TURING_ENDPOINT).await.unwrap();
254		let block = client.block(2042866);
255
256		let extrinsic_weight = block.extrinsic_weight().await.unwrap();
257		let block_weight = block.weight().await.unwrap();
258
259		assert_eq!(extrinsic_weight.ref_time, 39142682750);
260		assert_eq!(extrinsic_weight.proof_size, 1493);
261		assert_eq!(block_weight.normal.ref_time, 14095070750);
262		assert_eq!(block_weight.normal.proof_size, 0);
263		assert_eq!(block_weight.operational.ref_time, 0);
264		assert_eq!(block_weight.operational.proof_size, 0);
265		assert_eq!(block_weight.mandatory.ref_time, 27979773000);
266		assert_eq!(block_weight.mandatory.proof_size, 116950);
267	}
268
269	#[tokio::test]
270	async fn block_info_test() {
271		let client = Client::new(TURING_ENDPOINT).await.unwrap();
272		let block = client.block(2042866);
273
274		let info = block.info().await.unwrap();
275
276		assert_eq!(info.height, 2042866);
277		assert_eq!(
278			std::format!("{:?}", info.hash),
279			"0x66f2847020781416f98137f0c9ed7416e8e9d993d22924f36c6f16e066641429"
280		);
281	}
282
283	#[tokio::test]
284	async fn block_event_count_test() {
285		let client = Client::new(TURING_ENDPOINT).await.unwrap();
286		let block = client.block(2042866);
287		let count = block.event_count().await.unwrap();
288		assert_eq!(count, 10);
289	}
290
291	#[tokio::test]
292	async fn block_extrinsic_count_test() {
293		let client = Client::new(TURING_ENDPOINT).await.unwrap();
294		let block = client.block(2042866);
295
296		let count = block.extrinsic_count().await.unwrap();
297		assert_eq!(count, 3);
298	}
299
300	#[tokio::test]
301	async fn block_author_test() {
302		let client = Client::new(TURING_ENDPOINT).await.unwrap();
303		let block = client.block(2042866);
304
305		let author = block.author().await.unwrap();
306		assert_eq!(author.to_string(), String::from("5Fuedf79TqB6mMWzhu8aazzfPX1mawedb7rLuHpv6iYK2Z6c"));
307	}
308
309	#[tokio::test]
310	async fn block_timestamp_test() {
311		let client = Client::new(TURING_ENDPOINT).await.unwrap();
312		let block = client.block(2042866);
313
314		let timestamp = block.timestamp().await.unwrap();
315		assert_eq!(timestamp, 1752582560000);
316	}
317
318	#[tokio::test]
319	async fn block_header_test() {
320		let client = Client::new(TURING_ENDPOINT).await.unwrap();
321		let block = client.block(2042866);
322
323		let header = block.header().await.unwrap();
324		assert_eq!(header.number, 2042866);
325		assert_eq!(
326			std::format!("{:?}", header.hash()),
327			"0x66f2847020781416f98137f0c9ed7416e8e9d993d22924f36c6f16e066641429"
328		);
329		assert_eq!(
330			std::format!("{:?}", header.parent_hash),
331			"0xfca317c08a9b86bf8b8ae04df0bde83db31fab1b455ebd2317f7eca15e1d688e"
332		)
333	}
334
335	#[tokio::test]
336	async fn block_justification_test() {
337		let client = Client::new(TURING_ENDPOINT).await.unwrap();
338
339		let block = client.block(1900031);
340		let just = block.justification().await.unwrap();
341		assert!(just.is_none());
342
343		let block = client.block(1900032);
344		let just = block.justification().await.unwrap();
345		assert!(just.is_some());
346	}
347
348	#[tokio::test]
349	async fn block_extrinsic_infos_test() {
350		let client = Client::new(TURING_ENDPOINT).await.unwrap();
351
352		let block = client.block(2042863);
353
354		// All
355		let infos = block
356			.extrinsic_infos(ExtrinsicOpts::new().encode_as(EncodeSelector::None))
357			.await
358			.unwrap();
359		assert_eq!(infos.len(), 4);
360
361		// App Id
362		let infos = block
363			.extrinsic_infos(ExtrinsicOpts::new().encode_as(EncodeSelector::None).app_id(428))
364			.await
365			.unwrap();
366		assert_eq!(infos.len(), 1);
367
368		// Submit Data
369		let infos = block
370			.extrinsic_infos(
371				ExtrinsicOpts::new()
372					.encode_as(EncodeSelector::None)
373					.filter(avail::data_availability::tx::SubmitData::HEADER_INDEX),
374			)
375			.await
376			.unwrap();
377		assert_eq!(infos.len(), 1);
378
379		// SS58 address
380		// Submit Data
381		let infos = block
382			.extrinsic_infos(
383				ExtrinsicOpts::new()
384					.encode_as(EncodeSelector::None)
385					.ss58_address("5CAC4rBKRKJi83uCJzeC7PzS27sP8Esfymbeq5jFEFPieyJm"),
386			)
387			.await
388			.unwrap();
389		assert_eq!(infos.len(), 1);
390	}
391}