Check if msg.script.is_witness_program() before checking version
[rust-lightning] / lightning-block-sync / src / convert.rs
index 358076f4c25a883f5e571b421f9d3ff1ebe20f27..ed811d2cc0c3f629e16a450937c58d8287f35689 100644 (file)
@@ -1,20 +1,25 @@
 use crate::http::{BinaryResponse, JsonResponse};
-use crate::utils::hex_to_uint256;
+use crate::utils::hex_to_work;
 use crate::{BlockHeaderData, BlockSourceError};
 
-use bitcoin::blockdata::block::{Block, BlockHeader};
+use bitcoin::blockdata::block::{Block, Header};
 use bitcoin::consensus::encode;
 use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid};
-use bitcoin::hashes::hex::{FromHex, ToHex};
+use bitcoin::hashes::hex::FromHex;
 use bitcoin::Transaction;
 
-use serde::Deserialize;
-
 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 {
@@ -39,13 +44,24 @@ 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 {
        type Error = std::io::Error;
 
        fn try_into(self) -> std::io::Result<BlockHeaderData> {
-               let mut header = match self.0 {
+               let header = match self.0 {
                        serde_json::Value::Array(mut array) if !array.is_empty() => array.drain(..).next().unwrap(),
                        serde_json::Value::Object(_) => self.0,
                        _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unexpected JSON type")),
@@ -56,51 +72,38 @@ impl TryInto<BlockHeaderData> for JsonResponse {
                }
 
                // Add an empty previousblockhash for the genesis block.
-               if let None = header.get("previousblockhash") {
-                       let hash: BlockHash = Default::default();
-                       header.as_object_mut().unwrap().insert("previousblockhash".to_string(), serde_json::json!(hash.to_hex()));
-               }
-
-               match serde_json::from_value::<GetHeaderResponse>(header) {
-                       Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid header response")),
-                       Ok(response) => match response.try_into() {
-                               Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid header data")),
-                               Ok(header) => Ok(header),
-                       },
+               match header.try_into() {
+                       Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid header data")),
+                       Ok(header) => Ok(header),
                }
        }
 }
 
-/// Response data from `getblockheader` RPC and `headers` REST requests.
-#[derive(Deserialize)]
-struct GetHeaderResponse {
-       pub version: i32,
-       pub merkleroot: String,
-       pub time: u32,
-       pub nonce: u32,
-       pub bits: String,
-       pub previousblockhash: String,
-
-       pub chainwork: String,
-       pub height: u32,
-}
+impl TryFrom<serde_json::Value> for BlockHeaderData {
+       type Error = ();
 
-/// Converts from `GetHeaderResponse` to `BlockHeaderData`.
-impl TryFrom<GetHeaderResponse> for BlockHeaderData {
-       type Error = bitcoin::hashes::hex::Error;
+       fn try_from(response: serde_json::Value) -> Result<Self, ()> {
+               macro_rules! get_field { ($name: expr, $ty_access: tt) => {
+                       response.get($name).ok_or(())?.$ty_access().ok_or(())?
+               } }
 
-       fn try_from(response: GetHeaderResponse) -> Result<Self, bitcoin::hashes::hex::Error> {
                Ok(BlockHeaderData {
-                       header: BlockHeader {
-                               version: response.version,
-                               prev_blockhash: BlockHash::from_hex(&response.previousblockhash)?,
-                               merkle_root: TxMerkleNode::from_hex(&response.merkleroot)?,
-                               time: response.time,
-                               bits: u32::from_be_bytes(<[u8; 4]>::from_hex(&response.bits)?),
-                               nonce: response.nonce,
+                       header: Header {
+                               version: bitcoin::blockdata::block::Version::from_consensus(
+                                       get_field!("version", as_i64).try_into().map_err(|_| ())?
+                               ),
+                               prev_blockhash: if let Some(hash_str) = response.get("previousblockhash") {
+                                               BlockHash::from_str(hash_str.as_str().ok_or(())?).map_err(|_| ())?
+                                       } else { BlockHash::all_zeros() },
+                               merkle_root: TxMerkleNode::from_str(get_field!("merkleroot", as_str)).map_err(|_| ())?,
+                               time: get_field!("time", as_u64).try_into().map_err(|_| ())?,
+                               bits: bitcoin::CompactTarget::from_consensus(
+                                       u32::from_be_bytes(<[u8; 4]>::from_hex(get_field!("bits", as_str)).map_err(|_| ())?)
+                               ),
+                               nonce: get_field!("nonce", as_u64).try_into().map_err(|_| ())?,
                        },
-                       chainwork: hex_to_uint256(&response.chainwork)?,
-                       height: response.height,
+                       chainwork: hex_to_work(get_field!("chainwork", as_str)).map_err(|_| ())?,
+                       height: get_field!("height", as_u64).try_into().map_err(|_| ())?,
                })
        }
 }
@@ -133,7 +136,7 @@ impl TryInto<(BlockHash, Option<u32>)> for JsonResponse {
                }
 
                let hash = match &self.0["bestblockhash"] {
-                       serde_json::Value::String(hex_data) => match BlockHash::from_hex(&hex_data) {
+                       serde_json::Value::String(hex_data) => match BlockHash::from_str(&hex_data) {
                                Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")),
                                Ok(block_hash) => block_hash,
                        },
@@ -159,30 +162,13 @@ impl TryInto<(BlockHash, Option<u32>)> for JsonResponse {
 impl TryInto<Txid> for JsonResponse {
        type Error = std::io::Error;
        fn try_into(self) -> std::io::Result<Txid> {
-               match self.0.as_str() {
-                       None => Err(std::io::Error::new(
-                               std::io::ErrorKind::InvalidData,
-                               "expected JSON string",
-                       )),
-                       Some(hex_data) => match Vec::<u8>::from_hex(hex_data) {
-                               Err(_) => Err(std::io::Error::new(
-                                       std::io::ErrorKind::InvalidData,
-                                       "invalid hex data",
-                               )),
-                               Ok(txid_data) => match encode::deserialize(&txid_data) {
-                                       Err(_) => Err(std::io::Error::new(
-                                               std::io::ErrorKind::InvalidData,
-                                               "invalid txid",
-                                       )),
-                                       Ok(txid) => Ok(txid),
-                               },
-                       },
-               }
+               let hex_data = self.0.as_str().ok_or(Self::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string" ))?;
+               Txid::from_str(hex_data).map_err(|err|Self::Error::new(std::io::ErrorKind::InvalidData, err.to_string() ))
        }
 }
 
 /// Converts a JSON value into a transaction. WATCH OUT! this cannot be used for zero-input transactions
-/// (e.g. createrawtransaction). See https://github.com/rust-bitcoin/rust-bitcoincore-rpc/issues/197
+/// (e.g. createrawtransaction). See <https://github.com/rust-bitcoin/rust-bitcoincore-rpc/issues/197>
 impl TryInto<Transaction> for JsonResponse {
        type Error = std::io::Error;
        fn try_into(self) -> std::io::Result<Transaction> {
@@ -244,12 +230,55 @@ 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.
+#[cfg(feature = "rest-client")]
+pub(crate) struct GetUtxosResponse {
+       pub(crate) hit_bitmap_nonempty: bool
+}
+
+#[cfg(feature = "rest-client")]
+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::*;
        use bitcoin::blockdata::constants::genesis_block;
        use bitcoin::hashes::Hash;
        use bitcoin::network::constants::Network;
+       use hex::DisplayHex;
        use serde_json::value::Number;
        use serde_json::Value;
 
@@ -258,14 +287,14 @@ pub(crate) mod tests {
                fn from(data: BlockHeaderData) -> Self {
                        let BlockHeaderData { chainwork, height, header } = data;
                        serde_json::json!({
-                               "chainwork": chainwork.to_string()["0x".len()..],
+                               "chainwork": chainwork.to_be_bytes().as_hex().to_string(),
                                "height": height,
-                               "version": header.version,
-                               "merkleroot": header.merkle_root.to_hex(),
+                               "version": header.version.to_consensus(),
+                               "merkleroot": header.merkle_root.to_string(),
                                "time": header.time,
                                "nonce": header.nonce,
-                               "bits": header.bits.to_hex(),
-                               "previousblockhash": header.prev_blockhash.to_hex(),
+                               "bits": header.bits.to_consensus().to_be_bytes().as_hex().to_string(),
+                               "previousblockhash": header.prev_blockhash.to_string(),
                        })
                }
        }
@@ -307,7 +336,7 @@ pub(crate) mod tests {
                match TryInto::<BlockHeaderData>::try_into(response) {
                        Err(e) => {
                                assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
-                               assert_eq!(e.get_ref().unwrap().to_string(), "invalid header response");
+                               assert_eq!(e.get_ref().unwrap().to_string(), "invalid header data");
                        },
                        Ok(_) => panic!("Expected error"),
                }
@@ -354,7 +383,7 @@ pub(crate) mod tests {
        #[test]
        fn into_block_header_from_json_response_with_valid_header_array() {
                let genesis_block = genesis_block(Network::Bitcoin);
-               let best_block_header = BlockHeader {
+               let best_block_header = Header {
                        prev_blockhash: genesis_block.block_hash(),
                        ..genesis_block.header
                };
@@ -501,7 +530,7 @@ pub(crate) mod tests {
        fn into_block_hash_from_json_response_without_height() {
                let block = genesis_block(Network::Bitcoin);
                let response = JsonResponse(serde_json::json!({
-                       "bestblockhash": block.block_hash().to_hex(),
+                       "bestblockhash": block.block_hash().to_string(),
                }));
                match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
                        Err(e) => panic!("Unexpected error: {:?}", e),
@@ -516,7 +545,7 @@ pub(crate) mod tests {
        fn into_block_hash_from_json_response_with_unexpected_blocks_type() {
                let block = genesis_block(Network::Bitcoin);
                let response = JsonResponse(serde_json::json!({
-                       "bestblockhash": block.block_hash().to_hex(),
+                       "bestblockhash": block.block_hash().to_string(),
                        "blocks": "foo",
                }));
                match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
@@ -532,7 +561,7 @@ pub(crate) mod tests {
        fn into_block_hash_from_json_response_with_invalid_height() {
                let block = genesis_block(Network::Bitcoin);
                let response = JsonResponse(serde_json::json!({
-                       "bestblockhash": block.block_hash().to_hex(),
+                       "bestblockhash": block.block_hash().to_string(),
                        "blocks": std::u64::MAX,
                }));
                match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
@@ -548,7 +577,7 @@ pub(crate) mod tests {
        fn into_block_hash_from_json_response_with_height() {
                let block = genesis_block(Network::Bitcoin);
                let response = JsonResponse(serde_json::json!({
-                       "bestblockhash": block.block_hash().to_hex(),
+                       "bestblockhash": block.block_hash().to_string(),
                        "blocks": 1,
                }));
                match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
@@ -578,7 +607,7 @@ pub(crate) mod tests {
                match TryInto::<Txid>::try_into(response) {
                        Err(e) => {
                                assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
-                               assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
+                               assert_eq!(e.get_ref().unwrap().to_string(), "bad hex string length 6 (expected 64)");
                        }
                        Ok(_) => panic!("Expected error"),
                }
@@ -590,7 +619,7 @@ pub(crate) mod tests {
                match TryInto::<Txid>::try_into(response) {
                        Err(e) => {
                                assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
-                               assert_eq!(e.get_ref().unwrap().to_string(), "invalid txid");
+                               assert_eq!(e.get_ref().unwrap().to_string(), "bad hex string length 4 (expected 64)");
                        }
                        Ok(_) => panic!("Expected error"),
                }
@@ -606,6 +635,20 @@ pub(crate) mod tests {
                }
        }
 
+       #[test]
+       fn into_txid_from_bitcoind_rpc_json_response() {
+               let mut rpc_response = serde_json::json!(
+            {"error": "", "id": "770", "result": "7934f775149929a8b742487129a7c3a535dfb612f0b726cc67bc10bc2628f906"}
+
+        );
+        let r: std::io::Result<Txid> = JsonResponse(rpc_response.get_mut("result").unwrap().take())
+            .try_into();
+        assert_eq!(
+            r.unwrap().to_string(),
+            "7934f775149929a8b742487129a7c3a535dfb612f0b726cc67bc10bc2628f906"
+        );
+       }
+
        // TryInto<Transaction> can be used in two ways, first with plain hex response where data is
        // the hex encoded transaction (e.g. as a result of getrawtransaction) or as a JSON object
        // where the hex encoded transaction can be found in the hex field of the object (if present)