brk_iterator/
lib.rs

1use std::sync::Arc;
2
3use brk_error::Result;
4use brk_reader::Reader;
5use brk_rpc::Client;
6use brk_types::{BlockHash, Height};
7
8mod iterator;
9mod range;
10mod source;
11mod state;
12
13use iterator::*;
14use range::*;
15use source::*;
16use state::*;
17
18///
19/// Block iterator factory
20///
21/// Creates iterators over Bitcoin blocks from various sources (RPC/Reader).
22/// Iterators may end earlier than expected if a chain reorganization occurs.
23///
24/// Thread-safe and free to clone.
25///
26#[derive(Clone)]
27pub struct Blocks(Arc<Source>);
28
29impl Blocks {
30    /// Create with smart mode (auto-select source based on range size)
31    pub fn new(client: &Client, reader: &Reader) -> Self {
32        Self::new_inner(Source::Smart {
33            client: client.clone(),
34            reader: reader.clone(),
35        })
36    }
37
38    /// Create with RPC-only mode
39    pub fn new_rpc(client: &Client) -> Self {
40        Self::new_inner(Source::Rpc {
41            client: client.clone(),
42        })
43    }
44
45    /// Create with Reader-only mode
46    pub fn new_reader(reader: &Reader) -> Self {
47        Self::new_inner(Source::Reader {
48            reader: reader.clone(),
49        })
50    }
51
52    fn new_inner(source: Source) -> Self {
53        Self(Arc::new(source))
54    }
55
56    /// Iterate over a specific range (start..=end)
57    pub fn range(&self, start: Height, end: Height) -> Result<BlockIterator> {
58        self.iter(BlockRange::Span { start, end })
59    }
60
61    /// Iterate from start (inclusive) to chain tip
62    pub fn start(&self, start: Height) -> Result<BlockIterator> {
63        self.iter(BlockRange::Start { start })
64    }
65
66    /// Iterate from genesis to end (inclusive)
67    pub fn end(&self, end: Height) -> Result<BlockIterator> {
68        self.iter(BlockRange::End { end })
69    }
70
71    /// Iterate over last n blocks
72    pub fn last(&self, n: u32) -> Result<BlockIterator> {
73        self.iter(BlockRange::Last { n })
74    }
75
76    /// Iterate after hash
77    pub fn after(&self, hash: Option<BlockHash>) -> Result<BlockIterator> {
78        self.iter(BlockRange::After { hash })
79    }
80
81    fn iter(&self, range: BlockRange) -> Result<BlockIterator> {
82        let (start, end, hash_opt) = self.resolve_range(range)?;
83
84        let count = end.saturating_sub(*start) + 1;
85
86        let state = match &*self.0 {
87            Source::Smart { client, reader } => {
88                if count <= 10 {
89                    State::new_rpc(client.clone(), start, end, hash_opt)
90                } else {
91                    State::new_reader(reader.clone(), start, end, hash_opt)
92                }
93            }
94            Source::Rpc { client } => State::new_rpc(client.clone(), start, end, hash_opt),
95            Source::Reader { reader, .. } => {
96                State::new_reader(reader.clone(), start, end, hash_opt)
97            }
98        };
99
100        Ok(BlockIterator::new(state))
101    }
102
103    fn resolve_range(&self, range: BlockRange) -> Result<(Height, Height, Option<BlockHash>)> {
104        let client = self.0.client();
105
106        match range {
107            BlockRange::Span { start, end } => Ok((start, end, None)),
108            BlockRange::Start { start } => {
109                let end = client.get_last_height()?;
110                Ok((start, end, None))
111            }
112            BlockRange::End { end } => Ok((Height::ZERO, end, None)),
113            BlockRange::Last { n } => {
114                let end = client.get_last_height()?;
115                let start = Height::new((*end).saturating_sub(n - 1));
116                Ok((start, end, None))
117            }
118            BlockRange::After { hash } => {
119                let start = if let Some(hash) = hash.as_ref() {
120                    let block_info = client.get_block_header_info(hash)?;
121                    (block_info.height + 1).into()
122                } else {
123                    Height::ZERO
124                };
125                let end = client.get_last_height()?;
126                Ok((start, end, hash))
127            }
128        }
129    }
130}