bitcoin_explorer/parser/
blk_file.rs1use 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#[derive(Debug, Clone)]
14pub struct BlkFile {
15 files: HashMap<i32, PathBuf>,
16}
17
18impl BlkFile {
19 pub(crate) fn new(path: &Path) -> OpResult<BlkFile> {
23 Ok(BlkFile {
24 files: BlkFile::scan_path(path)?,
25 })
26 }
27
28 #[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 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 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 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 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 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 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}