bitcoin_explorer/parser/
blk_file.rs

1use crate::parser::errors::{OpError, OpErrorKind, OpResult};
2use crate::parser::reader::BlockchainRead;
3use bitcoin::{Block, Transaction};
4use std::collections::HashMap;
5use std::convert::From;
6use std::fs::{self, DirEntry, File};
7use std::io::{self, BufReader, Cursor, Seek, SeekFrom};
8use std::path::{Path, PathBuf};
9
10///
11/// An index of all blk files found.
12///
13#[derive(Debug, Clone)]
14pub struct BlkFile {
15    files: HashMap<i32, PathBuf>,
16}
17
18impl BlkFile {
19    ///
20    /// Construct an index of all blk files.
21    ///
22    pub(crate) fn new(path: &Path) -> OpResult<BlkFile> {
23        Ok(BlkFile {
24            files: BlkFile::scan_path(path)?,
25        })
26    }
27
28    ///
29    /// Read a Block from blk file.
30    ///
31    #[inline]
32    pub(crate) fn read_raw_block(&self, n_file: i32, offset: u32) -> OpResult<Vec<u8>> {
33        if let Some(blk_path) = self.files.get(&n_file) {
34            let mut r = BufReader::new(File::open(blk_path)?);
35            r.seek(SeekFrom::Start(offset as u64 - 4))?;
36            let block_size = r.read_u32()?;
37            let block = r.read_u8_vec(block_size)?;
38            Ok(block)
39        } else {
40            Err(OpError::from("blk file not found, sync with bitcoin core"))
41        }
42    }
43
44    ///
45    /// Read a Block from blk file.
46    ///
47    pub(crate) fn read_block(&self, n_file: i32, offset: u32) -> OpResult<Block> {
48        Cursor::new(self.read_raw_block(n_file, offset)?).read_block()
49    }
50
51    ///
52    /// Read a transaction from blk file.
53    ///
54    pub(crate) fn read_transaction(
55        &self,
56        n_file: i32,
57        n_pos: u32,
58        n_tx_offset: u32,
59    ) -> OpResult<Transaction> {
60        if let Some(blk_path) = self.files.get(&n_file) {
61            let mut r = BufReader::new(File::open(blk_path)?);
62            // the size of a header is 80.
63            r.seek(SeekFrom::Start(n_pos as u64 + n_tx_offset as u64 + 80))?;
64            r.read_transaction()
65        } else {
66            Err(OpError::from("blk file not found, sync with bitcoin core"))
67        }
68    }
69
70    ///
71    /// Scan blk folder to build an index of all blk files.
72    ///
73    fn scan_path(path: &Path) -> OpResult<HashMap<i32, PathBuf>> {
74        let mut collected = HashMap::with_capacity(4000);
75        for entry in fs::read_dir(path)? {
76            match entry {
77                Ok(de) => {
78                    let path = BlkFile::resolve_path(&de)?;
79                    if !path.is_file() {
80                        continue;
81                    };
82                    if let Some(file_name) = path.as_path().file_name() {
83                        if let Some(file_name) = file_name.to_str() {
84                            if let Some(index) = BlkFile::parse_blk_index(file_name) {
85                                collected.insert(index, path);
86                            }
87                        }
88                    }
89                }
90                Err(msg) => {
91                    return Err(OpError::from(msg));
92                }
93            }
94        }
95        collected.shrink_to_fit();
96        if collected.is_empty() {
97            Err(OpError::new(OpErrorKind::RuntimeError).join_msg("No blk files found!"))
98        } else {
99            Ok(collected)
100        }
101    }
102
103    ///
104    /// Resolve symlink.
105    ///
106    fn resolve_path(entry: &DirEntry) -> io::Result<PathBuf> {
107        if entry.file_type()?.is_symlink() {
108            fs::read_link(entry.path())
109        } else {
110            Ok(entry.path())
111        }
112    }
113
114    ///
115    /// Extract index from block file name.
116    ///
117    fn parse_blk_index(file_name: &str) -> Option<i32> {
118        let prefix = "blk";
119        let ext = ".dat";
120        if file_name.starts_with(prefix) && file_name.ends_with(ext) {
121            file_name[prefix.len()..(file_name.len() - ext.len())]
122                .parse::<i32>()
123                .ok()
124        } else {
125            None
126        }
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_parse_blk_index() {
136        assert_eq!(0, BlkFile::parse_blk_index("blk00000.dat").unwrap());
137        assert_eq!(6, BlkFile::parse_blk_index("blk6.dat").unwrap());
138        assert_eq!(1202, BlkFile::parse_blk_index("blk1202.dat").unwrap());
139        assert_eq!(
140            13412451,
141            BlkFile::parse_blk_index("blk13412451.dat").unwrap()
142        );
143        assert_eq!(true, BlkFile::parse_blk_index("blkindex.dat").is_none());
144        assert_eq!(true, BlkFile::parse_blk_index("invalid.dat").is_none());
145    }
146}