1 //! Simple RPC client implementation which implements [`BlockSource`] against a Bitcoin Core RPC
4 use crate::{BlockHeaderData, BlockSource, AsyncBlockSourceResult};
5 use crate::http::{HttpClient, HttpEndpoint, JsonResponse};
7 use bitcoin::blockdata::block::Block;
8 use bitcoin::hash_types::BlockHash;
9 use bitcoin::hashes::hex::ToHex;
13 use std::convert::TryFrom;
14 use std::convert::TryInto;
15 use std::sync::atomic::{AtomicUsize, Ordering};
17 /// A simple RPC client for calling methods using HTTP `POST`.
18 pub struct RpcClient {
20 endpoint: HttpEndpoint,
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)?;
32 basic_auth: "Basic ".to_string() + credentials,
35 id: AtomicUsize::new(0),
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!({
47 "id": &self.id.fetch_add(1, Ordering::AcqRel).to_string()
50 let mut response = self.client.post::<JsonResponse>(&uri, &host, &self.basic_auth, content)
52 if !response.is_object() {
53 return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object"));
56 let error = &response["error"];
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));
63 let result = &mut response["result"];
65 return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON result"));
68 JsonResponse(result.take()).try_into()
72 impl BlockSource for RpcClient {
73 fn get_header<'a>(&'a mut self, header_hash: &'a BlockHash, _height: Option<u32>) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
75 let header_hash = serde_json::json!(header_hash.to_hex());
76 Ok(self.call_method("getblockheader", &[header_hash]).await?)
80 fn get_block<'a>(&'a mut self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block> {
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?)
88 fn get_best_block<'a>(&'a mut self) -> AsyncBlockSourceResult<'a, (BlockHash, Option<u32>)> {
90 Ok(self.call_method("getblockchaininfo", &[]).await?)
98 use crate::http::client_tests::{HttpServer, MessageBody};
100 /// Credentials encoded in base64.
101 const CREDENTIALS: &'static str = "dXNlcjpwYXNzd29yZA==";
103 /// Converts a JSON value into `u64`.
104 impl TryInto<u64> for JsonResponse {
105 type Error = std::io::Error;
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")),
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();
120 match client.call_method::<u64>("getblockcount", &[]).await {
121 Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound),
122 Ok(_) => panic!("Expected error"),
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();
132 match client.call_method::<u64>("getblockcount", &[]).await {
134 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
135 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
137 Ok(_) => panic!("Expected error"),
142 async fn call_method_returning_error() {
143 let response = serde_json::json!({
144 "error": { "code": -8, "message": "invalid parameter" },
146 let server = HttpServer::responding_with_ok(MessageBody::Content(response));
147 let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
149 let invalid_block_hash = serde_json::json!("foo");
150 match client.call_method::<u64>("getblock", &[invalid_block_hash]).await {
152 assert_eq!(e.kind(), std::io::ErrorKind::Other);
153 assert_eq!(e.get_ref().unwrap().to_string(), "invalid parameter");
155 Ok(_) => panic!("Expected error"),
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();
165 match client.call_method::<u64>("getblockcount", &[]).await {
167 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
168 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON result");
170 Ok(_) => panic!("Expected error"),
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();
180 match client.call_method::<u64>("getblockcount", &[]).await {
182 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
183 assert_eq!(e.get_ref().unwrap().to_string(), "not a number");
185 Ok(_) => panic!("Expected error"),
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();
195 match client.call_method::<u64>("getblockcount", &[]).await {
196 Err(e) => panic!("Unexpected error: {:?}", e),
197 Ok(count) => assert_eq!(count, 654470),