bitcoin_explorer/parser/
tx_index.rs1use crate::parser::block_index::BlockIndex;
2use crate::parser::errors::{OpError, OpResult};
3use crate::parser::reader::BlockchainRead;
4use bitcoin::hashes::Hash;
5use bitcoin::Txid;
6use leveldb::database::Database;
7use leveldb::kv::KV;
8use leveldb::options::{Options, ReadOptions};
9use log::{info, warn};
10use std::collections::BTreeMap;
11use std::io::Cursor;
12use std::path::Path;
13use std::str::FromStr;
14
15const GENESIS_TXID: &str = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b";
16
17pub struct TxDB {
23 db: Option<Database<TxKey>>,
24 file_pos_to_height: BTreeMap<(i32, u32), i32>,
26 genesis_txid: Txid,
27}
28
29pub struct TransactionRecord {
31 pub txid: Txid,
32 pub n_file: i32,
33 pub n_pos: u32,
34 pub n_tx_offset: u32,
35}
36
37impl TransactionRecord {
38 fn from(key: &[u8], values: &[u8]) -> OpResult<Self> {
39 let mut reader = Cursor::new(values);
40 Ok(TransactionRecord {
41 txid: Txid::from_slice(key)?,
42 n_file: reader.read_varint()? as i32,
43 n_pos: reader.read_varint()? as u32,
44 n_tx_offset: reader.read_varint()? as u32,
45 })
46 }
47}
48
49impl TxDB {
50 pub fn new(path: &Path, blk_index: &BlockIndex) -> TxDB {
52 let option_db = TxDB::try_open_db(path);
53 if let Some(db) = option_db {
54 let mut file_pos_to_height = BTreeMap::new();
55 for b in blk_index.records.iter() {
56 file_pos_to_height.insert((b.n_file, b.n_data_pos), b.n_height);
57 }
58 TxDB {
59 db: Some(db),
60 file_pos_to_height,
61 genesis_txid: Txid::from_str(GENESIS_TXID).unwrap(),
62 }
63 } else {
64 TxDB::null()
65 }
66 }
67
68 #[inline]
69 pub(crate) fn is_open(&self) -> bool {
70 self.db.is_some()
71 }
72
73 #[inline]
74 pub(crate) fn null() -> TxDB {
75 TxDB {
76 db: None,
77 file_pos_to_height: BTreeMap::new(),
78 genesis_txid: Txid::from_str(GENESIS_TXID).unwrap(),
79 }
80 }
81
82 #[inline]
83 pub(crate) fn is_genesis_tx(&self, txid: &Txid) -> bool {
87 txid == &self.genesis_txid
88 }
89
90 fn try_open_db(path: &Path) -> Option<Database<TxKey>> {
91 if !path.exists() {
92 warn!("Failed to open tx_index DB: tx_index not built");
93 return None;
94 }
95 let options = Options::new();
96 match Database::open(path, options) {
97 Ok(db) => {
98 info! {"Successfully opened tx_index DB!"}
99 Some(db)
100 }
101 Err(e) => {
102 warn!("Filed to open tx_index DB: {:?}", e);
103 None
104 }
105 }
106 }
107
108 pub(crate) fn get_tx_record(&self, txid: &Txid) -> OpResult<TransactionRecord> {
110 if let Some(db) = &self.db {
111 let inner = txid.as_inner();
112 let mut key = Vec::with_capacity(inner.len() + 1);
113 key.push(b't');
114 key.extend(inner);
115 let key = TxKey { key };
116 let read_options = ReadOptions::new();
117 match db.get(read_options, &key) {
118 Ok(value) => {
119 if let Some(value) = value {
120 Ok(TransactionRecord::from(&key.key[1..], value.as_slice())?)
121 } else {
122 Err(OpError::from(
123 format!("value not found for txid: {}", txid).as_str(),
124 ))
125 }
126 }
127 Err(e) => Err(OpError::from(
128 format!("value not found for txid: {}", e).as_str(),
129 )),
130 }
131 } else {
132 Err(OpError::from("TxDB not open"))
133 }
134 }
135
136 pub(crate) fn get_block_height_of_tx(&self, txid: &Txid) -> OpResult<usize> {
137 if self.is_genesis_tx(txid) {
139 return Ok(0);
140 }
141 let record: TransactionRecord = self.get_tx_record(txid)?;
142 let file_pos_height = &self.file_pos_to_height;
143 match file_pos_height.get(&(record.n_file, record.n_pos)) {
144 None => Err(OpError::from("transaction not found")),
145 Some(pos_height) => Ok(*pos_height as usize),
146 }
147 }
148}
149
150struct TxKey {
152 key: Vec<u8>,
153}
154
155impl db_key::Key for TxKey {
157 fn from_u8(key: &[u8]) -> Self {
158 TxKey {
159 key: Vec::from(key),
160 }
161 }
162
163 fn as_slice<T, F: Fn(&[u8]) -> T>(&self, f: F) -> T {
164 f(&self.key)
165 }
166}