Disable fast-fail to let CI actually run even though beta is broken
[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::{BlockHeaderData, BlockSource, AsyncBlockSourceResult};
5 use crate::http::{HttpClient, HttpEndpoint, HttpError, JsonResponse};
6
7 use bitcoin::blockdata::block::Block;
8 use bitcoin::hash_types::BlockHash;
9 use bitcoin::hashes::hex::ToHex;
10
11 use serde_json;
12
13 use std::convert::TryFrom;
14 use std::convert::TryInto;
15 use std::sync::atomic::{AtomicUsize, Ordering};
16
17 /// A simple RPC client for calling methods using HTTP `POST`.
18 pub struct RpcClient {
19         basic_auth: String,
20         endpoint: HttpEndpoint,
21         client: HttpClient,
22         id: AtomicUsize,
23 }
24
25 impl RpcClient {
26         /// Creates a new RPC client connected to the given endpoint with the provided credentials. The
27         /// credentials should be a base64 encoding of a user name and password joined by a colon, as is
28         /// required for HTTP basic access authentication.
29         pub fn new(credentials: &str, endpoint: HttpEndpoint) -> std::io::Result<Self> {
30                 let client = HttpClient::connect(&endpoint)?;
31                 Ok(Self {
32                         basic_auth: "Basic ".to_string() + credentials,
33                         endpoint,
34                         client,
35                         id: AtomicUsize::new(0),
36                 })
37         }
38
39         /// Calls a method with the response encoded in JSON format and interpreted as type `T`.
40         pub async fn call_method<T>(&mut self, method: &str, params: &[serde_json::Value]) -> std::io::Result<T>
41         where JsonResponse: TryFrom<Vec<u8>, Error = std::io::Error> + TryInto<T, Error = std::io::Error> {
42                 let host = format!("{}:{}", self.endpoint.host(), self.endpoint.port());
43                 let uri = self.endpoint.path();
44                 let content = serde_json::json!({
45                         "method": method,
46                         "params": params,
47                         "id": &self.id.fetch_add(1, Ordering::AcqRel).to_string()
48                 });
49
50                 let mut response = match self.client.post::<JsonResponse>(&uri, &host, &self.basic_auth, content).await {
51                         Ok(JsonResponse(response)) => response,
52                         Err(e) if e.kind() == std::io::ErrorKind::Other => {
53                                 match e.get_ref().unwrap().downcast_ref::<HttpError>() {
54                                         Some(http_error) => match JsonResponse::try_from(http_error.contents.clone()) {
55                                                 Ok(JsonResponse(response)) => response,
56                                                 Err(_) => Err(e)?,
57                                         },
58                                         None => Err(e)?,
59                                 }
60                         },
61                         Err(e) => Err(e)?,
62                 };
63
64                 if !response.is_object() {
65                         return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object"));
66                 }
67
68                 let error = &response["error"];
69                 if !error.is_null() {
70                         // TODO: Examine error code for a more precise std::io::ErrorKind.
71                         let message = error["message"].as_str().unwrap_or("unknown error");
72                         return Err(std::io::Error::new(std::io::ErrorKind::Other, message));
73                 }
74
75                 let result = &mut response["result"];
76                 if result.is_null() {
77                         return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON result"));
78                 }
79
80                 JsonResponse(result.take()).try_into()
81         }
82 }
83
84 impl BlockSource for RpcClient {
85         fn get_header<'a>(&'a mut self, header_hash: &'a BlockHash, _height: Option<u32>) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
86                 Box::pin(async move {
87                         let header_hash = serde_json::json!(header_hash.to_hex());
88                         Ok(self.call_method("getblockheader", &[header_hash]).await?)
89                 })
90         }
91
92         fn get_block<'a>(&'a mut self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block> {
93                 Box::pin(async move {
94                         let header_hash = serde_json::json!(header_hash.to_hex());
95                         let verbosity = serde_json::json!(0);
96                         Ok(self.call_method("getblock", &[header_hash, verbosity]).await?)
97                 })
98         }
99
100         fn get_best_block<'a>(&'a mut self) -> AsyncBlockSourceResult<'a, (BlockHash, Option<u32>)> {
101                 Box::pin(async move {
102                         Ok(self.call_method("getblockchaininfo", &[]).await?)
103                 })
104         }
105 }
106
107 #[cfg(test)]
108 mod tests {
109         use super::*;
110         use crate::http::client_tests::{HttpServer, MessageBody};
111
112         /// Credentials encoded in base64.
113         const CREDENTIALS: &'static str = "dXNlcjpwYXNzd29yZA==";
114
115         /// Converts a JSON value into `u64`.
116         impl TryInto<u64> for JsonResponse {
117                 type Error = std::io::Error;
118
119                 fn try_into(self) -> std::io::Result<u64> {
120                         match self.0.as_u64() {
121                                 None => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "not a number")),
122                                 Some(n) => Ok(n),
123                         }
124                 }
125         }
126
127         #[tokio::test]
128         async fn call_method_returning_unknown_response() {
129                 let server = HttpServer::responding_with_not_found();
130                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
131
132                 match client.call_method::<u64>("getblockcount", &[]).await {
133                         Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::Other),
134                         Ok(_) => panic!("Expected error"),
135                 }
136         }
137
138         #[tokio::test]
139         async fn call_method_returning_malfomred_response() {
140                 let response = serde_json::json!("foo");
141                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
142                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
143
144                 match client.call_method::<u64>("getblockcount", &[]).await {
145                         Err(e) => {
146                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
147                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
148                         },
149                         Ok(_) => panic!("Expected error"),
150                 }
151         }
152
153         #[tokio::test]
154         async fn call_method_returning_error() {
155                 let response = serde_json::json!({
156                         "error": { "code": -8, "message": "invalid parameter" },
157                 });
158                 let server = HttpServer::responding_with_server_error(response);
159                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
160
161                 let invalid_block_hash = serde_json::json!("foo");
162                 match client.call_method::<u64>("getblock", &[invalid_block_hash]).await {
163                         Err(e) => {
164                                 assert_eq!(e.kind(), std::io::ErrorKind::Other);
165                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid parameter");
166                         },
167                         Ok(_) => panic!("Expected error"),
168                 }
169         }
170
171         #[tokio::test]
172         async fn call_method_returning_missing_result() {
173                 let response = serde_json::json!({ "result": null });
174                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
175                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
176
177                 match client.call_method::<u64>("getblockcount", &[]).await {
178                         Err(e) => {
179                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
180                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON result");
181                         },
182                         Ok(_) => panic!("Expected error"),
183                 }
184         }
185
186         #[tokio::test]
187         async fn call_method_returning_malformed_result() {
188                 let response = serde_json::json!({ "result": "foo" });
189                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
190                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
191
192                 match client.call_method::<u64>("getblockcount", &[]).await {
193                         Err(e) => {
194                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
195                                 assert_eq!(e.get_ref().unwrap().to_string(), "not a number");
196                         },
197                         Ok(_) => panic!("Expected error"),
198                 }
199         }
200
201         #[tokio::test]
202         async fn call_method_returning_valid_result() {
203                 let response = serde_json::json!({ "result": 654470 });
204                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
205                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
206
207                 match client.call_method::<u64>("getblockcount", &[]).await {
208                         Err(e) => panic!("Unexpected error: {:?}", e),
209                         Ok(count) => assert_eq!(count, 654470),
210                 }
211         }
212 }