1use std::path::PathBuf;
2
3use crate::error::BlkReaderError;
4use crate::index::{BlockIndexEntry, IndexReader};
5use crate::reader::BlkReader;
6
7#[derive(Debug, Clone)]
9pub struct BlkReaderConfig {
10 pub blocks_dir: PathBuf,
12 pub index_dir: PathBuf,
14 pub start_height: u32,
16 pub end_height: u32,
18 pub read_buffer_bytes: usize,
20}
21
22impl Default for BlkReaderConfig {
23 fn default() -> Self {
24 Self {
25 blocks_dir: PathBuf::new(),
26 index_dir: PathBuf::new(),
27 start_height: 0,
28 end_height: u32::MAX,
29 read_buffer_bytes: 8 * 1024 * 1024,
30 }
31 }
32}
33
34#[derive(Debug)]
36pub struct RawBlock {
37 pub height: u32,
39 pub hash: [u8; 32],
42 pub data: Vec<u8>,
44}
45
46pub struct BlockIterator {
72 sorted_entries: Vec<BlockIndexEntry>,
73 cursor: usize,
74 reader: BlkReader,
75}
76
77impl BlockIterator {
78 pub fn new(config: BlkReaderConfig) -> Result<Self, BlkReaderError> {
84 let all_entries = IndexReader::read_all(&config.index_dir)?;
85
86 let mut filtered: Vec<BlockIndexEntry> = all_entries
87 .into_iter()
88 .filter(|e| {
89 e.is_valid_and_available()
90 && e.height >= config.start_height
91 && e.height < config.end_height
92 })
93 .collect();
94
95 filtered.sort_unstable_by_key(|e| e.height);
96
97 tracing::info!(
98 count = filtered.len(),
99 start_height = config.start_height,
100 end_height = config.end_height,
101 "BlockIterator: entries filtered and sorted"
102 );
103
104 let reader = BlkReader::new(config.blocks_dir, config.read_buffer_bytes);
105
106 Ok(Self {
107 sorted_entries: filtered,
108 cursor: 0,
109 reader,
110 })
111 }
112}
113
114impl Iterator for BlockIterator {
115 type Item = Result<RawBlock, BlkReaderError>;
116
117 fn next(&mut self) -> Option<Self::Item> {
118 if self.cursor >= self.sorted_entries.len() {
119 return None;
120 }
121
122 let entry = &self.sorted_entries[self.cursor];
123 self.cursor += 1;
124
125 let result = self.reader
126 .read_block_at(entry.n_file, entry.n_data_pos)
127 .map(|data| RawBlock {
128 height: entry.height,
129 hash: entry.hash,
130 data,
131 });
132
133 Some(result)
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use crate::index::BlockIndexEntry;
140
141 fn make_entry(height: u32, status: u32, n_file: u32, n_data_pos: u32) -> BlockIndexEntry {
142 BlockIndexEntry {
143 hash: [0u8; 32],
144 height,
145 status,
146 n_file,
147 n_data_pos,
148 }
149 }
150
151 #[test]
152 fn test_filters_orphans() {
153 let orphan = make_entry(100, 0x00, 0, 0);
154 assert!(!orphan.is_valid_and_available());
155
156 let valid_no_data = make_entry(101, 0x04, 0, 0);
157 assert!(!valid_no_data.is_valid_and_available());
158
159 let valid = make_entry(102, 0x04 | 0x08, 0, 0);
160 assert!(valid.is_valid_and_available());
161 }
162
163 #[test]
164 fn test_filters_by_height_range() {
165 let entries = vec![
166 make_entry(0, 0x0C, 0, 0),
167 make_entry(5, 0x0C, 0, 0),
168 make_entry(10, 0x0C, 0, 0),
169 make_entry(15, 0x0C, 0, 0),
170 ];
171
172 let filtered: Vec<_> = entries
173 .iter()
174 .filter(|e| e.is_valid_and_available() && e.height >= 5 && e.height < 10)
175 .collect();
176
177 assert_eq!(filtered.len(), 1);
178 assert_eq!(filtered[0].height, 5);
179 }
180
181 #[test]
182 fn test_sorts_by_height() {
183 let mut entries = vec![
184 make_entry(300, 0x0C, 0, 0),
185 make_entry(100, 0x0C, 0, 0),
186 make_entry(200, 0x0C, 0, 0),
187 ];
188 entries.sort_unstable_by_key(|e| e.height);
189
190 assert_eq!(entries[0].height, 100);
191 assert_eq!(entries[1].height, 200);
192 assert_eq!(entries[2].height, 300);
193 for i in 1..entries.len() {
194 assert!(entries[i].height > entries[i - 1].height);
195 }
196 }
197}