add rustfmt + CI + other fixups
[ldk-sample] / src / bitcoind_client.rs
1 use crate::convert::{BlockchainInfo, FeeResponse, FundedTx, NewAddress, RawTx, SignedTx};
2 use base64;
3 use bitcoin::blockdata::transaction::Transaction;
4 use bitcoin::consensus::encode;
5 use bitcoin::util::address::Address;
6 use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
7 use lightning_block_sync::http::HttpEndpoint;
8 use lightning_block_sync::rpc::RpcClient;
9 use serde_json;
10 use std::collections::HashMap;
11 use std::str::FromStr;
12 use std::sync::Mutex;
13 use tokio::runtime::{Handle, Runtime};
14
15 pub struct BitcoindClient {
16         bitcoind_rpc_client: Mutex<RpcClient>,
17         host: String,
18         port: u16,
19         rpc_user: String,
20         rpc_password: String,
21         runtime: Mutex<Runtime>,
22 }
23
24 impl BitcoindClient {
25         pub fn new(
26                 host: String, port: u16, rpc_user: String, rpc_password: String,
27         ) -> std::io::Result<Self> {
28                 let http_endpoint = HttpEndpoint::for_host(host.clone()).with_port(port);
29                 let rpc_credentials =
30                         base64::encode(format!("{}:{}", rpc_user.clone(), rpc_password.clone()));
31                 let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?;
32                 let client = Self {
33                         bitcoind_rpc_client: Mutex::new(bitcoind_rpc_client),
34                         host,
35                         port,
36                         rpc_user,
37                         rpc_password,
38                         runtime: Mutex::new(Runtime::new().unwrap()),
39                 };
40                 Ok(client)
41         }
42
43         pub fn get_new_rpc_client(&self) -> std::io::Result<RpcClient> {
44                 let http_endpoint = HttpEndpoint::for_host(self.host.clone()).with_port(self.port);
45                 let rpc_credentials =
46                         base64::encode(format!("{}:{}", self.rpc_user.clone(), self.rpc_password.clone()));
47                 RpcClient::new(&rpc_credentials, http_endpoint)
48         }
49
50         pub fn create_raw_transaction(&self, outputs: Vec<HashMap<String, f64>>) -> RawTx {
51                 let runtime = self.runtime.lock().unwrap();
52                 let mut rpc = self.bitcoind_rpc_client.lock().unwrap();
53
54                 let outputs_json = serde_json::json!(outputs);
55                 runtime
56                         .block_on(rpc.call_method::<RawTx>(
57                                 "createrawtransaction",
58                                 &vec![serde_json::json!([]), outputs_json],
59                         ))
60                         .unwrap()
61         }
62
63         pub fn fund_raw_transaction(&self, raw_tx: RawTx) -> FundedTx {
64                 let runtime = self.runtime.lock().unwrap();
65                 let mut rpc = self.bitcoind_rpc_client.lock().unwrap();
66
67                 let raw_tx_json = serde_json::json!(raw_tx.0);
68                 runtime.block_on(rpc.call_method("fundrawtransaction", &[raw_tx_json])).unwrap()
69         }
70
71         pub fn sign_raw_transaction_with_wallet(&self, tx_hex: String) -> SignedTx {
72                 let runtime = self.runtime.lock().unwrap();
73                 let mut rpc = self.bitcoind_rpc_client.lock().unwrap();
74
75                 let tx_hex_json = serde_json::json!(tx_hex);
76                 runtime
77                         .block_on(rpc.call_method("signrawtransactionwithwallet", &vec![tx_hex_json]))
78                         .unwrap()
79         }
80
81         pub fn get_new_address(&self) -> Address {
82                 let runtime = self.runtime.lock().unwrap();
83                 let mut rpc = self.bitcoind_rpc_client.lock().unwrap();
84
85                 let addr_args = vec![serde_json::json!("LDK output address")];
86                 let addr =
87                         runtime.block_on(rpc.call_method::<NewAddress>("getnewaddress", &addr_args)).unwrap();
88                 Address::from_str(addr.0.as_str()).unwrap()
89         }
90
91         pub fn get_blockchain_info(&self) -> BlockchainInfo {
92                 let runtime = self.runtime.lock().unwrap();
93                 let mut rpc = self.bitcoind_rpc_client.lock().unwrap();
94
95                 runtime.block_on(rpc.call_method::<BlockchainInfo>("getblockchaininfo", &vec![])).unwrap()
96         }
97 }
98
99 impl FeeEstimator for BitcoindClient {
100         fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
101                 let runtime = self.runtime.lock().unwrap();
102                 let mut rpc = self.bitcoind_rpc_client.lock().unwrap();
103
104                 let (conf_target, estimate_mode, default) = match confirmation_target {
105                         ConfirmationTarget::Background => (144, "ECONOMICAL", 253),
106                         ConfirmationTarget::Normal => (18, "ECONOMICAL", 20000),
107                         ConfirmationTarget::HighPriority => (6, "ECONOMICAL", 50000),
108                 };
109
110                 // This function may be called from a tokio runtime, or not. So we need to check before
111                 // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime".
112                 let conf_target_json = serde_json::json!(conf_target);
113                 let estimate_mode_json = serde_json::json!(estimate_mode);
114                 let resp = match Handle::try_current() {
115                         Ok(_) => tokio::task::block_in_place(|| {
116                                 runtime
117                                         .block_on(rpc.call_method::<FeeResponse>(
118                                                 "estimatesmartfee",
119                                                 &vec![conf_target_json, estimate_mode_json],
120                                         ))
121                                         .unwrap()
122                         }),
123                         _ => runtime
124                                 .block_on(rpc.call_method::<FeeResponse>(
125                                         "estimatesmartfee",
126                                         &vec![conf_target_json, estimate_mode_json],
127                                 ))
128                                 .unwrap(),
129                 };
130                 if resp.errored {
131                         return default;
132                 }
133                 resp.feerate.unwrap()
134         }
135 }
136
137 impl BroadcasterInterface for BitcoindClient {
138         fn broadcast_transaction(&self, tx: &Transaction) {
139                 let mut rpc = self.bitcoind_rpc_client.lock().unwrap();
140                 let runtime = self.runtime.lock().unwrap();
141
142                 let tx_serialized = serde_json::json!(encode::serialize_hex(tx));
143                 // This function may be called from a tokio runtime, or not. So we need to check before
144                 // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime".
145                 match Handle::try_current() {
146                         Ok(_) => {
147                                 tokio::task::block_in_place(|| {
148                                         runtime
149                                                 .block_on(
150                                                         rpc.call_method::<RawTx>("sendrawtransaction", &vec![tx_serialized]),
151                                                 )
152                                                 .unwrap();
153                                 });
154                         }
155                         _ => {
156                                 runtime
157                                         .block_on(rpc.call_method::<RawTx>("sendrawtransaction", &vec![tx_serialized]))
158                                         .unwrap();
159                         }
160                 }
161         }
162 }