bitcoin_explorer/parser/
block_index.rs

1use 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::{BTreeMap, HashMap};
11use std::fmt;
12use std::io::Cursor;
13use std::path::Path;
14
15///
16/// See Bitcoin Core repository for definition.
17///
18const 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///
32/// - Map from block height to block hash (records)
33/// - Map from block hash to block height (hash_to_height)
34///
35#[derive(Clone)]
36pub struct BlockIndex {
37    pub records: Box<[BlockIndexRecord]>,
38    pub hash_to_height: HashMap<BlockHash, i32>,
39}
40
41///
42/// BLOCK_INDEX RECORD as defined in Bitcoin Core.
43///
44#[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    ///
58    /// Build a collections of block index.
59    ///
60    pub(crate) fn new(p: &Path) -> OpResult<BlockIndex> {
61        let records = load_block_index(p)?.into_boxed_slice();
62
63        // build a reverse index to lookup block height of a particular block hash.
64        let mut hash_to_height = HashMap::with_capacity(records.len());
65        for b in records.iter() {
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
76///
77/// Load all block index in memory from leveldb (i.e. `blocks/index` path).
78///
79/// Map from block height to block index record.
80///
81pub fn load_block_index(path: &Path) -> OpResult<Vec<BlockIndexRecord>> {
82    let mut block_index_by_block_hash = BTreeMap::new();
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    let mut max_height_block_hash = Option::<(BlockHash, i32)>::None;
91
92    while iter.advance() {
93        let k = iter.key();
94        let v = iter.value();
95        if is_block_index_record(&k.key) {
96            let record = BlockIndexRecord::from(&v)?;
97            // only add valid block index record that has block data.
98            if record.n_height == 0
99                || (record.n_status & BLOCK_VALID_MASK >= BLOCK_VALID_SCRIPTS
100                    && record.n_status & BLOCK_HAVE_DATA > 0)
101            {
102                let block_hash = record.block_header.block_hash();
103                // find the block with max height
104                if let Some((hash, height)) = max_height_block_hash.as_mut() {
105                    if record.n_height > *height {
106                        *hash = block_hash;
107                        *height = record.n_height;
108                    }
109                } else {
110                    max_height_block_hash = Some((block_hash, record.n_height));
111                }
112                block_index_by_block_hash.insert(block_hash, record);
113            }
114        }
115    }
116    // build the longest chain
117    if let Some((hash, height)) = max_height_block_hash {
118        let mut block_index = Vec::with_capacity(height as usize + 1);
119        let mut current_hash = hash;
120        let mut current_height = height;
121        // recursively build block index from max height block.
122        while current_height >= 0 {
123            let blk = block_index_by_block_hash
124                .remove(&current_hash)
125                .expect("block hash not found in block index!");
126            assert_eq!(
127                current_height, blk.n_height,
128                "some block info missing from block index levelDB,\
129                       delete Bitcoin folder and re-download!"
130            );
131            current_hash = blk.block_header.prev_blockhash;
132            current_height -= 1;
133            block_index.push(blk);
134        }
135        block_index.reverse();
136        Ok(block_index)
137    } else {
138        Ok(Vec::with_capacity(0))
139    }
140}
141
142/// levelDB key util
143struct BlockKey {
144    key: Vec<u8>,
145}
146
147/// levelDB key util
148impl db_key::Key for BlockKey {
149    fn from_u8(key: &[u8]) -> Self {
150        BlockKey {
151            key: Vec::from(key),
152        }
153    }
154
155    fn as_slice<T, F: Fn(&[u8]) -> T>(&self, f: F) -> T {
156        f(&self.key)
157    }
158}
159
160impl BlockIndexRecord {
161    ///
162    /// Decode levelDB value for Block Index Record.
163    ///
164    fn from(values: &[u8]) -> OpResult<Self> {
165        let mut reader = Cursor::new(values);
166
167        let n_version = reader.read_varint()? as i32;
168        let n_height = reader.read_varint()? as i32;
169        let n_status = reader.read_varint()? as u32;
170        let n_tx = reader.read_varint()? as u32;
171        let n_file = if n_status & (BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO) > 0 {
172            reader.read_varint()? as i32
173        } else {
174            -1
175        };
176        let n_data_pos = if n_status & BLOCK_HAVE_DATA > 0 {
177            reader.read_varint()? as u32
178        } else {
179            u32::MAX
180        };
181        let n_undo_pos = if n_status & BLOCK_HAVE_UNDO > 0 {
182            reader.read_varint()? as u32
183        } else {
184            u32::MAX
185        };
186        let block_header = reader.read_block_header()?;
187
188        Ok(BlockIndexRecord {
189            n_version,
190            n_height,
191            n_status,
192            n_tx,
193            n_file,
194            n_data_pos,
195            n_undo_pos,
196            block_header,
197        })
198    }
199}
200
201#[inline]
202fn is_block_index_record(data: &[u8]) -> bool {
203    data.first() == Some(&b'b')
204}
205
206impl fmt::Debug for BlockIndexRecord {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        f.debug_struct("BlockIndexRecord")
209            .field("version", &self.n_version)
210            .field("height", &self.n_height)
211            .field("status", &self.n_status)
212            .field("n_tx", &self.n_tx)
213            .field("n_file", &self.n_file)
214            .field("n_data_pos", &self.n_data_pos)
215            .field("header", &self.block_header)
216            .finish()
217    }
218}