Enforce no missing docs in all crates (+ add docs that were missing)
[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, 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 = self.client.post::<JsonResponse>(&uri, &host, &self.basic_auth, content)
51                         .await?.0;
52                 if !response.is_object() {
53                         return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object"));
54                 }
55
56                 let error = &response["error"];
57                 if !error.is_null() {
58                         // TODO: Examine error code for a more precise std::io::ErrorKind.
59                         let message = error["message"].as_str().unwrap_or("unknown error");
60                         return Err(std::io::Error::new(std::io::ErrorKind::Other, message));
61                 }
62
63                 let result = &mut response["result"];
64                 if result.is_null() {
65                         return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON result"));
66                 }
67
68                 JsonResponse(result.take()).try_into()
69         }
70 }
71
72 impl BlockSource for RpcClient {
73         fn get_header<'a>(&'a mut self, header_hash: &'a BlockHash, _height: Option<u32>) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
74                 Box::pin(async move {
75                         let header_hash = serde_json::json!(header_hash.to_hex());
76                         Ok(self.call_method("getblockheader", &[header_hash]).await?)
77                 })
78         }
79
80         fn get_block<'a>(&'a mut self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block> {
81                 Box::pin(async move {
82                         let header_hash = serde_json::json!(header_hash.to_hex());
83                         let verbosity = serde_json::json!(0);
84                         Ok(self.call_method("getblock", &[header_hash, verbosity]).await?)
85                 })
86         }
87
88         fn get_best_block<'a>(&'a mut self) -> AsyncBlockSourceResult<'a, (BlockHash, Option<u32>)> {
89                 Box::pin(async move {
90                         Ok(self.call_method("getblockchaininfo", &[]).await?)
91                 })
92         }
93 }
94
95 #[cfg(test)]
96 mod tests {
97         use super::*;
98         use crate::http::client_tests::{HttpServer, MessageBody};
99
100         /// Credentials encoded in base64.
101         const CREDENTIALS: &'static str = "dXNlcjpwYXNzd29yZA==";
102
103         /// Converts a JSON value into `u64`.
104         impl TryInto<u64> for JsonResponse {
105                 type Error = std::io::Error;
106
107                 fn try_into(self) -> std::io::Result<u64> {
108                         match self.0.as_u64() {
109                                 None => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "not a number")),
110                                 Some(n) => Ok(n),
111                         }
112                 }
113         }
114
115         #[tokio::test]
116         async fn call_method_returning_unknown_response() {
117                 let server = HttpServer::responding_with_not_found();
118                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
119
120                 match client.call_method::<u64>("getblockcount", &[]).await {
121                         Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound),
122                         Ok(_) => panic!("Expected error"),
123                 }
124         }
125
126         #[tokio::test]
127         async fn call_method_returning_malfomred_response() {
128                 let response = serde_json::json!("foo");
129                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
130                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
131
132                 match client.call_method::<u64>("getblockcount", &[]).await {
133                         Err(e) => {
134                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
135                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
136                         },
137                         Ok(_) => panic!("Expected error"),
138                 }
139         }
140
141         #[tokio::test]
142         async fn call_method_returning_error() {
143                 let response = serde_json::json!({
144                         "error": { "code": -8, "message": "invalid parameter" },
145                 });
146                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
147                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
148
149                 let invalid_block_hash = serde_json::json!("foo");
150                 match client.call_method::<u64>("getblock", &[invalid_block_hash]).await {
151                         Err(e) => {
152                                 assert_eq!(e.kind(), std::io::ErrorKind::Other);
153                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid parameter");
154                         },
155                         Ok(_) => panic!("Expected error"),
156                 }
157         }
158
159         #[tokio::test]
160         async fn call_method_returning_missing_result() {
161                 let response = serde_json::json!({ "result": null });
162                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
163                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
164
165                 match client.call_method::<u64>("getblockcount", &[]).await {
166                         Err(e) => {
167                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
168                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON result");
169                         },
170                         Ok(_) => panic!("Expected error"),
171                 }
172         }
173
174         #[tokio::test]
175         async fn call_method_returning_malformed_result() {
176                 let response = serde_json::json!({ "result": "foo" });
177                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
178                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
179
180                 match client.call_method::<u64>("getblockcount", &[]).await {
181                         Err(e) => {
182                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
183                                 assert_eq!(e.get_ref().unwrap().to_string(), "not a number");
184                         },
185                         Ok(_) => panic!("Expected error"),
186                 }
187         }
188
189         #[tokio::test]
190         async fn call_method_returning_valid_result() {
191                 let response = serde_json::json!({ "result": 654470 });
192                 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
193                 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
194
195                 match client.call_method::<u64>("getblockcount", &[]).await {
196                         Err(e) => panic!("Unexpected error: {:?}", e),
197                         Ok(count) => assert_eq!(count, 654470),
198                 }
199         }
200 }