X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-block-sync%2Fsrc%2Fconvert.rs;h=ed811d2cc0c3f629e16a450937c58d8287f35689;hb=10480f009e6e57cd409916ea08ac679b0953e4d5;hp=d6294e1d2a79518c05a674cae5ea86d41fc1faf7;hpb=a5ecb851716bc0819586894232653ec4b5e2c67f;p=rust-lightning diff --git a/lightning-block-sync/src/convert.rs b/lightning-block-sync/src/convert.rs index d6294e1d..ed811d2c 100644 --- a/lightning-block-sync/src/convert.rs +++ b/lightning-block-sync/src/convert.rs @@ -1,8 +1,8 @@ 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; @@ -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 for JsonResponse { + type Error = std::io::Error; + fn try_into(self) -> Result { Ok(self.0) } +} + /// Conversion from `std::io::Error` into `BlockSourceError`. impl From for BlockSourceError { fn from(e: std::io::Error) -> BlockSourceError { @@ -38,6 +44,17 @@ impl TryInto for BinaryResponse { } } +/// Parses binary data as a block hash. +impl TryInto for BinaryResponse { + type Error = std::io::Error; + + fn try_into(self) -> std::io::Result { + 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 for JsonResponse { @@ -71,17 +88,21 @@ impl TryFrom for BlockHeaderData { } } Ok(BlockHeaderData { - header: BlockHeader { - version: get_field!("version", as_i64).try_into().map_err(|_| ())?, + 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_hex(hash_str.as_str().ok_or(())?).map_err(|_| ())? + BlockHash::from_str(hash_str.as_str().ok_or(())?).map_err(|_| ())? } else { BlockHash::all_zeros() }, - merkle_root: TxMerkleNode::from_hex(get_field!("merkleroot", as_str)).map_err(|_| ())?, + merkle_root: TxMerkleNode::from_str(get_field!("merkleroot", as_str)).map_err(|_| ())?, time: get_field!("time", as_u64).try_into().map_err(|_| ())?, - bits: u32::from_be_bytes(<[u8; 4]>::from_hex(get_field!("bits", as_str)).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(get_field!("chainwork", as_str)).map_err(|_| ())?, + chainwork: hex_to_work(get_field!("chainwork", as_str)).map_err(|_| ())?, height: get_field!("height", as_u64).try_into().map_err(|_| ())?, }) } @@ -115,7 +136,7 @@ impl TryInto<(BlockHash, Option)> 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, }, @@ -141,25 +162,8 @@ impl TryInto<(BlockHash, Option)> for JsonResponse { impl TryInto for JsonResponse { type Error = std::io::Error; fn try_into(self) -> std::io::Result { - match self.0.as_str() { - None => Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "expected JSON string", - )), - Some(hex_data) => match Vec::::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() )) } } @@ -226,13 +230,55 @@ impl TryInto for JsonResponse { } } +impl TryInto for JsonResponse { + type Error = std::io::Error; + + fn try_into(self) -> std::io::Result { + 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 for JsonResponse { + type Error = std::io::Error; + + fn try_into(self) -> std::io::Result { + 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::hashes::hex::ToHex; use bitcoin::network::constants::Network; + use hex::DisplayHex; use serde_json::value::Number; use serde_json::Value; @@ -241,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(), }) } } @@ -337,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 }; @@ -484,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)>::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), @@ -499,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)>::try_into(response) { @@ -515,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)>::try_into(response) { @@ -531,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)>::try_into(response) { @@ -561,7 +607,7 @@ pub(crate) mod tests { match TryInto::::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"), } @@ -573,7 +619,7 @@ pub(crate) mod tests { match TryInto::::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"), } @@ -589,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 = JsonResponse(rpc_response.get_mut("result").unwrap().take()) + .try_into(); + assert_eq!( + r.unwrap().to_string(), + "7934f775149929a8b742487129a7c3a535dfb612f0b726cc67bc10bc2628f906" + ); + } + // TryInto 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)