Merge pull request #2660 from benthecarman/flexible-fee-rate
[rust-lightning] / lightning-block-sync / src / rpc.rs
1 //! Simple RPC client implementation which implements [`BlockSource`] against a Bitcoin Core RPC
2 //! endpoint.
3
4 use crate::{BlockData, BlockHeaderData, BlockSource, AsyncBlockSourceResult};
5 use crate::http::{HttpClient, HttpEndpoint, HttpError, JsonResponse};
6 use crate::gossip::UtxoSource;
7
8 use bitcoin::hash_types::BlockHash;
9 use bitcoin::hashes::hex::ToHex;
10 use bitcoin::OutPoint;
11
12 use std::sync::Mutex;
13
14 use serde_json;
15
16 use std::convert::TryFrom;
17 use std::convert::TryInto;
18 use std::error::Error;
19 use std::fmt;
20 use std::sync::atomic::{AtomicUsize, Ordering};
21
22 /// An error returned by the RPC server.
23 #[derive(Debug)]
24 pub struct RpcError {
25         /// The error code.
26         pub code: i64,
27         /// The error message.
28         pub message: String,
29 }
30
31 impl fmt::Display for RpcError {
32     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33         write!(f, "RPC error {}: {}", self.code, self.message)
34     }
35 }
36
37 impl Error for RpcError {}
38
39 /// A simple RPC client for calling methods using HTTP `POST`.
40 ///
41 /// Implements [`BlockSource`] and may return an `Err` containing [`RpcError`]. See
42 /// [`RpcClient::call_method`] for details.
43 pub struct RpcClient {
44         basic_auth: String,
45         endpoint: HttpEndpoint,
46         client: Mutex<Option<HttpClient>>,
47         id: AtomicUsize,
48 }
49
50 impl RpcClient {
51         /// Creates a new RPC client connected to the given endpoint with the provided credentials. The
52         /// credentials should be a base64 encoding of a user name and password joined by a colon, as is
53         /// required for HTTP basic access authentication.
54         pub fn new(credentials: &str, endpoint: HttpEndpoint) -> std::io::Result<Self> {
55                 Ok(Self {
56                         basic_auth: "Basic ".to_string() + credentials,
57                         endpoint,
58                         client: Mutex::new(None),
59                         id: AtomicUsize::new(0),
60                 })
61         }
62
63         /// Calls a method with the response encoded in JSON format and interpreted as type `T`.
64         ///
65         /// When an `Err` is returned, [`std::io::Error::into_inner`] may contain an [`RpcError`] if
66         /// [`std::io::Error::kind`] is [`std::io::ErrorKind::Other`].
67         pub async fn call_method<T>(&self, method: &str, params: &[serde_json::Value]) -> std::io::Result<T>
68         where JsonResponse: TryFrom<Vec<u8>, Error = std::io::Error> + TryInto<T, Error = std::io::Error> {
69                 let host = format!("{}:{}", self.endpoint.host(), self.endpoint.port());
70                 let uri = self.endpoint.path();
71                 let content = serde_json::json!({
72                         "method": method,
73                         "params": params,
74                         "id": &self.id.fetch_add(1, Ordering::AcqRel).to_string()
75                 });
76
77                 let mut client = if let Some(client) = self.client.lock().unwrap().take() { client }
78                         else { HttpClient::connect(&self.endpoint)? };
79                 let http_response = client.post::<JsonResponse>(&uri, &host, &self.basic_auth, content).await;
80                 *self.client.lock().unwrap() = Some(client);
81
82                 let mut response = match http_response {
83                         Ok(JsonResponse(response)) => response,
84                         Err(e) if e.kind() == std::io::ErrorKind::Other => {
85                                 match e.get_ref().unwrap().downcast_ref::<HttpError>() {
86                                         Some(http_error) => match JsonResponse::try_from(http_error.contents.clone()) {
87                                                 Ok(JsonResponse(response)) => response,
88                                                 Err(_) => Err(e)?,
89                                         },
90                                         None => Err(e)?,
91                                 }
92                         },
93                         Err(e) => Err(e)?,
94                 };
95
96                 if !response.is_object() {
97                         return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object"));
98                 }
99
100                 let error = &response["error"];
101                 if !error.is_null() {
102                         // TODO: Examine error code for a more precise std::io::ErrorKind.
103                         let rpc_error = RpcError { 
104                                 code: error["code"].as_i64().unwrap_or(-1), 
105                                 message: error["message"].as_str().unwrap_or("unknown error").to_string() 
106                         };
107                         return Err(std::io::Error::new(std::io::ErrorKind::Other, rpc_error));
108                 }
109
110                 let result = match response.get_mut("result") {
111                         Some(result) => result.take(),
112                         None =>
113                                 return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON result")),
114                 };
115
116                 JsonResponse(result).try_into()
117         }
118 }
119
120 impl BlockSource for RpcClient {
121         fn get_header<'a>(&'a self, header_hash: &'a BlockHash, _height: Option<u32>) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
122                 Box::pin(async move {
123                         let header_hash = serde_json::json!(header_hash.to_hex());
124                         Ok(self.call_method("getblockheader", &[header_hash]).await?)
125                 })
126         }
127
128         fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, BlockData> {
129                 Box::pin(async move {
130                         let header_hash = serde_json::json!(header_hash.to_hex());
131                         let verbosity = serde_json::json!(0);
132                         Ok(BlockData::FullBlock(self.call_method("getblock", &[header_hash, verbosity]).await?))
133                 })
134         }
135
136         fn get_best_block<'a>(&'a self) -> AsyncBlockSourceResult<'a, (BlockHash, Option<u32>)> {
137                 Box::pin(async move {
138                         Ok(self.call_method("getblockchaininfo", &[]).await?)
139                 })
140         }
141 }
142
143 impl UtxoSource for RpcClient {
144         fn get_block_hash_by_height<'a>(&'a self, block_height: u32) -> AsyncBlockSourceResult<'a, BlockHash> {
145                 Box::pin(async move {
146                         let height_param = serde_json::json!(block_height);
147                         Ok(self.call_method("getblockhash", &[height_param]).await?)
148                 })
149         }
150
151         fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool> {
152                 Box::pin(async move {
153                         let txid_param = serde_json::json!(outpoint.txid.to_hex());
154                         let vout_param = serde_json::json!(outpoint.vout);
155                         let include_mempool = serde_json::json!(false);
156                         let utxo_opt: serde_json::Value = self.call_method(
157                                 "gettxout", &[txid_param, vout_param, include_mempool]).await?;
158                         Ok(!utxo_opt.is_null())
159                 })
160         }
161 }
162
163 #[cfg(test)]
164 mod tests {
165         use super::*;
166         use crate::http::client_tests::{HttpServer, MessageBody};
167
168         use bitcoin::hashes::Hash;
169
170         /// Credentials encoded in base64.
171         const CREDENTIALS: &'static str = "dXNlcjpwYXNzd29yZA==";
172
173         /// Converts a JSON value into `u64`.
174         impl TryInto<u64> for JsonResponse {
175                 type Error = std::io::Error;
176
177                 fn try_into(self) -> std::io::Result<u64> {
178                         match self.0.as_u64() {
179                                 None => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "not a number")),
180                                 Some(n) => Ok(n),
181                         }
182                 }
183         }
184
185         #[tokio::test]
186         async fn call_method_returning_unknown_response() {
187                 let server = HttpServer::responding_with_not_found();
188                 let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
189
190                 match client.call_method::<u64>("getblockcount", &[]).await {
191                         Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::Other),
192                         Ok(_) => panic!("Expected error"),
193                 }
194         }
195
196         #[tokio::test]
197         async fn call_method_returning_malfomred_response() {
198                 let response = serde_json::json!("foo");
199                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
200                 let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
201
202                 match client.call_method::<u64>("getblockcount", &[]).await {
203                         Err(e) => {
204                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
205                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
206                         },
207                         Ok(_) => panic!("Expected error"),
208                 }
209         }
210
211         #[tokio::test]
212         async fn call_method_returning_error() {
213                 let response = serde_json::json!({
214                         "error": { "code": -8, "message": "invalid parameter" },
215                 });
216                 let server = HttpServer::responding_with_server_error(response);
217                 let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
218
219                 let invalid_block_hash = serde_json::json!("foo");
220                 match client.call_method::<u64>("getblock", &[invalid_block_hash]).await {
221                         Err(e) => {
222                                 assert_eq!(e.kind(), std::io::ErrorKind::Other);
223                                 let rpc_error: Box<RpcError> = e.into_inner().unwrap().downcast().unwrap();
224                                 assert_eq!(rpc_error.code, -8);
225                                 assert_eq!(rpc_error.message, "invalid parameter");
226                         },
227                         Ok(_) => panic!("Expected error"),
228                 }
229         }
230
231         #[tokio::test]
232         async fn call_method_returning_missing_result() {
233                 let response = serde_json::json!({  });
234                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
235                 let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
236
237                 match client.call_method::<u64>("getblockcount", &[]).await {
238                         Err(e) => {
239                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
240                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON result");
241                         },
242                         Ok(_) => panic!("Expected error"),
243                 }
244         }
245
246         #[tokio::test]
247         async fn call_method_returning_malformed_result() {
248                 let response = serde_json::json!({ "result": "foo" });
249                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
250                 let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
251
252                 match client.call_method::<u64>("getblockcount", &[]).await {
253                         Err(e) => {
254                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
255                                 assert_eq!(e.get_ref().unwrap().to_string(), "not a number");
256                         },
257                         Ok(_) => panic!("Expected error"),
258                 }
259         }
260
261         #[tokio::test]
262         async fn call_method_returning_valid_result() {
263                 let response = serde_json::json!({ "result": 654470 });
264                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
265                 let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
266
267                 match client.call_method::<u64>("getblockcount", &[]).await {
268                         Err(e) => panic!("Unexpected error: {:?}", e),
269                         Ok(count) => assert_eq!(count, 654470),
270                 }
271         }
272
273         #[tokio::test]
274         async fn fails_to_fetch_spent_utxo() {
275                 let response = serde_json::json!({ "result": null });
276                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
277                 let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
278                 let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0);
279                 let unspent_output = client.is_output_unspent(outpoint).await.unwrap();
280                 assert_eq!(unspent_output, false);
281         }
282
283         #[tokio::test]
284         async fn fetches_utxo() {
285                 let response = serde_json::json!({ "result": {"bestblock": 1, "confirmations": 42}});
286                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
287                 let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
288                 let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0);
289                 let unspent_output = client.is_output_unspent(outpoint).await.unwrap();
290                 assert_eq!(unspent_output, true);
291         }
292 }