1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#![crate_name = "bitcoind_client"]

//! A bitcoind RPC client.

#![forbid(unsafe_code)]
#![allow(bare_trait_objects)]
#![allow(ellipsis_inclusive_range_patterns)]
#![warn(rustdoc::broken_intra_doc_links)]
#![warn(missing_docs)]

/// Bitcoind RPC client
pub mod bitcoind_client;
mod convert;
#[cfg(feature = "dummy-source")]
/// Dummy source for testing
pub mod dummy;
/// Esplora RPC client
pub mod esplora_client;
/// Chain follower with SPV proof generation
pub mod follower;
#[cfg(test)]
mod test_utils;
/// Chain follower with TXOO+SPV proof generation
pub mod txoo_follower;

pub use self::bitcoind_client::{BitcoindClient, BlockSource};
pub use self::convert::BlockchainInfo;
pub use crate::bitcoind_client::bitcoind_client_from_url;
use crate::esplora_client::EsploraClient;
use async_trait::async_trait;
use bitcoin::OutPoint;
use bitcoin::{Network, Transaction};
use core::fmt;
use std::fmt::{Display, Formatter};
use std::path::PathBuf;
use url::Url;

/// RPC errors
#[derive(Debug)]
pub enum Error {
    /// JSON RPC Error
    JsonRpc(jsonrpc_async::error::Error),
    /// JSON Error
    Json(serde_json::error::Error),
    /// IO Error
    Io(std::io::Error),
    /// Esplora Error
    Esplora(String),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.write_str(format!("{:?}", self).as_str())
    }
}

impl std::error::Error for Error {}

impl From<jsonrpc_async::error::Error> for Error {
    fn from(e: jsonrpc_async::error::Error) -> Error {
        Error::JsonRpc(e)
    }
}

impl From<serde_json::error::Error> for Error {
    fn from(e: serde_json::error::Error) -> Error {
        Error::Json(e)
    }
}

impl From<std::io::Error> for Error {
    fn from(e: std::io::Error) -> Error {
        Error::Io(e)
    }
}

/// A trait for a generic block source
#[async_trait]
pub trait Explorer {
    /// Get number of confirmations when an outpoint is confirmed and unspent
    /// Returns None if the outpoint is not confirmed or is spent
    async fn get_utxo_confirmations(&self, txout: &OutPoint) -> Result<Option<u64>, Error>;

    /// Broadcast transaction
    async fn broadcast_transaction(&self, tx: &Transaction) -> Result<(), Error>;

    /// Get the transaction that spends the given outpoint, if exists in chain
    async fn get_utxo_spending_tx(&self, txout: &OutPoint) -> Result<Option<Transaction>, Error>;
}

/// The block explorer type
pub enum BlockExplorerType {
    /// A bitcoind RPC client "explorer"
    Bitcoind,
    /// The Blockstream Esplora block explorer
    Esplora,
}

/// Construct a block explorer client from an RPC URL, a network and a block explorer type
pub async fn explorer_from_url(
    network: Network,
    block_explorer_type: BlockExplorerType,
    url: Url,
) -> Box<dyn Explorer> {
    match block_explorer_type {
        BlockExplorerType::Bitcoind => Box::new(bitcoind_client_from_url(url, network).await),
        BlockExplorerType::Esplora => Box::new(EsploraClient::new(url).await),
    }
}

fn bitcoin_network_path(base_path: PathBuf, network: Network) -> PathBuf {
    match network {
        Network::Bitcoin => base_path,
        Network::Testnet => base_path.join("testnet3"),
        Network::Signet => base_path.join("signet"),
        Network::Regtest => base_path.join("regtest"),
        _ => unreachable!()
    }
}

/// use the supplied RPC url, or default it bassd on the network
pub fn default_bitcoin_rpc_url(rpc: Option<String>, network: Network) -> String {
    rpc.unwrap_or_else(|| {
        match network {
            Network::Bitcoin => "http://localhost:8332",
            Network::Testnet => "http://localhost:18332",
            Network::Signet => "http://localhost:38442",
            Network::Regtest => "http://localhost:18443",
            _ => unreachable!(),
        }
        .to_owned()
    })
}