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}