bitcoin_explorer/api/mod.rs
1//!
2//! Crates APIs, essential structs, functions, methods are all here!
3//!
4//! To quickly understand how to use this crate, have a look at the
5//! documentation for `bitcoin_explorer::BitcoinDB`!!.
6//!
7//! # Example
8//!
9//! ```rust
10//! use bitcoin_explorer::BitcoinDB;
11//! use std::path::Path;
12//!
13//! let path = Path::new("/Users/me/bitcoin");
14//!
15//! // launch without reading txindex
16//! let db = BitcoinDB::new(path, false).unwrap();
17//!
18//! // launch attempting to read txindex
19//! let db = BitcoinDB::new(path, true).unwrap();
20//! ```
21//!
22
23mod connected;
24
25use crate::parser::blk_file::BlkFile;
26use crate::parser::errors::{OpError, OpResult};
27use crate::parser::script::{evaluate_script, ScriptInfo};
28use crate::parser::tx_index::TxDB;
29use std::ops::Deref;
30use std::path::Path;
31use std::sync::Arc;
32// re-exports
33pub use crate::iter::{BlockIter, ConnectedBlockIter};
34pub use crate::parser::block_index::{BlockIndex, BlockIndexRecord};
35pub use crate::parser::proto::connected_proto::{
36 ConnectedBlock, ConnectedTx, FConnectedBlock, FConnectedTransaction, SConnectedBlock,
37 SConnectedTransaction,
38};
39pub use crate::parser::proto::full_proto::{FBlock, FBlockHeader, FTransaction, FTxOut};
40pub use crate::parser::proto::simple_proto::{SBlock, SBlockHeader, STransaction, STxOut};
41pub use bitcoin::hashes::hex::{FromHex, ToHex};
42pub use bitcoin::{Address, Block, BlockHash, BlockHeader, Network, Script, Transaction, Txid};
43
44///
45/// Extract addresses from a script public key.
46///
47#[deprecated(since = "1.2.7", note = "use `get_addresses_from_script` instead")]
48pub fn parse_script(script_pub_key: &str) -> OpResult<ScriptInfo> {
49 get_addresses_from_script(script_pub_key)
50}
51
52///
53/// Extract addresses from a script public key.
54///
55#[inline]
56pub fn get_addresses_from_script(script_pub_key: &str) -> OpResult<ScriptInfo> {
57 let script = Script::from_hex(script_pub_key)?;
58 Ok(evaluate_script(&script, Network::Bitcoin))
59}
60
61pub struct InnerDB {
62 pub block_index: BlockIndex,
63 pub blk_file: BlkFile,
64 pub tx_db: TxDB,
65}
66
67///
68/// This is the main struct of this crate!! Click and read the doc.
69///
70/// All queries start from initializing `BitcoinDB`.
71///
72/// Note: This is an Arc wrap around `InnerDB`.
73///
74#[derive(Clone)]
75pub struct BitcoinDB(Arc<InnerDB>);
76
77impl Deref for BitcoinDB {
78 type Target = InnerDB;
79
80 fn deref(&self) -> &Self::Target {
81 self.0.deref()
82 }
83}
84
85impl BitcoinDB {
86 ///
87 /// This is the main structure for reading Bitcoin blockchain data.
88 ///
89 /// Instantiating this class by passing the `-datadir` directory of
90 /// Bitcoin core to the `new()` method.
91 /// `tx_index`: whether to try to open tx_index levelDB.
92 ///
93 /// # Example
94 ///
95 /// ```rust
96 /// use bitcoin_explorer::BitcoinDB;
97 /// use std::path::Path;
98 ///
99 /// let path = Path::new("/Users/me/bitcoin");
100 ///
101 /// // launch without reading txindex
102 /// let db = BitcoinDB::new(path, false).unwrap();
103 ///
104 /// // launch attempting to read txindex
105 /// let db = BitcoinDB::new(path, true).unwrap();
106 /// ```
107 pub fn new(p: &Path, tx_index: bool) -> OpResult<BitcoinDB> {
108 if !p.exists() {
109 return Err(OpError::from("data_dir does not exist"));
110 }
111 let blk_path = p.join("blocks");
112 let index_path = blk_path.join("index");
113 let block_index = BlockIndex::new(index_path.as_path())?;
114 let tx_db = if tx_index {
115 let tx_index_path = p.join("indexes").join("txindex");
116 TxDB::new(&tx_index_path, &block_index)
117 } else {
118 TxDB::null()
119 };
120 let inner = InnerDB {
121 block_index,
122 blk_file: BlkFile::new(blk_path.as_path())?,
123 tx_db,
124 };
125 Ok(BitcoinDB(Arc::new(inner)))
126 }
127
128 ///
129 /// Get the maximum height found in block index.
130 ///
131 /// Note, not all blocks lower than this height have
132 /// been downloaded (different from `get_block_count()`).
133 ///
134 /// Deprecated: use `get_block_count()`
135 ///
136 #[deprecated(since = "1.2.6", note = "use `get_block_count()` instead")]
137 pub fn get_max_height(&self) -> usize {
138 self.block_index.records.len()
139 }
140
141 ///
142 /// Get the maximum number of blocks downloaded.
143 ///
144 /// This API guarantee that block 0 to `get_block_count() - 1`
145 /// have been downloaded and available for query.
146 ///
147 pub fn get_block_count(&self) -> usize {
148 let records = self.block_index.records.len();
149 for h in 0..records {
150 // n_tx == 0 indicates that the block is not downloaded
151 if self.block_index.records.get(h).unwrap().n_tx == 0 {
152 return h;
153 }
154 }
155 records
156 }
157
158 ///
159 /// Get block header information.
160 ///
161 /// This is an in-memory query, thus very fast.
162 /// This method is useful for computing blockchain statistics.
163 ///
164 /// # Example
165 ///
166 /// ## Compute total number of transactions
167 /// ```rust
168 /// use bitcoin_explorer::BitcoinDB;
169 /// use std::path::Path;
170 ///
171 /// let path = Path::new("/Users/me/bitcoin");
172 ///
173 /// // launch without reading txindex
174 /// let db = BitcoinDB::new(path, false).unwrap();
175 ///
176 /// let mut total_number_of_tx: usize = 0;
177 ///
178 /// // This computation should finish immediately. No Disk Access.
179 /// for i in 0..db.get_block_count() {
180 /// let header = db.get_header(i).unwrap();
181 /// total_number_of_tx += header.n_tx as usize;
182 /// }
183 /// println!("total number of transactions found on disk : {}.", total_number_of_tx);
184 /// ```
185 ///
186 pub fn get_header(&self, height: usize) -> OpResult<&BlockIndexRecord> {
187 if let Some(header) = self.block_index.records.get(height) {
188 Ok(header)
189 } else {
190 Err(OpError::from("height not found"))
191 }
192 }
193
194 ///
195 /// Get block hash of a certain height.
196 ///
197 pub fn get_hash_from_height(&self, height: usize) -> OpResult<BlockHash> {
198 match self.block_index.records.get(height) {
199 None => Err(OpError::from("height not found")),
200 Some(s) => Ok(s.block_header.block_hash()),
201 }
202 }
203
204 ///
205 /// Get block height of certain hash.
206 ///
207 /// Note that the hash is a hex string of the block hash.
208 ///
209 pub fn get_height_from_hash(&self, hash: &BlockHash) -> OpResult<usize> {
210 match self.block_index.hash_to_height.get(hash) {
211 None => Err(OpError::from("hash not found")),
212 Some(h) => Ok(*h as usize),
213 }
214 }
215
216 ///
217 /// Get a raw block as bytes
218 ///
219 pub fn get_raw_block(&self, height: usize) -> OpResult<Vec<u8>> {
220 if let Some(index) = self.block_index.records.get(height) {
221 let blk = self
222 .blk_file
223 .read_raw_block(index.n_file, index.n_data_pos)?;
224 Ok(blk)
225 } else {
226 Err(OpError::from("height not found"))
227 }
228 }
229
230 ///
231 /// Get a block (in different formats (Block, FBlock, SBlock))
232 ///
233 /// # Example
234 /// ```rust
235 /// use bitcoin_explorer::{BitcoinDB, FBlock, SBlock, Block};
236 /// use std::path::Path;
237 ///
238 /// let path = Path::new("/Users/me/bitcoin");
239 ///
240 /// // launch without reading txindex
241 /// let db = BitcoinDB::new(path, false).unwrap();
242 ///
243 /// // get block of height 600000 (in different formats)
244 /// let block: Block = db.get_block(600000).unwrap();
245 /// let block: FBlock = db.get_block(600000).unwrap();
246 /// let block: SBlock = db.get_block(600000).unwrap();
247 /// ```
248 ///
249 pub fn get_block<T: From<Block>>(&self, height: usize) -> OpResult<T> {
250 if let Some(index) = self.block_index.records.get(height) {
251 let blk = self.blk_file.read_block(index.n_file, index.n_data_pos)?;
252 Ok(blk.into())
253 } else {
254 Err(OpError::from("height not found"))
255 }
256 }
257
258 ///
259 /// Get a transaction by providing txid.
260 ///
261 /// This function requires `txindex` to be set to `true` for `BitcoinDB`,
262 /// and requires that flag `txindex=1` has been enabled when
263 /// running Bitcoin Core.
264 ///
265 /// A transaction cannot be found using this function if it is
266 /// not yet indexed using `txindex`.
267 ///
268 /// # Example
269 /// ```rust
270 /// use bitcoin_explorer::{BitcoinDB, Transaction, FTransaction, STransaction, Txid, FromHex};
271 /// use std::path::Path;
272 ///
273 /// let path = Path::new("/Users/me/bitcoin");
274 ///
275 /// // !!must launch with txindex=true!!
276 /// let db = BitcoinDB::new(path, true).unwrap();
277 ///
278 /// // get transaction
279 /// // e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468
280 /// let txid_str = "e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468";
281 /// let txid = Txid::from_hex(txid_str).unwrap();
282 ///
283 /// // get transactions in different formats
284 /// let tx: Transaction = db.get_transaction(&txid).unwrap();
285 /// let tx: FTransaction = db.get_transaction(&txid).unwrap();
286 /// let tx: STransaction = db.get_transaction(&txid).unwrap();
287 /// ```
288 ///
289 pub fn get_transaction<T: From<Transaction>>(&self, txid: &Txid) -> OpResult<T> {
290 if !self.tx_db.is_open() {
291 return Err(OpError::from("TxDB not open"));
292 }
293 // give special treatment for genesis transaction
294 if self.tx_db.is_genesis_tx(txid) {
295 return Ok(self.get_block::<Block>(0)?.txdata.swap_remove(0).into());
296 }
297 let record = self.tx_db.get_tx_record(txid)?;
298 let tx = self
299 .blk_file
300 .read_transaction(record.n_file, record.n_pos, record.n_tx_offset)?;
301 Ok(tx.into())
302 }
303
304 ///
305 /// Get the height of the block containing a particular transaction.
306 ///
307 /// This function requires `txindex` to be set to `true` for `BitcoinDB`,
308 /// and requires that flag `txindex=1` has been enabled when
309 /// running Bitcoin Core.
310 ///
311 /// A transaction cannot be found using this function if it is
312 /// not yet indexed using `txindex`.
313 ///
314 pub fn get_height_of_transaction(&self, txid: &Txid) -> OpResult<usize> {
315 if !self.tx_db.is_open() {
316 return Err(OpError::from("TxDB not open"));
317 }
318 self.tx_db.get_block_height_of_tx(txid)
319 }
320
321 ///
322 /// Iterate through all blocks from `start` to `end` (excluded).
323 ///
324 /// Formats: `Block` / `FBlock` / `SBlock`.
325 ///
326 /// # Performance
327 ///
328 /// This iterator is implemented to read the blocks in concurrency,
329 /// but the result is still produced in sequential order.
330 /// Results read are stored in a synced queue for `next()`
331 /// to get.
332 ///
333 /// The iterator stops automatically when a block cannot be
334 /// read (i.e., when the max height in the database met).
335 ///
336 /// This is a very efficient implementation.
337 /// Using SSD and intel core i7 (4 core, 8 threads)
338 /// Iterating from height 0 to 700000 takes about 10 minutes.
339 ///
340 /// # Example
341 ///
342 /// ```rust
343 /// use bitcoin_explorer::{BitcoinDB, Block, SBlock, FBlock};
344 /// use std::path::Path;
345 ///
346 /// let path = Path::new("/Users/me/bitcoin");
347 ///
348 /// // launch without reading txindex
349 /// let db = BitcoinDB::new(path, false).unwrap();
350 ///
351 /// // iterate over block from 600000 to 700000
352 /// for block in db.iter_block::<Block>(600000, 700000) {
353 /// for tx in block.txdata {
354 /// println!("do something for this transaction");
355 /// }
356 /// }
357 ///
358 /// // iterate over block from 600000 to 700000
359 /// for block in db.iter_block::<FBlock>(600000, 700000) {
360 /// for tx in block.txdata {
361 /// println!("do something for this transaction");
362 /// }
363 /// }
364 ///
365 /// // iterate over block from 600000 to 700000
366 /// for block in db.iter_block::<SBlock>(600000, 700000) {
367 /// for tx in block.txdata {
368 /// println!("do something for this transaction");
369 /// }
370 /// }
371 /// ```
372 ///
373 pub fn iter_block<T>(&self, start: usize, end: usize) -> BlockIter<T>
374 where
375 T: From<Block> + Send + 'static,
376 {
377 BlockIter::from_range(self, start, end)
378 }
379
380 ///
381 /// Iterate through all blocks of given heights.
382 ///
383 /// Formats: `Block` / `FBlock` / `SBlock`.
384 ///
385 /// # Performance
386 ///
387 /// This iterator is implemented to read the blocks in concurrency,
388 /// but the result is still produced in the given order in `heights`.
389 /// Results read are stored in a synced queue for `next()`
390 /// to get.
391 ///
392 /// This is a very efficient implementation.
393 /// Using SSD and intel core i7 (4 core, 8 threads)
394 /// Iterating from height 0 to 700000 takes about 10 minutes.
395 ///
396 /// ## Fails Fast
397 ///
398 /// The iterator stops immediately when a `height` cannot be found.
399 ///
400 /// # Example
401 ///
402 /// ```rust
403 /// use bitcoin_explorer::{BitcoinDB, Block, FBlock, SBlock};
404 /// use std::path::Path;
405 ///
406 /// let path = Path::new("/Users/me/bitcoin");
407 ///
408 /// // launch without reading txindex
409 /// let db = BitcoinDB::new(path, false).unwrap();
410 ///
411 /// let some_heights = vec![3, 5, 7, 9];
412 ///
413 /// // iterate over blocks from 600000 to 700000
414 /// for block in db.iter_heights::<Block, _>(some_heights.clone()) {
415 /// for tx in block.txdata {
416 /// println!("do something for this transaction");
417 /// }
418 /// }
419 ///
420 /// // iterate over simple blocks from 600000 to 700000
421 /// for block in db.iter_heights::<SBlock, _>(some_heights.clone()) {
422 /// for tx in block.txdata {
423 /// println!("do something for this transaction");
424 /// }
425 /// }
426 ///
427 /// // iterate over full blocks from 600000 to 700000
428 /// for block in db.iter_heights::<FBlock, _>(some_heights.clone()) {
429 /// for tx in block.txdata {
430 /// println!("do something for this transaction");
431 /// }
432 /// }
433 ///
434 ///
435 /// ```
436 ///
437 pub fn iter_heights<T, TIter>(&self, heights: TIter) -> BlockIter<T>
438 where
439 T: 'static + From<Block> + Send,
440 TIter: IntoIterator<Item = usize> + Send + 'static,
441 <TIter as IntoIterator>::IntoIter: Send + 'static,
442 {
443 BlockIter::new(self, heights)
444 }
445}