bitceptron_retriever/client/
mod.rs

1pub 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 {}