gear_subxt/blocks/
blocks_client.rs

1// Copyright 2019-2023 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5use super::Block;
6use crate::{
7    client::OnlineClientT,
8    config::{Config, Header},
9    error::{BlockError, Error},
10    utils::PhantomDataSendSync,
11};
12use derivative::Derivative;
13use futures::{future::Either, stream, Stream, StreamExt};
14use std::{future::Future, pin::Pin};
15
16type BlockStream<T> = Pin<Box<dyn Stream<Item = Result<T, Error>> + Send>>;
17type BlockStreamRes<T> = Result<BlockStream<T>, Error>;
18
19/// A client for working with blocks.
20#[derive(Derivative)]
21#[derivative(Clone(bound = "Client: Clone"))]
22pub struct BlocksClient<T, Client> {
23    client: Client,
24    _marker: PhantomDataSendSync<T>,
25}
26
27impl<T, Client> BlocksClient<T, Client> {
28    /// Create a new [`BlocksClient`].
29    pub fn new(client: Client) -> Self {
30        Self {
31            client,
32            _marker: PhantomDataSendSync::new(),
33        }
34    }
35}
36
37impl<T, Client> BlocksClient<T, Client>
38where
39    T: Config,
40    Client: OnlineClientT<T>,
41{
42    /// Obtain block details given the provided block hash.
43    ///
44    /// # Warning
45    ///
46    /// This call only supports blocks produced since the most recent
47    /// runtime upgrade. You can attempt to retrieve older blocks,
48    /// but may run into errors attempting to work with them.
49    pub fn at(
50        &self,
51        block_hash: T::Hash,
52    ) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
53        self.at_or_latest(Some(block_hash))
54    }
55
56    /// Obtain block details of the latest block hash.
57    pub fn at_latest(
58        &self,
59    ) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
60        self.at_or_latest(None)
61    }
62
63    /// Obtain block details given the provided block hash, or the latest block if `None` is
64    /// provided.
65    fn at_or_latest(
66        &self,
67        block_hash: Option<T::Hash>,
68    ) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
69        let client = self.client.clone();
70        async move {
71            // If block hash is not provided, get the hash
72            // for the latest block and use that.
73            let block_hash = match block_hash {
74                Some(hash) => hash,
75                None => client
76                    .rpc()
77                    .block_hash(None)
78                    .await?
79                    .expect("didn't pass a block number; qed"),
80            };
81
82            let block_header = match client.rpc().header(Some(block_hash)).await? {
83                Some(header) => header,
84                None => return Err(BlockError::not_found(block_hash).into()),
85            };
86
87            Ok(Block::new(block_header, client))
88        }
89    }
90
91    /// Subscribe to all new blocks imported by the node.
92    ///
93    /// **Note:** You probably want to use [`Self::subscribe_finalized()`] most of
94    /// the time.
95    pub fn subscribe_all(
96        &self,
97    ) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
98    where
99        Client: Send + Sync + 'static,
100    {
101        let client = self.client.clone();
102        header_sub_fut_to_block_sub(self.clone(), async move {
103            let sub = client.rpc().subscribe_all_block_headers().await?;
104            BlockStreamRes::Ok(Box::pin(sub))
105        })
106    }
107
108    /// Subscribe to all new blocks imported by the node onto the current best fork.
109    ///
110    /// **Note:** You probably want to use [`Self::subscribe_finalized()`] most of
111    /// the time.
112    pub fn subscribe_best(
113        &self,
114    ) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
115    where
116        Client: Send + Sync + 'static,
117    {
118        let client = self.client.clone();
119        header_sub_fut_to_block_sub(self.clone(), async move {
120            let sub = client.rpc().subscribe_best_block_headers().await?;
121            BlockStreamRes::Ok(Box::pin(sub))
122        })
123    }
124
125    /// Subscribe to finalized blocks.
126    pub fn subscribe_finalized(
127        &self,
128    ) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
129    where
130        Client: Send + Sync + 'static,
131    {
132        let client = self.client.clone();
133        header_sub_fut_to_block_sub(self.clone(), async move {
134            // Fetch the last finalised block details immediately, so that we'll get
135            // all blocks after this one.
136            let last_finalized_block_hash = client.rpc().finalized_head().await?;
137            let last_finalized_block_num = client
138                .rpc()
139                .header(Some(last_finalized_block_hash))
140                .await?
141                .map(|h| h.number().into());
142
143            let sub = client.rpc().subscribe_finalized_block_headers().await?;
144
145            // Adjust the subscription stream to fill in any missing blocks.
146            BlockStreamRes::Ok(
147                subscribe_to_block_headers_filling_in_gaps(client, last_finalized_block_num, sub)
148                    .boxed(),
149            )
150        })
151    }
152}
153
154/// Take a promise that will return a subscription to some block headers,
155/// and return a subscription to some blocks based on this.
156async fn header_sub_fut_to_block_sub<T, Client, S>(
157    blocks_client: BlocksClient<T, Client>,
158    sub: S,
159) -> Result<BlockStream<Block<T, Client>>, Error>
160where
161    T: Config,
162    S: Future<Output = Result<BlockStream<T::Header>, Error>> + Send + 'static,
163    Client: OnlineClientT<T> + Send + Sync + 'static,
164{
165    let sub = sub.await?.then(move |header| {
166        let client = blocks_client.client.clone();
167        async move {
168            let header = match header {
169                Ok(header) => header,
170                Err(e) => return Err(e),
171            };
172
173            Ok(Block::new(header, client))
174        }
175    });
176    BlockStreamRes::Ok(Box::pin(sub))
177}
178
179/// Note: This is exposed for testing but is not considered stable and may change
180/// without notice in a patch release.
181#[doc(hidden)]
182pub fn subscribe_to_block_headers_filling_in_gaps<T, Client, S, E>(
183    client: Client,
184    mut last_block_num: Option<u64>,
185    sub: S,
186) -> impl Stream<Item = Result<T::Header, Error>> + Send
187where
188    T: Config,
189    Client: OnlineClientT<T>,
190    S: Stream<Item = Result<T::Header, E>> + Send,
191    E: Into<Error> + Send + 'static,
192{
193    sub.flat_map(move |s| {
194        let client = client.clone();
195
196        // Get the header, or return a stream containing just the error.
197        let header = match s {
198            Ok(header) => header,
199            Err(e) => return Either::Left(stream::once(async { Err(e.into()) })),
200        };
201
202        // We want all previous details up to, but not including this current block num.
203        let end_block_num = header.number().into();
204
205        // This is one after the last block we returned details for last time.
206        let start_block_num = last_block_num.map(|n| n + 1).unwrap_or(end_block_num);
207
208        // Iterate over all of the previous blocks we need headers for, ignoring the current block
209        // (which we already have the header info for):
210        let previous_headers = stream::iter(start_block_num..end_block_num)
211            .then(move |n| {
212                let rpc = client.rpc().clone();
213                async move {
214                    let hash = rpc.block_hash(Some(n.into())).await?;
215                    let header = rpc.header(hash).await?;
216                    Ok::<_, Error>(header)
217                }
218            })
219            .filter_map(|h| async { h.transpose() });
220
221        // On the next iteration, we'll get details starting just after this end block.
222        last_block_num = Some(end_block_num);
223
224        // Return a combination of any previous headers plus the new header.
225        Either::Right(previous_headers.chain(stream::once(async { Ok(header) })))
226    })
227}