Make an initial dummy RPC call to check that the connection works
[ldk-sample] / src / bitcoind_client.rs
1 use crate::convert::{BlockchainInfo, FeeResponse, FundedTx, NewAddress, RawTx, SignedTx};
2 use base64;
3 use bitcoin::blockdata::block::Block;
4 use bitcoin::blockdata::transaction::Transaction;
5 use bitcoin::consensus::encode;
6 use bitcoin::hash_types::BlockHash;
7 use bitcoin::util::address::Address;
8 use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
9 use lightning_block_sync::http::HttpEndpoint;
10 use lightning_block_sync::rpc::RpcClient;
11 use lightning_block_sync::{AsyncBlockSourceResult, BlockHeaderData, BlockSource};
12 use serde_json;
13 use std::collections::HashMap;
14 use std::str::FromStr;
15 use std::sync::atomic::{AtomicU32, Ordering};
16 use std::sync::Arc;
17 use std::time::Duration;
18 use tokio::sync::Mutex;
19
20 pub struct BitcoindClient {
21         bitcoind_rpc_client: Arc<Mutex<RpcClient>>,
22         host: String,
23         port: u16,
24         rpc_user: String,
25         rpc_password: String,
26         fees: Arc<HashMap<Target, AtomicU32>>,
27 }
28
29 #[derive(Clone, Eq, Hash, PartialEq)]
30 pub enum Target {
31         Background,
32         Normal,
33         HighPriority,
34 }
35
36 impl BlockSource for &BitcoindClient {
37         fn get_header<'a>(
38                 &'a mut self, header_hash: &'a BlockHash, height_hint: Option<u32>,
39         ) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
40                 Box::pin(async move {
41                         let mut rpc = self.bitcoind_rpc_client.lock().await;
42                         rpc.get_header(header_hash, height_hint).await
43                 })
44         }
45
46         fn get_block<'a>(
47                 &'a mut self, header_hash: &'a BlockHash,
48         ) -> AsyncBlockSourceResult<'a, Block> {
49                 Box::pin(async move {
50                         let mut rpc = self.bitcoind_rpc_client.lock().await;
51                         rpc.get_block(header_hash).await
52                 })
53         }
54
55         fn get_best_block<'a>(&'a mut self) -> AsyncBlockSourceResult<(BlockHash, Option<u32>)> {
56                 Box::pin(async move {
57                         let mut rpc = self.bitcoind_rpc_client.lock().await;
58                         rpc.get_best_block().await
59                 })
60         }
61 }
62
63 impl BitcoindClient {
64         pub async fn new(
65                 host: String, port: u16, rpc_user: String, rpc_password: String,
66         ) -> std::io::Result<Self> {
67                 let http_endpoint = HttpEndpoint::for_host(host.clone()).with_port(port);
68                 let rpc_credentials =
69                         base64::encode(format!("{}:{}", rpc_user.clone(), rpc_password.clone()));
70                 let mut bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?;
71                 let _dummy = bitcoind_rpc_client.call_method::<BlockchainInfo>("getblockchaininfo", &vec![]).await
72                         .map_err(|_| std::io::Error::new(std::io::ErrorKind::PermissionDenied,
73                                 "Failed to make initial call to bitcoind - please check your RPC user/password and access settings"))?;
74                 let mut fees: HashMap<Target, AtomicU32> = HashMap::new();
75                 fees.insert(Target::Background, AtomicU32::new(253));
76                 fees.insert(Target::Normal, AtomicU32::new(2000));
77                 fees.insert(Target::HighPriority, AtomicU32::new(5000));
78                 let client = Self {
79                         bitcoind_rpc_client: Arc::new(Mutex::new(bitcoind_rpc_client)),
80                         host,
81                         port,
82                         rpc_user,
83                         rpc_password,
84                         fees: Arc::new(fees),
85                 };
86                 BitcoindClient::poll_for_fee_estimates(
87                         client.fees.clone(),
88                         client.bitcoind_rpc_client.clone(),
89                 )
90                 .await;
91                 Ok(client)
92         }
93
94         async fn poll_for_fee_estimates(
95                 fees: Arc<HashMap<Target, AtomicU32>>, rpc_client: Arc<Mutex<RpcClient>>,
96         ) {
97                 tokio::spawn(async move {
98                         loop {
99                                 let background_estimate = {
100                                         let mut rpc = rpc_client.lock().await;
101                                         let background_conf_target = serde_json::json!(144);
102                                         let background_estimate_mode = serde_json::json!("ECONOMICAL");
103                                         let resp = rpc
104                                                 .call_method::<FeeResponse>(
105                                                         "estimatesmartfee",
106                                                         &vec![background_conf_target, background_estimate_mode],
107                                                 )
108                                                 .await
109                                                 .unwrap();
110                                         match resp.feerate {
111                                                 Some(fee) => fee,
112                                                 None => 253,
113                                         }
114                                 };
115                                 // if background_estimate.
116
117                                 let normal_estimate = {
118                                         let mut rpc = rpc_client.lock().await;
119                                         let normal_conf_target = serde_json::json!(18);
120                                         let normal_estimate_mode = serde_json::json!("ECONOMICAL");
121                                         let resp = rpc
122                                                 .call_method::<FeeResponse>(
123                                                         "estimatesmartfee",
124                                                         &vec![normal_conf_target, normal_estimate_mode],
125                                                 )
126                                                 .await
127                                                 .unwrap();
128                                         match resp.feerate {
129                                                 Some(fee) => fee,
130                                                 None => 2000,
131                                         }
132                                 };
133
134                                 let high_prio_estimate = {
135                                         let mut rpc = rpc_client.lock().await;
136                                         let high_prio_conf_target = serde_json::json!(6);
137                                         let high_prio_estimate_mode = serde_json::json!("CONSERVATIVE");
138                                         let resp = rpc
139                                                 .call_method::<FeeResponse>(
140                                                         "estimatesmartfee",
141                                                         &vec![high_prio_conf_target, high_prio_estimate_mode],
142                                                 )
143                                                 .await
144                                                 .unwrap();
145
146                                         match resp.feerate {
147                                                 Some(fee) => fee,
148                                                 None => 5000,
149                                         }
150                                 };
151
152                                 fees.get(&Target::Background)
153                                         .unwrap()
154                                         .store(background_estimate, Ordering::Release);
155                                 fees.get(&Target::Normal).unwrap().store(normal_estimate, Ordering::Release);
156                                 fees.get(&Target::HighPriority)
157                                         .unwrap()
158                                         .store(high_prio_estimate, Ordering::Release);
159                                 tokio::time::sleep(Duration::from_secs(60)).await;
160                         }
161                 });
162         }
163
164         pub fn get_new_rpc_client(&self) -> std::io::Result<RpcClient> {
165                 let http_endpoint = HttpEndpoint::for_host(self.host.clone()).with_port(self.port);
166                 let rpc_credentials =
167                         base64::encode(format!("{}:{}", self.rpc_user.clone(), self.rpc_password.clone()));
168                 RpcClient::new(&rpc_credentials, http_endpoint)
169         }
170
171         pub async fn create_raw_transaction(&self, outputs: Vec<HashMap<String, f64>>) -> RawTx {
172                 let mut rpc = self.bitcoind_rpc_client.lock().await;
173
174                 let outputs_json = serde_json::json!(outputs);
175                 rpc.call_method::<RawTx>("createrawtransaction", &vec![serde_json::json!([]), outputs_json])
176                         .await
177                         .unwrap()
178         }
179
180         pub async fn fund_raw_transaction(&self, raw_tx: RawTx) -> FundedTx {
181                 let mut rpc = self.bitcoind_rpc_client.lock().await;
182
183                 let raw_tx_json = serde_json::json!(raw_tx.0);
184                 rpc.call_method("fundrawtransaction", &[raw_tx_json]).await.unwrap()
185         }
186
187         pub async fn send_raw_transaction(&self, raw_tx: RawTx) {
188                 let mut rpc = self.bitcoind_rpc_client.lock().await;
189
190                 let raw_tx_json = serde_json::json!(raw_tx.0);
191                 rpc.call_method::<RawTx>("sendrawtransaction", &[raw_tx_json]).await.unwrap();
192         }
193
194         pub async fn sign_raw_transaction_with_wallet(&self, tx_hex: String) -> SignedTx {
195                 let mut rpc = self.bitcoind_rpc_client.lock().await;
196
197                 let tx_hex_json = serde_json::json!(tx_hex);
198                 rpc.call_method("signrawtransactionwithwallet", &vec![tx_hex_json]).await.unwrap()
199         }
200
201         pub async fn get_new_address(&self) -> Address {
202                 let mut rpc = self.bitcoind_rpc_client.lock().await;
203
204                 let addr_args = vec![serde_json::json!("LDK output address")];
205                 let addr = rpc.call_method::<NewAddress>("getnewaddress", &addr_args).await.unwrap();
206                 Address::from_str(addr.0.as_str()).unwrap()
207         }
208
209         pub async fn get_blockchain_info(&self) -> BlockchainInfo {
210                 let mut rpc = self.bitcoind_rpc_client.lock().await;
211                 rpc.call_method::<BlockchainInfo>("getblockchaininfo", &vec![]).await.unwrap()
212         }
213 }
214
215 impl FeeEstimator for BitcoindClient {
216         fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
217                 match confirmation_target {
218                         ConfirmationTarget::Background => {
219                                 self.fees.get(&Target::Background).unwrap().load(Ordering::Acquire)
220                         }
221                         ConfirmationTarget::Normal => {
222                                 self.fees.get(&Target::Normal).unwrap().load(Ordering::Acquire)
223                         }
224                         ConfirmationTarget::HighPriority => {
225                                 self.fees.get(&Target::HighPriority).unwrap().load(Ordering::Acquire)
226                         }
227                 }
228         }
229 }
230
231 impl BroadcasterInterface for BitcoindClient {
232         fn broadcast_transaction(&self, tx: &Transaction) {
233                 let bitcoind_rpc_client = self.bitcoind_rpc_client.clone();
234                 let tx_serialized = serde_json::json!(encode::serialize_hex(tx));
235                 tokio::spawn(async move {
236                         let mut rpc = bitcoind_rpc_client.lock().await;
237                         rpc.call_method::<RawTx>("sendrawtransaction", &vec![tx_serialized]).await.unwrap();
238                 });
239         }
240 }