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::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)?;
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 (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
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<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
105/// levelDB key util
106struct BlockKey {
107    key: Vec<u8>,
108}
109
110/// levelDB key util
111impl 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    ///
125    /// Decode levelDB value for Block Index Record.
126    ///
127    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}