bitcoind_client/
lib.rs

1#![crate_name = "bitcoind_client"]
2
3//! A bitcoind RPC client.
4
5#![forbid(unsafe_code)]
6#![allow(bare_trait_objects)]
7#![allow(ellipsis_inclusive_range_patterns)]
8#![warn(rustdoc::broken_intra_doc_links)]
9#![warn(missing_docs)]
10
11/// Bitcoind RPC client
12pub mod bitcoind_client;
13mod convert;
14#[cfg(feature = "dummy-source")]
15/// Dummy source for testing
16pub mod dummy;
17/// Esplora RPC client
18pub mod esplora_client;
19/// Chain follower with SPV proof generation
20pub mod follower;
21#[cfg(test)]
22mod test_utils;
23/// Chain follower with TXOO+SPV proof generation
24pub mod txoo_follower;
25
26pub use self::bitcoind_client::{BitcoindClient, BlockSource};
27pub use self::convert::BlockchainInfo;
28pub use crate::bitcoind_client::bitcoind_client_from_url;
29use crate::esplora_client::EsploraClient;
30use async_trait::async_trait;
31use bitcoin::OutPoint;
32use bitcoin::{Network, Transaction};
33use core::fmt;
34use std::fmt::{Display, Formatter};
35use std::path::PathBuf;
36use url::Url;
37
38/// RPC errors
39#[derive(Debug)]
40pub enum Error {
41    /// JSON RPC Error
42    JsonRpc(jsonrpc_async::error::Error),
43    /// JSON Error
44    Json(serde_json::error::Error),
45    /// IO Error
46    Io(std::io::Error),
47    /// Esplora Error
48    Esplora(String),
49}
50
51impl Display for Error {
52    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
53        f.write_str(format!("{:?}", self).as_str())
54    }
55}
56
57impl std::error::Error for Error {}
58
59impl From<jsonrpc_async::error::Error> for Error {
60    fn from(e: jsonrpc_async::error::Error) -> Error {
61        Error::JsonRpc(e)
62    }
63}
64
65impl From<serde_json::error::Error> for Error {
66    fn from(e: serde_json::error::Error) -> Error {
67        Error::Json(e)
68    }
69}
70
71impl From<std::io::Error> for Error {
72    fn from(e: std::io::Error) -> Error {
73        Error::Io(e)
74    }
75}
76
77/// A trait for a generic block source
78#[async_trait]
79pub trait Explorer {
80    /// Get number of confirmations when an outpoint is confirmed and unspent
81    /// Returns None if the outpoint is not confirmed or is spent
82    async fn get_utxo_confirmations(&self, txout: &OutPoint) -> Result<Option<u64>, Error>;
83
84    /// Broadcast transaction
85    async fn broadcast_transaction(&self, tx: &Transaction) -> Result<(), Error>;
86
87    /// Get the transaction that spends the given outpoint, if exists in chain
88    async fn get_utxo_spending_tx(&self, txout: &OutPoint) -> Result<Option<Transaction>, Error>;
89}
90
91/// The block explorer type
92pub enum BlockExplorerType {
93    /// A bitcoind RPC client "explorer"
94    Bitcoind,
95    /// The Blockstream Esplora block explorer
96    Esplora,
97}
98
99/// Construct a block explorer client from an RPC URL, a network and a block explorer type
100pub async fn explorer_from_url(
101    network: Network,
102    block_explorer_type: BlockExplorerType,
103    url: Url,
104) -> Box<dyn Explorer> {
105    match block_explorer_type {
106        BlockExplorerType::Bitcoind => Box::new(bitcoind_client_from_url(url, network).await),
107        BlockExplorerType::Esplora => Box::new(EsploraClient::new(url).await),
108    }
109}
110
111fn bitcoin_network_path(base_path: PathBuf, network: Network) -> PathBuf {
112    match network {
113        Network::Bitcoin => base_path,
114        Network::Testnet => base_path.join("testnet3"),
115        Network::Signet => base_path.join("signet"),
116        Network::Regtest => base_path.join("regtest"),
117        _ => unreachable!()
118    }
119}
120
121/// use the supplied RPC url, or default it bassd on the network
122pub fn default_bitcoin_rpc_url(rpc: Option<String>, network: Network) -> String {
123    rpc.unwrap_or_else(|| {
124        match network {
125            Network::Bitcoin => "http://localhost:8332",
126            Network::Testnet => "http://localhost:18332",
127            Network::Signet => "http://localhost:38442",
128            Network::Regtest => "http://localhost:18443",
129            _ => unreachable!(),
130        }
131        .to_owned()
132    })
133}