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}