bitcoin_explorer/parser/
block_index.rs1use crate::parser::errors::OpResult;
2use crate::parser::reader::BlockchainRead;
3use bitcoin::{BlockHash, BlockHeader};
4use leveldb::database::iterator::LevelDBIterator;
5use leveldb::database::Database;
6use leveldb::iterator::Iterable;
7use leveldb::options::{Options, ReadOptions};
8use log::info;
9use serde::Serialize;
10use std::collections::HashMap;
11use std::fmt;
12use std::io::Cursor;
13use std::path::Path;
14
15const BLOCK_VALID_HEADER: u32 = 1;
19const BLOCK_VALID_TREE: u32 = 2;
20const BLOCK_VALID_TRANSACTIONS: u32 = 3;
21const BLOCK_VALID_CHAIN: u32 = 4;
22const BLOCK_VALID_SCRIPTS: u32 = 5;
23const BLOCK_VALID_MASK: u32 = BLOCK_VALID_HEADER
24 | BLOCK_VALID_TREE
25 | BLOCK_VALID_TRANSACTIONS
26 | BLOCK_VALID_CHAIN
27 | BLOCK_VALID_SCRIPTS;
28const BLOCK_HAVE_DATA: u32 = 8;
29const BLOCK_HAVE_UNDO: u32 = 16;
30
31#[derive(Clone)]
36pub struct BlockIndex {
37 pub records: Box<[BlockIndexRecord]>,
38 pub hash_to_height: HashMap<BlockHash, i32>,
39}
40
41#[derive(Serialize, Clone)]
45pub struct BlockIndexRecord {
46 pub n_version: i32,
47 pub n_height: i32,
48 pub n_status: u32,
49 pub n_tx: u32,
50 pub n_file: i32,
51 pub n_data_pos: u32,
52 pub n_undo_pos: u32,
53 pub block_header: BlockHeader,
54}
55
56impl BlockIndex {
57 pub(crate) fn new(p: &Path) -> OpResult<BlockIndex> {
61 let records = load_block_index(p)?;
62
63 let mut hash_to_height = HashMap::with_capacity(records.len());
65 for (check_height, b) in records.iter().enumerate() {
66 hash_to_height.insert(b.block_header.block_hash(), b.n_height);
67 }
68 hash_to_height.shrink_to_fit();
69 Ok(BlockIndex {
70 records,
71 hash_to_height,
72 })
73 }
74}
75
76pub fn load_block_index(path: &Path) -> OpResult<Box<[BlockIndexRecord]>> {
82 let mut block_index = Vec::with_capacity(800000);
83
84 info!("Start loading block_index");
85 let mut options = Options::new();
86 options.create_if_missing = false;
87 let db: Database<BlockKey> = Database::open(path, options)?;
88 let options = ReadOptions::new();
89 let mut iter = db.iter(options);
90
91 while iter.advance() {
92 let k = iter.key();
93 let v = iter.value();
94 if is_block_index_record(&k.key) {
95 let record = BlockIndexRecord::from(&v)?;
96 if record.n_status & (BLOCK_VALID_MASK | BLOCK_HAVE_DATA) > 0 {
97 block_index.push(record);
98 }
99 }
100 }
101 block_index.sort_by_key(|b| b.n_height);
102 Ok(block_index.into_boxed_slice())
103}
104
105struct BlockKey {
107 key: Vec<u8>,
108}
109
110impl db_key::Key for BlockKey {
112 fn from_u8(key: &[u8]) -> Self {
113 BlockKey {
114 key: Vec::from(key),
115 }
116 }
117
118 fn as_slice<T, F: Fn(&[u8]) -> T>(&self, f: F) -> T {
119 f(&self.key)
120 }
121}
122
123impl BlockIndexRecord {
124 fn from(values: &[u8]) -> OpResult<Self> {
128 let mut reader = Cursor::new(values);
129
130 let n_version = reader.read_varint()? as i32;
131 let n_height = reader.read_varint()? as i32;
132 let n_status = reader.read_varint()? as u32;
133 let n_tx = reader.read_varint()? as u32;
134 let n_file = if n_status & (BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO) > 0 {
135 reader.read_varint()? as i32
136 } else {
137 -1
138 };
139 let n_data_pos = if n_status & BLOCK_HAVE_DATA > 0 {
140 reader.read_varint()? as u32
141 } else {
142 u32::MAX
143 };
144 let n_undo_pos = if n_status & BLOCK_HAVE_UNDO > 0 {
145 reader.read_varint()? as u32
146 } else {
147 u32::MAX
148 };
149 let block_header = reader.read_block_header()?;
150
151 Ok(BlockIndexRecord {
152 n_version,
153 n_height,
154 n_status,
155 n_tx,
156 n_file,
157 n_data_pos,
158 n_undo_pos,
159 block_header,
160 })
161 }
162}
163
164#[inline]
165fn is_block_index_record(data: &[u8]) -> bool {
166 data.first() == Some(&b'b')
167}
168
169impl fmt::Debug for BlockIndexRecord {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 f.debug_struct("BlockIndexRecord")
172 .field("version", &self.n_version)
173 .field("height", &self.n_height)
174 .field("status", &self.n_status)
175 .field("n_tx", &self.n_tx)
176 .field("n_file", &self.n_file)
177 .field("n_data_pos", &self.n_data_pos)
178 .field("header", &self.block_header)
179 .finish()
180 }
181}