Implement the `UtxoSource` interface for REST/RPC clients
[rust-lightning] / lightning-block-sync / src / convert.rs
index d6294e1d2a79518c05a674cae5ea86d41fc1faf7..bf9e9577619a3fd0a1a7fecc3d79cd070bd706e1 100644 (file)
@@ -13,8 +13,14 @@ use serde_json;
 use std::convert::From;
 use std::convert::TryFrom;
 use std::convert::TryInto;
+use std::str::FromStr;
 use bitcoin::hashes::Hash;
 
+impl TryInto<serde_json::Value> for JsonResponse {
+       type Error = std::io::Error;
+       fn try_into(self) -> Result<serde_json::Value, std::io::Error> { Ok(self.0) }
+}
+
 /// Conversion from `std::io::Error` into `BlockSourceError`.
 impl From<std::io::Error> for BlockSourceError {
        fn from(e: std::io::Error) -> BlockSourceError {
@@ -38,6 +44,17 @@ impl TryInto<Block> for BinaryResponse {
        }
 }
 
+/// Parses binary data as a block hash.
+impl TryInto<BlockHash> for BinaryResponse {
+       type Error = std::io::Error;
+
+       fn try_into(self) -> std::io::Result<BlockHash> {
+               BlockHash::from_slice(&self.0).map_err(|_|
+                       std::io::Error::new(std::io::ErrorKind::InvalidData, "bad block hash length")
+               )
+       }
+}
+
 /// Converts a JSON value into block header data. The JSON value may be an object representing a
 /// block header or an array of such objects. In the latter case, the first object is converted.
 impl TryInto<BlockHeaderData> for JsonResponse {
@@ -226,6 +243,46 @@ impl TryInto<Transaction> for JsonResponse {
        }
 }
 
+impl TryInto<BlockHash> for JsonResponse {
+       type Error = std::io::Error;
+
+       fn try_into(self) -> std::io::Result<BlockHash> {
+               match self.0.as_str() {
+                       None => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string")),
+                       Some(hex_data) if hex_data.len() != 64 =>
+                               Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hash length")),
+                       Some(hex_data) => BlockHash::from_str(hex_data)
+                               .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")),
+               }
+       }
+}
+
+/// The REST `getutxos` endpoint retuns a whole pile of data we don't care about and one bit we do
+/// - whether the `hit bitmap` field had any entries. Thus we condense the result down into only
+/// that.
+pub(crate) struct GetUtxosResponse {
+       pub(crate) hit_bitmap_nonempty: bool
+}
+
+impl TryInto<GetUtxosResponse> for JsonResponse {
+       type Error = std::io::Error;
+
+       fn try_into(self) -> std::io::Result<GetUtxosResponse> {
+               let bitmap_str =
+                       self.0.as_object().ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected an object"))?
+                       .get("bitmap").ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "missing bitmap field"))?
+                       .as_str().ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "bitmap should be an str"))?;
+                       let mut hit_bitmap_nonempty = false;
+                       for c in bitmap_str.chars() {
+                               if c < '0' || c > '9' {
+                                       return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid byte"));
+                               }
+                               if c > '0' { hit_bitmap_nonempty = true; }
+                       }
+                       Ok(GetUtxosResponse { hit_bitmap_nonempty })
+       }
+}
+
 #[cfg(test)]
 pub(crate) mod tests {
        use super::*;