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.blk_file.read_raw_block(index.n_file, index.n_data_pos)?;
222            Ok(blk)
223        } else {
224            Err(OpError::from("height not found"))
225        }
226    }
227
228    ///
229    /// Get a block (in different formats (Block, FBlock, SBlock))
230    ///
231    /// # Example
232    /// ```rust
233    /// use bitcoin_explorer::{BitcoinDB, FBlock, SBlock, Block};
234    /// use std::path::Path;
235    ///
236    /// let path = Path::new("/Users/me/bitcoin");
237    ///
238    /// // launch without reading txindex
239    /// let db = BitcoinDB::new(path, false).unwrap();
240    ///
241    /// // get block of height 600000 (in different formats)
242    /// let block: Block = db.get_block(600000).unwrap();
243    /// let block: FBlock = db.get_block(600000).unwrap();
244    /// let block: SBlock = db.get_block(600000).unwrap();
245    /// ```
246    ///
247    pub fn get_block<T: From<Block>>(&self, height: usize) -> OpResult<T> {
248        if let Some(index) = self.block_index.records.get(height) {
249            let blk = self.blk_file.read_block(index.n_file, index.n_data_pos)?;
250            Ok(blk.into())
251        } else {
252            Err(OpError::from("height not found"))
253        }
254    }
255
256    ///
257    /// Get a transaction by providing txid.
258    ///
259    /// This function requires `txindex` to be set to `true` for `BitcoinDB`,
260    /// and requires that flag `txindex=1` has been enabled when
261    /// running Bitcoin Core.
262    ///
263    /// A transaction cannot be found using this function if it is
264    /// not yet indexed using `txindex`.
265    ///
266    /// # Example
267    /// ```rust
268    /// use bitcoin_explorer::{BitcoinDB, Transaction, FTransaction, STransaction, Txid, FromHex};
269    /// use std::path::Path;
270    ///
271    /// let path = Path::new("/Users/me/bitcoin");
272    ///
273    /// // !!must launch with txindex=true!!
274    /// let db = BitcoinDB::new(path, true).unwrap();
275    ///
276    /// // get transaction
277    /// // e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468
278    /// let txid_str = "e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468";
279    /// let txid = Txid::from_hex(txid_str).unwrap();
280    ///
281    /// // get transactions in different formats
282    /// let tx: Transaction = db.get_transaction(&txid).unwrap();
283    /// let tx: FTransaction = db.get_transaction(&txid).unwrap();
284    /// let tx: STransaction = db.get_transaction(&txid).unwrap();
285    /// ```
286    ///
287    pub fn get_transaction<T: From<Transaction>>(&self, txid: &Txid) -> OpResult<T> {
288        if !self.tx_db.is_open() {
289            return Err(OpError::from("TxDB not open"));
290        }
291        // give special treatment for genesis transaction
292        if self.tx_db.is_genesis_tx(txid) {
293            return Ok(self.get_block::<Block>(0)?.txdata.swap_remove(0).into());
294        }
295        let record = self.tx_db.get_tx_record(txid)?;
296        let tx = self
297            .blk_file
298            .read_transaction(record.n_file, record.n_pos, record.n_tx_offset)?;
299        Ok(tx.into())
300    }
301
302    ///
303    /// Get the height of the block containing a particular transaction.
304    ///
305    /// This function requires `txindex` to be set to `true` for `BitcoinDB`,
306    /// and requires that flag `txindex=1` has been enabled when
307    /// running Bitcoin Core.
308    ///
309    /// A transaction cannot be found using this function if it is
310    /// not yet indexed using `txindex`.
311    ///
312    pub fn get_height_of_transaction(&self, txid: &Txid) -> OpResult<usize> {
313        if !self.tx_db.is_open() {
314            return Err(OpError::from("TxDB not open"));
315        }
316        self.tx_db.get_block_height_of_tx(txid)
317    }
318
319    ///
320    /// Iterate through all blocks from `start` to `end` (excluded).
321    ///
322    /// Formats: `Block` / `FBlock` / `SBlock`.
323    ///
324    /// # Performance
325    ///
326    /// This iterator is implemented to read the blocks in concurrency,
327    /// but the result is still produced in sequential order.
328    /// Results read are stored in a synced queue for `next()`
329    /// to get.
330    ///
331    /// The iterator stops automatically when a block cannot be
332    /// read (i.e., when the max height in the database met).
333    ///
334    /// This is a very efficient implementation.
335    /// Using SSD and intel core i7 (4 core, 8 threads)
336    /// Iterating from height 0 to 700000 takes about 10 minutes.
337    ///
338    /// # Example
339    ///
340    /// ```rust
341    /// use bitcoin_explorer::{BitcoinDB, Block, SBlock, FBlock};
342    /// use std::path::Path;
343    ///
344    /// let path = Path::new("/Users/me/bitcoin");
345    ///
346    /// // launch without reading txindex
347    /// let db = BitcoinDB::new(path, false).unwrap();
348    ///
349    /// // iterate over block from 600000 to 700000
350    /// for block in db.iter_block::<Block>(600000, 700000) {
351    ///     for tx in block.txdata {
352    ///         println!("do something for this transaction");
353    ///     }
354    /// }
355    ///
356    /// // iterate over block from 600000 to 700000
357    /// for block in db.iter_block::<FBlock>(600000, 700000) {
358    ///     for tx in block.txdata {
359    ///         println!("do something for this transaction");
360    ///     }
361    /// }
362    ///
363    /// // iterate over block from 600000 to 700000
364    /// for block in db.iter_block::<SBlock>(600000, 700000) {
365    ///     for tx in block.txdata {
366    ///         println!("do something for this transaction");
367    ///     }
368    /// }
369    /// ```
370    ///
371    pub fn iter_block<T>(&self, start: usize, end: usize) -> BlockIter<T>
372    where
373        T: From<Block> + Send + 'static,
374    {
375        BlockIter::from_range(self, start, end)
376    }
377
378    ///
379    /// Iterate through all blocks of given heights.
380    ///
381    /// Formats: `Block` / `FBlock` / `SBlock`.
382    ///
383    /// # Performance
384    ///
385    /// This iterator is implemented to read the blocks in concurrency,
386    /// but the result is still produced in the given order in `heights`.
387    /// Results read are stored in a synced queue for `next()`
388    /// to get.
389    ///
390    /// This is a very efficient implementation.
391    /// Using SSD and intel core i7 (4 core, 8 threads)
392    /// Iterating from height 0 to 700000 takes about 10 minutes.
393    ///
394    /// ## Fails Fast
395    ///
396    /// The iterator stops immediately when a `height` cannot be found.
397    ///
398    /// # Example
399    ///
400    /// ```rust
401    /// use bitcoin_explorer::{BitcoinDB, Block, FBlock, SBlock};
402    /// use std::path::Path;
403    ///
404    /// let path = Path::new("/Users/me/bitcoin");
405    ///
406    /// // launch without reading txindex
407    /// let db = BitcoinDB::new(path, false).unwrap();
408    ///
409    /// let some_heights = vec![3, 5, 7, 9];
410    ///
411    /// // iterate over blocks from 600000 to 700000
412    /// for block in db.iter_heights::<Block, _>(some_heights.clone()) {
413    ///     for tx in block.txdata {
414    ///         println!("do something for this transaction");
415    ///     }
416    /// }
417    ///
418    /// // iterate over simple blocks from 600000 to 700000
419    /// for block in db.iter_heights::<SBlock, _>(some_heights.clone()) {
420    ///     for tx in block.txdata {
421    ///         println!("do something for this transaction");
422    ///     }
423    /// }
424    ///
425    /// // iterate over full blocks from 600000 to 700000
426    /// for block in db.iter_heights::<FBlock, _>(some_heights.clone()) {
427    ///     for tx in block.txdata {
428    ///         println!("do something for this transaction");
429    ///     }
430    /// }
431    ///
432    ///
433    /// ```
434    ///
435    pub fn iter_heights<T, TIter>(&self, heights: TIter) -> BlockIter<T>
436    where
437        T: 'static + From<Block> + Send,
438        TIter: IntoIterator<Item = usize> + Send + 'static,
439        <TIter as IntoIterator>::IntoIter: Send + 'static,
440    {
441        BlockIter::new(self, heights)
442    }
443}