Parse RPC errors as JSON content
[rust-lightning] / lightning-block-sync / src / rpc.rs
index 60ea1a360c06f9038d05ed64712a4816fdfbde3f..88199688aefd1a48fd128aac5c45f319f947c90e 100644 (file)
@@ -1,4 +1,12 @@
-use crate::http::{HttpClient, HttpEndpoint, JsonResponse};
+//! Simple RPC client implementation which implements [`BlockSource`] against a Bitcoin Core RPC
+//! endpoint.
+
+use crate::{BlockHeaderData, BlockSource, AsyncBlockSourceResult};
+use crate::http::{HttpClient, HttpEndpoint, HttpError, JsonResponse};
+
+use bitcoin::blockdata::block::Block;
+use bitcoin::hash_types::BlockHash;
+use bitcoin::hashes::hex::ToHex;
 
 use serde_json;
 
@@ -29,7 +37,7 @@ impl RpcClient {
        }
 
        /// Calls a method with the response encoded in JSON format and interpreted as type `T`.
-       async fn call_method<T>(&mut self, method: &str, params: &[serde_json::Value]) -> std::io::Result<T>
+       pub async fn call_method<T>(&mut self, method: &str, params: &[serde_json::Value]) -> std::io::Result<T>
        where JsonResponse: TryFrom<Vec<u8>, Error = std::io::Error> + TryInto<T, Error = std::io::Error> {
                let host = format!("{}:{}", self.endpoint.host(), self.endpoint.port());
                let uri = self.endpoint.path();
@@ -39,8 +47,20 @@ impl RpcClient {
                        "id": &self.id.fetch_add(1, Ordering::AcqRel).to_string()
                });
 
-               let mut response = self.client.post::<JsonResponse>(&uri, &host, &self.basic_auth, content)
-                       .await?.0;
+               let mut response = match self.client.post::<JsonResponse>(&uri, &host, &self.basic_auth, content).await {
+                       Ok(JsonResponse(response)) => response,
+                       Err(e) if e.kind() == std::io::ErrorKind::Other => {
+                               match e.get_ref().unwrap().downcast_ref::<HttpError>() {
+                                       Some(http_error) => match JsonResponse::try_from(http_error.contents.clone()) {
+                                               Ok(JsonResponse(response)) => response,
+                                               Err(_) => Err(e)?,
+                                       },
+                                       None => Err(e)?,
+                               }
+                       },
+                       Err(e) => Err(e)?,
+               };
+
                if !response.is_object() {
                        return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object"));
                }
@@ -61,6 +81,29 @@ impl RpcClient {
        }
 }
 
+impl BlockSource for RpcClient {
+       fn get_header<'a>(&'a mut self, header_hash: &'a BlockHash, _height: Option<u32>) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
+               Box::pin(async move {
+                       let header_hash = serde_json::json!(header_hash.to_hex());
+                       Ok(self.call_method("getblockheader", &[header_hash]).await?)
+               })
+       }
+
+       fn get_block<'a>(&'a mut self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block> {
+               Box::pin(async move {
+                       let header_hash = serde_json::json!(header_hash.to_hex());
+                       let verbosity = serde_json::json!(0);
+                       Ok(self.call_method("getblock", &[header_hash, verbosity]).await?)
+               })
+       }
+
+       fn get_best_block<'a>(&'a mut self) -> AsyncBlockSourceResult<'a, (BlockHash, Option<u32>)> {
+               Box::pin(async move {
+                       Ok(self.call_method("getblockchaininfo", &[]).await?)
+               })
+       }
+}
+
 #[cfg(test)]
 mod tests {
        use super::*;
@@ -87,7 +130,7 @@ mod tests {
                let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
 
                match client.call_method::<u64>("getblockcount", &[]).await {
-                       Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound),
+                       Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::Other),
                        Ok(_) => panic!("Expected error"),
                }
        }
@@ -112,7 +155,7 @@ mod tests {
                let response = serde_json::json!({
                        "error": { "code": -8, "message": "invalid parameter" },
                });
-               let server = HttpServer::responding_with_ok(MessageBody::Content(response));
+               let server = HttpServer::responding_with_server_error(response);
                let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
 
                let invalid_block_hash = serde_json::json!("foo");