bitcoinsv_rpc/
client.rs

1//! Bitcoin SV node client implementation.
2
3use crate::error::Result;
4use crate::rest::RestClient;
5use crate::rpc::RpcClient;
6use async_trait::async_trait;
7use bitcoinsv::bitcoin::{Block, BlockHash, BlockHeader};
8
9/// Trait for communicating with a Bitcoin node.
10///
11/// This trait defines the common interface for interacting with Bitcoin nodes,
12/// allowing different client implementations (e.g., SV node, Teranode) to provide
13/// the same functionality.
14#[async_trait]
15pub trait NodeClient {
16    /// Returns the hash of the best (tip) block in the longest blockchain.
17    async fn get_best_block_hash(&self) -> Result<BlockHash>;
18
19    /// Returns the block header for the specified block hash.
20    ///
21    /// # Arguments
22    ///
23    /// * `block_hash` - The hash of the block to retrieve
24    async fn get_block_header(&self, block_hash: &BlockHash) -> Result<BlockHeader>;
25
26    /// Returns the complete block data for the specified block hash.
27    ///
28    /// # Arguments
29    ///
30    /// * `block_hash` - The hash of the block to retrieve
31    async fn get_block(&self, block_hash: &BlockHash) -> Result<Block>;
32}
33
34/// Client for communicating with a Bitcoin SV node.
35///
36/// This client manages both JSON-RPC and REST API connections to a Bitcoin SV node.
37/// The client is cloneable and safe for concurrent use - clones share the same underlying
38/// connection pool for efficient resource usage.
39///
40/// # Example
41///
42/// ```no_run
43/// use bitcoinsv_rpc::{NodeClient, SvNodeClient};
44///
45/// #[tokio::main]
46/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
47///     let client = SvNodeClient::new(
48///         "http://localhost:8332",
49///         Some("user".to_string()),
50///         Some("password".to_string()),
51///     )?;
52///
53///     let hash = client.get_best_block_hash().await?;
54///     println!("Best block: {}", hash);
55///
56///     // Clone the client for concurrent use
57///     let client2 = client.clone();
58///     let (hash1, hash2) = tokio::join!(
59///         client.get_best_block_hash(),
60///         client2.get_best_block_hash(),
61///     );
62///
63///     Ok(())
64/// }
65/// ```
66#[derive(Clone)]
67pub struct SvNodeClient {
68    rpc: RpcClient,
69    rest: RestClient,
70}
71
72impl SvNodeClient {
73    /// Creates a new client connection to a Bitcoin SV node.
74    ///
75    /// # Arguments
76    ///
77    /// * `url` - The base URL of the node (e.g., "http://localhost:8332")
78    /// * `username` - Optional RPC username for authentication
79    /// * `password` - Optional RPC password for authentication
80    ///
81    /// # Errors
82    ///
83    /// Returns an error if the URL is invalid.
84    pub fn new(url: &str, username: Option<String>, password: Option<String>) -> Result<Self> {
85        let rpc = RpcClient::new(url, username, password)?;
86        let rest = RestClient::new(url)?;
87
88        Ok(Self { rpc, rest })
89    }
90}
91
92#[async_trait]
93impl NodeClient for SvNodeClient {
94    async fn get_best_block_hash(&self) -> Result<BlockHash> {
95        self.rpc.get_best_block_hash().await
96    }
97
98    async fn get_block_header(&self, block_hash: &BlockHash) -> Result<BlockHeader> {
99        self.rpc.get_block_header(block_hash).await
100    }
101
102    async fn get_block(&self, block_hash: &BlockHash) -> Result<Block> {
103        self.rest.get_block(block_hash).await
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_client_creation() {
113        let client = SvNodeClient::new(
114            "http://localhost:8332",
115            Some("user".to_string()),
116            Some("password".to_string()),
117        );
118        assert!(client.is_ok());
119    }
120
121    #[test]
122    fn test_client_creation_no_auth() {
123        let client = SvNodeClient::new("http://localhost:8332", None, None);
124        assert!(client.is_ok());
125    }
126}