avail_rust_client/subscription/
block.rs

1//! Subscription adapters focused on block headers, bodies, and emitted events.
2
3use crate::{
4	AvailHeader, Client, LegacyBlock, RpcError, Sub,
5	block::{Block, events::BlockEventsQuery},
6};
7use avail_rust_core::{H256, rpc::BlockPhaseEvent};
8use std::time::Duration;
9
10/// Subscription wrapper that streams [`LegacyBlock`] values.
11#[derive(Clone)]
12pub struct LegacyBlockSub {
13	sub: Sub,
14}
15
16impl LegacyBlockSub {
17	/// Creates a subscription that yields legacy blocks as you iterate.
18	///
19	/// The client is cloned and no network traffic occurs until [`LegacyBlockSub::next`] or
20	/// [`LegacyBlockSub::prev`] is awaited.
21	///
22	/// # Arguments
23	/// * `client` - Client used to drive the subscription.
24	///
25	/// # Returns
26	/// Returns a [`LegacyBlockSub`] ready to iterate over legacy blocks.
27	pub fn new(client: Client) -> Self {
28		Self { sub: Sub::new(client) }
29	}
30
31	/// Fetches the next legacy block; rewinds the cursor if the RPC call fails.
32	///
33	/// # Returns
34	/// - `Ok(Some(LegacyBlock))` when the node provides a block for the current cursor.
35	/// - `Ok(None)` when the block exists but contains no legacy payload.
36	/// - `Err(RpcError)` when the RPC call fails; the internal block height resets so a retry will
37	///   reattempt the same block.
38	///
39	/// # Errors
40	/// Returns `Err(RpcError)` when the underlying RPC request fails.
41	pub async fn next(&mut self) -> Result<Option<LegacyBlock>, RpcError> {
42		let info = self.sub.next().await?;
43		let block = match self
44			.sub
45			.client_ref()
46			.chain()
47			.retry_on(Some(self.sub.should_retry_on_error()), Some(true))
48			.legacy_block(Some(info.hash))
49			.await
50		{
51			Ok(x) => x,
52			Err(err) => {
53				// Revet block height if we fail to fetch block
54				self.sub.set_block_height(info.height);
55				return Err(err);
56			},
57		};
58		Ok(block)
59	}
60
61	/// Fetches the previous legacy block; rewinds the cursor if the RPC call fails.
62	///
63	/// The result semantics mirror [`LegacyBlockSub::next`].
64	///
65	/// # Returns
66	/// - `Ok(Some(LegacyBlock))` when the previous block is available.
67	/// - `Ok(None)` when the block exists but contains no legacy payload.
68	/// - `Err(RpcError)` when the RPC call fails and the cursor is rewound.
69	///
70	/// # Errors
71	/// Returns `Err(RpcError)` when the underlying RPC request fails.
72	pub async fn prev(&mut self) -> Result<Option<LegacyBlock>, RpcError> {
73		let info = self.sub.prev().await?;
74		let block = match self
75			.sub
76			.client_ref()
77			.chain()
78			.retry_on(Some(self.sub.should_retry_on_error()), Some(true))
79			.legacy_block(Some(info.hash))
80			.await
81		{
82			Ok(x) => x,
83			Err(err) => {
84				// Revet block height if we fail to fetch block
85				self.sub.set_block_height(info.height);
86				return Err(err);
87			},
88		};
89		Ok(block)
90	}
91
92	/// Reports whether the subscription retries after RPC failures.
93	///
94	/// # Returns
95	/// Returns `true` when retries are enabled, otherwise `false`.
96	pub fn should_retry_on_error(&self) -> bool {
97		self.sub.should_retry_on_error()
98	}
99
100	/// Follow best blocks instead of finalized ones.
101	///
102	/// # Arguments
103	/// * `value` - `true` to follow best blocks, `false` for finalized blocks.
104	pub fn use_best_block(&mut self, value: bool) {
105		self.sub.use_best_block(value);
106	}
107
108	/// Jump the cursor to a specific starting height.
109	///
110	/// # Arguments
111	/// * `block_height` - Height used as the starting point for iteration.
112	pub fn set_block_height(&mut self, block_height: u32) {
113		self.sub.set_block_height(block_height);
114	}
115
116	/// Change how often we poll for new blocks when following the chain head.
117	///
118	/// # Arguments
119	/// * `value` - Poll interval used while tailing the head.
120	pub fn set_pool_rate(&mut self, value: Duration) {
121		self.sub.set_pool_rate(value);
122	}
123
124	/// Controls retry behaviour: `Some(true)` forces retries, `Some(false)` disables them, and `None`
125	/// keeps the client's default.
126	///
127	/// # Arguments
128	/// * `value` - Retry override applied to the underlying subscription.
129	pub fn set_retry_on_error(&mut self, value: Option<bool>) {
130		self.sub.set_retry_on_error(value);
131	}
132}
133
134#[derive(Clone)]
135pub struct BlockSubValue {
136	pub value: Block,
137	pub block_height: u32,
138	pub block_hash: H256,
139}
140
141/// Subscription wrapper that streams block handles (`Block`).
142#[derive(Clone)]
143pub struct BlockSub {
144	sub: Sub,
145}
146
147impl BlockSub {
148	/// Creates a subscription that yields [`Block`] handles as you iterate. The handlers can be
149	/// used to inspect extrinsics, events, or raw data.
150	///
151	/// # Arguments
152	/// * `client` - Client used to drive the subscription.
153	///
154	/// # Returns
155	/// Returns a [`BlockSub`] ready to iterate over blocks.
156	pub fn new(client: Client) -> Self {
157		Self { sub: Sub::new(client) }
158	}
159
160	/// Fetches the next block handle along with its `BlockInfo`.
161	///
162	/// # Returns
163	/// - `Ok(BlockSubValue)` when a block is available at the current cursor.
164	/// - `Err(RpcError)` when the underlying subscription fails to advance.
165	///
166	/// # Errors
167	/// Returns `Err(RpcError)` when fetching the next block fails.
168	pub async fn next(&mut self) -> Result<BlockSubValue, RpcError> {
169		let info = self.sub.next().await?;
170		let value = Block::new(self.sub.client_ref().clone(), info.hash);
171		Ok(BlockSubValue { value, block_hash: info.hash, block_height: info.height })
172	}
173
174	/// Fetches the previous block handle along with its `BlockInfo`.
175	///
176	/// Return semantics mirror [`BlockSub::next`].
177	///
178	/// # Returns
179	/// Returns the previous block handle and metadata, or `Err(RpcError)` on failure.
180	///
181	/// # Errors
182	/// Returns `Err(RpcError)` when fetching the previous block fails.
183	pub async fn prev(&mut self) -> Result<BlockSubValue, RpcError> {
184		let info = self.sub.prev().await?;
185		let value = Block::new(self.sub.client_ref().clone(), info.hash);
186		Ok(BlockSubValue { value, block_hash: info.hash, block_height: info.height })
187	}
188
189	/// Reports whether failed RPC calls will be retried.
190	pub fn should_retry_on_error(&self) -> bool {
191		self.sub.should_retry_on_error()
192	}
193
194	/// Follow best blocks instead of finalized ones.
195	///
196	/// # Arguments
197	/// * `value` - `true` to follow best blocks, `false` for finalized blocks.
198	pub fn use_best_block(&mut self, value: bool) {
199		self.sub.use_best_block(value);
200	}
201
202	/// Jump the cursor to a specific starting height.
203	///
204	/// # Arguments
205	/// * `block_height` - Height used as the starting point for iteration.
206	pub fn set_block_height(&mut self, block_height: u32) {
207		self.sub.set_block_height(block_height);
208	}
209
210	/// Change how often we poll for new blocks when tailing the head.
211	///
212	/// # Arguments
213	/// * `value` - Poll interval used while tailing the head.
214	pub fn set_pool_rate(&mut self, value: Duration) {
215		self.sub.set_pool_rate(value);
216	}
217
218	/// Controls retry behaviour: `Some(true)` forces retries, `Some(false)` disables them, and `None`
219	/// keeps the client's default.
220	///
221	/// # Arguments
222	/// * `value` - Retry override applied to the underlying subscription.
223	pub fn set_retry_on_error(&mut self, value: Option<bool>) {
224		self.sub.set_retry_on_error(value);
225	}
226}
227
228#[derive(Debug, Clone)]
229pub struct BlockEventsSubValue {
230	pub list: Vec<BlockPhaseEvent>,
231	pub block_height: u32,
232	pub block_hash: H256,
233}
234
235/// Subscription wrapper that streams [`BlockPhaseEvent`] lists.
236#[derive(Clone)]
237pub struct BlockEventsSub {
238	sub: Sub,
239	opts: avail_rust_core::rpc::EventOpts,
240}
241
242impl BlockEventsSub {
243	/// Creates a subscription that yields event batches filtered by the supplied options. No network
244	/// calls are made until [`BlockEventsSub::next`] is awaited.
245	pub fn new(client: Client, opts: avail_rust_core::rpc::EventOpts) -> Self {
246		Self { sub: Sub::new(client), opts }
247	}
248
249	/// Fetches the next block with matching events; rewinds on RPC failure.
250	///
251	/// # Returns
252	/// - `Ok(BlockEventsSubResult)` when events matching the configured filters are found.
253	/// - `Err(crate::Error)` when the RPC request fails; the internal cursor rewinds so the block can be
254	///   retried.
255	///
256	/// Empty event lists are skipped automatically.
257	pub async fn next(&mut self) -> Result<BlockEventsSubValue, crate::Error> {
258		loop {
259			let info = self.sub.next().await?;
260			let block = BlockEventsQuery::new(self.sub.client_ref().clone(), info.hash);
261			let events = match block.raw(self.opts.clone()).await {
262				Ok(x) => x,
263				Err(err) => {
264					// Revet block height if we fail to fetch events
265					self.sub.set_block_height(info.height);
266					return Err(err);
267				},
268			};
269
270			if events.is_empty() {
271				continue;
272			}
273
274			return Ok(BlockEventsSubValue {
275				list: events,
276				block_height: info.height,
277				block_hash: info.hash,
278			});
279		}
280	}
281
282	/// Replaces the filter options applied to subsequent `next` calls.
283	///
284	/// # Arguments
285	/// * `value` - New event filtering options supplied to the RPC query.
286	pub fn set_options(&mut self, value: avail_rust_core::rpc::EventOpts) {
287		self.opts = value;
288	}
289
290	/// Reports whether failed RPC calls will be retried.
291	pub fn should_retry_on_error(&self) -> bool {
292		self.sub.should_retry_on_error()
293	}
294
295	/// Follow best blocks instead of finalized ones.
296	pub fn use_best_block(&mut self, value: bool) {
297		self.sub.use_best_block(value);
298	}
299
300	/// Jump the cursor to a specific starting height.
301	pub fn set_block_height(&mut self, block_height: u32) {
302		self.sub.set_block_height(block_height);
303	}
304
305	/// Change how often the subscription polls for new blocks when following the head.
306	pub fn set_pool_rate(&mut self, value: Duration) {
307		self.sub.set_pool_rate(value);
308	}
309
310	/// Override the retry behaviour (`Some(true)` = force, `Some(false)` = disable).
311	pub fn set_retry_on_error(&mut self, value: Option<bool>) {
312		self.sub.set_retry_on_error(value);
313	}
314}
315
316/// Subscription that mirrors [`Sub`] but yields [`AvailHeader`].
317#[derive(Clone)]
318pub struct BlockHeaderSub {
319	sub: Sub,
320}
321
322impl BlockHeaderSub {
323	/// Creates a new [`AvailHeader`] subscription.
324	/// Creates a subscription that yields legacy blocks as you iterate.
325	///
326	/// The client is cloned and no network traffic occurs until [`LegacyBlockSub::next`] or
327	/// [`LegacyBlockSub::prev`] is awaited.
328	///
329	/// # Arguments
330	/// * `client` - Client used to drive the subscription.
331	///
332	/// # Returns
333	/// Returns a [`LegacyBlockSub`] ready to iterate over legacy blocks.
334	pub fn new(client: Client) -> Self {
335		Self { sub: Sub::new(client) }
336	}
337
338	/// Returns the next [`AvailHeader`] matching the underlying [`Sub::next`] cursor.
339	///
340	/// When the RPC call fails, the internal height is rewound so the same block can be retried.
341	pub async fn next(&mut self) -> Result<Option<AvailHeader>, crate::Error> {
342		let info = self.sub.next().await?;
343		let header = match self
344			.sub
345			.client_ref()
346			.chain()
347			.retry_on(Some(self.sub.should_retry_on_error()), Some(true))
348			.block_header(Some(info.hash))
349			.await
350		{
351			Ok(x) => x,
352			Err(err) => {
353				// Revet block height if we fail to fetch block header
354				self.sub.set_block_height(info.height);
355				return Err(err);
356			},
357		};
358
359		Ok(header)
360	}
361
362	/// Returns the previous [`AvailHeader`] using [`Sub::prev`] as the cursor source.
363	///
364	/// When the RPC call fails, the internal height is rewound so the same block can be retried.
365	pub async fn prev(&mut self) -> Result<Option<AvailHeader>, crate::Error> {
366		let info = self.sub.prev().await?;
367		let header = match self
368			.sub
369			.client_ref()
370			.chain()
371			.retry_on(Some(self.sub.should_retry_on_error()), Some(true))
372			.block_header(Some(info.hash))
373			.await
374		{
375			Ok(x) => x,
376			Err(err) => {
377				// Revet block height if we fail to fetch block header
378				self.sub.set_block_height(info.height);
379				return Err(err);
380			},
381		};
382
383		Ok(header)
384	}
385
386	/// Reports whether the subscription retries after RPC failures.
387	pub fn should_retry_on_error(&self) -> bool {
388		self.sub.should_retry_on_error()
389	}
390
391	/// Follow best blocks instead of finalized ones.
392	pub fn use_best_block(&mut self, value: bool) {
393		self.sub.use_best_block(value);
394	}
395
396	/// Jump the cursor to a specific starting height.
397	pub fn set_block_height(&mut self, block_height: u32) {
398		self.sub.set_block_height(block_height);
399	}
400
401	/// Change how often we poll for new blocks.
402	pub fn set_pool_rate(&mut self, value: Duration) {
403		self.sub.set_pool_rate(value);
404	}
405
406	/// Choose whether this subscription should retry after RPC failures.
407	pub fn set_retry_on_error(&mut self, value: Option<bool>) {
408		self.sub.set_retry_on_error(value);
409	}
410}
411
412#[cfg(test)]
413mod tests {
414	use super::*;
415	use crate::{clients::mock_client::MockClient, error::Error, prelude::*, subxt_rpcs::RpcClient};
416
417	// This test will be by flaky and that is OK.
418	#[tokio::test]
419	async fn block_sub_test() -> Result<(), Error> {
420		let client = Client::new(TURING_ENDPOINT).await?;
421
422		//
423		// Test Case 1: Latest Block Height + Next
424		//
425		let mut sub = BlockSub::new(client.clone());
426
427		let block_height = client.finalized().block_height().await?;
428		let value = sub.next().await?;
429		assert_eq!(value.block_height, block_height);
430
431		//
432		// Test Case 2: Latest Block Height + Prev
433		//
434		let mut sub = BlockSub::new(client.clone());
435
436		let block_height = client.finalized().block_height().await?;
437		let value = sub.prev().await?;
438		assert_eq!(value.block_height, block_height - 1);
439
440		//
441		// Test Case 3: Set Block Height + Next + Next + Next
442		//
443		let block_height = 1900000u32;
444		let mut sub = BlockSub::new(client.clone());
445		sub.set_block_height(block_height);
446		for i in 0..3 {
447			let value = sub.next().await?;
448			assert_eq!(value.block_height, block_height + i);
449		}
450
451		//
452		// Test Case 4: Set Block Height + Prev + Prev + Prev
453		//
454		let block_height = 1900000u32;
455		let mut sub = BlockSub::new(client.clone());
456		sub.set_block_height(block_height);
457		for i in 0..3 {
458			let value = sub.prev().await?;
459			assert_eq!(value.block_height, block_height - i - 1);
460		}
461
462		//
463		// Test Case 5: Set Block Height + Next + Prev
464		//
465		let block_height = 1900000u32;
466		let mut sub = BlockSub::new(client.clone());
467		sub.set_block_height(block_height);
468
469		let value = sub.next().await?;
470		assert_eq!(value.block_height, block_height);
471
472		let value = sub.prev().await?;
473		assert_eq!(value.block_height, block_height - 1);
474
475		//
476		// Test Case 6: Set Block Height + Prev + Next
477		//
478		let block_height = 1900000u32;
479		let mut sub = BlockSub::new(client.clone());
480		sub.set_block_height(block_height);
481
482		let value = sub.prev().await?;
483		assert_eq!(value.block_height, block_height - 1);
484
485		let value = sub.next().await?;
486		assert_eq!(value.block_height, block_height);
487
488		Ok(())
489	}
490
491	// This test will be by flaky and that is OK.
492	#[tokio::test]
493	async fn header_sub_test() -> Result<(), Error> {
494		let (rpc_client, mut commander) = MockClient::new(TURING_ENDPOINT);
495		let client = Client::from_rpc_client(RpcClient::new(rpc_client)).await?;
496
497		//
498		// Test Case 1: Latest Block Height + Next
499		//
500		let mut sub = BlockHeaderSub::new(client.clone());
501
502		let block_height = client.finalized().block_height().await?;
503		let value = sub.next().await?.expect("Should be there");
504		assert_eq!(value.number, block_height);
505
506		//
507		// Test Case 2: Latest Block Height + Prev
508		//
509		let mut sub = BlockHeaderSub::new(client.clone());
510
511		let block_height = client.finalized().block_height().await?;
512		let value = sub.prev().await?.expect("Should be there");
513		assert_eq!(value.number, block_height - 1);
514
515		//
516		// Test Case 3: Set Block Height + Next + Next + Next
517		//
518		let block_height = 1900000u32;
519		let mut sub = BlockHeaderSub::new(client.clone());
520		sub.set_block_height(block_height);
521		for i in 0..3 {
522			let value = sub.next().await?.expect("Should be there");
523			assert_eq!(value.number, block_height + i);
524		}
525
526		//
527		// Test Case 4: Set Block Height + Prev + Prev + Prev
528		//
529		let block_height = 1900000u32;
530		let mut sub = BlockHeaderSub::new(client.clone());
531		sub.set_block_height(block_height);
532		for i in 0..3 {
533			let value = sub.prev().await?.expect("Should be there");
534			assert_eq!(value.number, block_height - i - 1);
535		}
536
537		//
538		// Test Case 5: Set Block Height + Next + Prev
539		//
540		let block_height = 1900000u32;
541		let mut sub = BlockHeaderSub::new(client.clone());
542		sub.set_block_height(block_height);
543
544		let value = sub.next().await?.expect("Should be there");
545		assert_eq!(value.number, block_height);
546
547		let value = sub.prev().await?.expect("Should be there");
548		assert_eq!(value.number, block_height - 1);
549
550		//
551		// Test Case 6: Set Block Height + Prev + Next
552		//
553		let block_height = 1900000u32;
554		let mut sub = BlockHeaderSub::new(client.clone());
555		sub.set_block_height(block_height);
556
557		let value = sub.prev().await?.expect("Should be there");
558		assert_eq!(value.number, block_height - 1);
559
560		let value = sub.next().await?.expect("Should be there");
561		assert_eq!(value.number, block_height);
562
563		//
564		// Test Case 6: Set Block Height + Next + Fail + Next
565		//
566		let block_height = 1900000u32;
567		let mut sub = BlockHeaderSub::new(client.clone());
568		sub.set_retry_on_error(Some(false));
569		sub.set_block_height(block_height);
570
571		let value = sub.next().await?.expect("Should be there");
572		assert_eq!(value.number, block_height);
573		assert_eq!(sub.sub.as_finalized().next_block_height, block_height + 1);
574
575		commander.block_header_err(None);
576		let _ = sub.next().await.expect_err("Should fail");
577		assert_eq!(sub.sub.as_finalized().next_block_height, block_height + 1);
578
579		let value = sub.next().await?.expect("Should be there");
580		assert_eq!(value.number, block_height + 1);
581		assert_eq!(sub.sub.as_finalized().next_block_height, block_height + 2);
582
583		Ok(())
584	}
585
586	// This test will be by flaky and that is OK.
587	#[tokio::test]
588	async fn legacy_block_sub_test() -> Result<(), Error> {
589		let (rpc_client, mut commander) = MockClient::new(TURING_ENDPOINT);
590		let client = Client::from_rpc_client(RpcClient::new(rpc_client)).await?;
591
592		//
593		// Test Case 1: Latest Block Height + Next
594		//
595		let mut sub = LegacyBlockSub::new(client.clone());
596
597		let block_height = client.finalized().block_height().await?;
598		let value = sub.next().await?.expect("Should be there");
599		assert_eq!(value.block.header.number, block_height);
600
601		//
602		// Test Case 2: Latest Block Height + Prev
603		//
604		let mut sub = LegacyBlockSub::new(client.clone());
605
606		let block_height = client.finalized().block_height().await?;
607		let value = sub.prev().await?.expect("Should be there");
608		assert_eq!(value.block.header.number, block_height - 1);
609
610		//
611		// Test Case 3: Set Block Height + Next + Next + Next
612		//
613		let block_height = 1900000u32;
614		let mut sub = LegacyBlockSub::new(client.clone());
615		sub.set_block_height(block_height);
616		for i in 0..3 {
617			let value = sub.next().await?.expect("Should be there");
618			assert_eq!(value.block.header.number, block_height + i);
619		}
620
621		//
622		// Test Case 4: Set Block Height + Prev + Prev + Prev
623		//
624		let block_height = 1900000u32;
625		let mut sub = LegacyBlockSub::new(client.clone());
626		sub.set_block_height(block_height);
627		for i in 0..3 {
628			let value = sub.prev().await?.expect("Should be there");
629			assert_eq!(value.block.header.number, block_height - i - 1);
630		}
631
632		//
633		// Test Case 5: Set Block Height + Next + Prev
634		//
635		let block_height = 1900000u32;
636		let mut sub = LegacyBlockSub::new(client.clone());
637		sub.set_block_height(block_height);
638
639		let value = sub.next().await?.expect("Should be there");
640		assert_eq!(value.block.header.number, block_height);
641
642		let value = sub.prev().await?.expect("Should be there");
643		assert_eq!(value.block.header.number, block_height - 1);
644
645		//
646		// Test Case 6: Set Block Height + Prev + Next
647		//
648		let block_height = 1900000u32;
649		let mut sub = LegacyBlockSub::new(client.clone());
650		sub.set_block_height(block_height);
651
652		let value = sub.prev().await?.expect("Should be there");
653		assert_eq!(value.block.header.number, block_height - 1);
654
655		let value = sub.next().await?.expect("Should be there");
656		assert_eq!(value.block.header.number, block_height);
657
658		//
659		// Test Case 6: Set Block Height + Next + Fail + Next
660		//
661		let block_height = 1900000u32;
662		let mut sub = LegacyBlockSub::new(client.clone());
663		sub.set_retry_on_error(Some(false));
664		sub.set_block_height(block_height);
665
666		let value = sub.next().await?.expect("Should be there");
667		assert_eq!(value.block.header.number, block_height);
668		assert_eq!(sub.sub.as_finalized().next_block_height, block_height + 1);
669
670		commander.legacy_block_err(None);
671		let _ = sub.next().await.expect_err("Should fail");
672		assert_eq!(sub.sub.as_finalized().next_block_height, block_height + 1);
673
674		let value = sub.next().await?.expect("Should be there");
675		assert_eq!(value.block.header.number, block_height + 1);
676		assert_eq!(sub.sub.as_finalized().next_block_height, block_height + 2);
677
678		Ok(())
679	}
680}