bitceptron_retriever/client/
mod.rs1pub mod client_setting;
2pub mod dump_utxout_set_result;
3
4use std::{fs, path::PathBuf, str::FromStr, sync::Arc, time::Duration};
5
6use bitcoincore_rpc::{jsonrpc::serde_json::Value, Auth, RpcApi};
7use tracing::{error, info};
8use zeroize::{Zeroize, ZeroizeOnDrop};
9
10use crate::{
11 error::RetrieverError,
12 path_pairs::{PathScanRequestDescriptorTrio, PathScanResultDescriptorTrio},
13};
14
15use self::{client_setting::ClientSetting, dump_utxout_set_result::DumpTxoutSetResult};
16
17#[derive(Debug, Clone)]
18pub struct BitcoincoreRpcClient {
19 client: Arc<bitcoincore_rpc::Client>,
20}
21
22impl Default for BitcoincoreRpcClient {
23 fn default() -> Self {
24 Self {
25 client: Arc::new(bitcoincore_rpc::Client::new("0.0.0.0", Auth::None).unwrap()),
26 }
27 }
28}
29
30impl BitcoincoreRpcClient {
31 pub async fn new(setting: ClientSetting) -> Result<Self, RetrieverError> {
32 info!("Creation of bitcoincore rpc client started.");
33 let (client_result_sender, mut client_result_receiver) =
34 tokio::sync::mpsc::unbounded_channel();
35 let (user, pass) = Auth::CookieFile(PathBuf::from_str(setting.get_cookie_path()).unwrap())
36 .get_user_pass()?;
37 tokio::task::spawn_blocking(move || {
38 let jsonrpc_build = bitcoincore_rpc::jsonrpc::simple_http::Builder::new()
39 .timeout(Duration::from_secs(*setting.get_timeout_seconds()))
40 .auth(user.unwrap(), pass)
41 .url(format!("{}:{}", setting.get_rpc_url(), setting.get_rpc_port()).as_str())
42 .map_err(|err| client_result_sender.send(Err(RetrieverError::from(err))))
43 .unwrap()
44 .build();
45 let jsonrpc_client = bitcoincore_rpc::jsonrpc::Client::from(jsonrpc_build);
46 let client = bitcoincore_rpc::Client::from_jsonrpc(jsonrpc_client);
47 info!("Creation of bitcoincore rpc client finished successfully.");
48 match client.ping() {
49 Ok(_) => {
50 info!("Bitcoincore rpc client responded successfully to ping.");
51 let _ = client_result_sender.send(Ok(BitcoincoreRpcClient {
52 client: Arc::new(client),
53 }));
54 }
55 Err(_) => {
56 error!("Bitcoincore rpc client did not respond to the ping.");
57 let _ =
58 client_result_sender.send(Err(RetrieverError::BitcoincoreRpcUnreachable));
59 }
60 };
61 });
62
63 client_result_receiver.recv().await.unwrap()
64 }
65
66 pub async fn dump_utxo_set(
67 &self,
68 data_dump_dir_path: &str,
69 ) -> Result<DumpTxoutSetResult, RetrieverError> {
70 let dir_path = PathBuf::from_str(data_dump_dir_path).unwrap();
71 let mut file_path = dir_path.clone();
72 file_path.extend(["utxo_dump.dat"]);
73 if file_path.exists() {
74 error!("Dump file already exists in datadir.");
75 return Err(RetrieverError::DumpFileAlreadyExistsInPath);
76 }
77 fs::create_dir_all(&dir_path)?;
78 let client = self.client.clone();
79 let (response_sender, response_receiver) = tokio::sync::oneshot::channel();
80 tokio::task::spawn_blocking(move || {
81 info!("Requesting the utxo dump file from bitcoincore.");
82 let response = client.call::<DumpTxoutSetResult>(
83 "dumptxoutset",
84 &[Value::String(file_path.to_str().unwrap().to_string())],
85 );
86 info!("Utxo dump file fetched from bitcoincore successfully.");
87 let _ = response_sender.send(response);
88 });
89
90 Ok(response_receiver.await.unwrap()?)
91 }
92
93 pub async fn scan_utxo_set(
94 &self,
95 scan_requests: Vec<PathScanRequestDescriptorTrio>,
96 ) -> Result<Vec<PathScanResultDescriptorTrio>, RetrieverError> {
97 info!("Scanning the utxo set for details of non-empty ScriptPubKeys.");
98 let (results_sender, mut results_receiver) = tokio::sync::mpsc::unbounded_channel();
99 let client = self.client.clone();
100 tokio::task::spawn_blocking(move || {
101 let mut results = vec![];
102 for PathScanRequestDescriptorTrio(path, request, descriptor) in scan_requests {
103 info!("Scan request sent to bitcoincore.");
104 results.push(PathScanResultDescriptorTrio::new(
105 path,
106 client
107 .scan_tx_out_set_blocking(&[request])
108 .map_err(|err| results_sender.send(Err(RetrieverError::from(err))))
109 .unwrap(),
110 descriptor,
111 ));
112 info!("Scan result received from bitcoincore.");
113 }
114 info!("Bitcoincore scan for details completed.");
115 let _ = results_sender.send(Ok(results));
116 });
117 let results = results_receiver.recv().await.unwrap();
118 results
119 }
120}
121
122impl Zeroize for BitcoincoreRpcClient {
123 fn zeroize(&mut self) {
124 let client = bitcoincore_rpc::Client::new(
125 "0.0.0.0:0000",
126 Auth::CookieFile(PathBuf::from_str("/cookie/jar/obviously").unwrap()),
127 )
128 .unwrap();
129 self.client = Arc::new(client);
130 }
131}
132
133impl ZeroizeOnDrop for BitcoincoreRpcClient {}