use crate::http::{BinaryResponse, JsonResponse}; use crate::utils::hex_to_uint256; use crate::{BlockHeaderData, BlockSourceError}; use bitcoin::blockdata::block::{Block, BlockHeader}; use bitcoin::consensus::encode; use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid}; use bitcoin::hashes::hex::FromHex; use bitcoin::Transaction; use serde_json; use std::convert::From; use std::convert::TryFrom; use std::convert::TryInto; use bitcoin::hashes::Hash; /// Conversion from `std::io::Error` into `BlockSourceError`. impl From for BlockSourceError { fn from(e: std::io::Error) -> BlockSourceError { match e.kind() { std::io::ErrorKind::InvalidData => BlockSourceError::persistent(e), std::io::ErrorKind::InvalidInput => BlockSourceError::persistent(e), _ => BlockSourceError::transient(e), } } } /// Parses binary data as a block. impl TryInto for BinaryResponse { type Error = std::io::Error; fn try_into(self) -> std::io::Result { match encode::deserialize(&self.0) { Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid block data")), Ok(block) => Ok(block), } } } /// 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 { type Error = std::io::Error; fn try_into(self) -> std::io::Result { 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")), }; if !header.is_object() { return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object")); } // Add an empty previousblockhash for the genesis block. match header.try_into() { Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid header data")), Ok(header) => Ok(header), } } } impl TryFrom for BlockHeaderData { type Error = (); fn try_from(response: serde_json::Value) -> Result { macro_rules! get_field { ($name: expr, $ty_access: tt) => { response.get($name).ok_or(())?.$ty_access().ok_or(())? } } Ok(BlockHeaderData { header: BlockHeader { version: 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(|_| ())? } else { BlockHash::all_zeros() }, merkle_root: TxMerkleNode::from_hex(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(|_| ())?), nonce: get_field!("nonce", as_u64).try_into().map_err(|_| ())?, }, chainwork: hex_to_uint256(get_field!("chainwork", as_str)).map_err(|_| ())?, height: get_field!("height", as_u64).try_into().map_err(|_| ())?, }) } } /// Converts a JSON value into a block. Assumes the block is hex-encoded in a JSON string. 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(block_data) => match encode::deserialize(&block_data) { Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid block data")), Ok(block) => Ok(block), }, }, } } } /// Converts a JSON value into the best block hash and optional height. impl TryInto<(BlockHash, Option)> for JsonResponse { type Error = std::io::Error; fn try_into(self) -> std::io::Result<(BlockHash, Option)> { if !self.0.is_object() { return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object")); } let hash = match &self.0["bestblockhash"] { serde_json::Value::String(hex_data) => match BlockHash::from_hex(&hex_data) { Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")), Ok(block_hash) => block_hash, }, _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string")), }; let height = match &self.0["blocks"] { serde_json::Value::Null => None, serde_json::Value::Number(height) => match height.as_u64() { None => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid height")), Some(height) => match height.try_into() { Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid height")), Ok(height) => Some(height), } }, _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON number")), }; Ok((hash, height)) } } 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), }, }, } } } /// Converts a JSON value into a transaction. WATCH OUT! this cannot be used for zero-input transactions /// (e.g. createrawtransaction). See impl TryInto for JsonResponse { type Error = std::io::Error; fn try_into(self) -> std::io::Result { let hex_tx = if self.0.is_object() { // result is json encoded match &self.0["hex"] { // result has hex field serde_json::Value::String(hex_data) => match self.0["complete"] { // result may or may not be signed (e.g. signrawtransactionwithwallet) serde_json::Value::Bool(x) => { if x == false { let reason = match &self.0["errors"][0]["error"] { serde_json::Value::String(x) => x.as_str(), _ => "Unknown error", }; return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("transaction couldn't be signed. {}", reason), )); } else { hex_data } } // result is a complete transaction (e.g. getrawtranaction verbose) _ => hex_data, }, _ => return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, "expected JSON string", )), } } else { // result is plain text (e.g. getrawtransaction no verbose) match self.0.as_str() { Some(hex_tx) => hex_tx, None => { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, "expected JSON string", )) } } }; match Vec::::from_hex(hex_tx) { Err(_) => Err(std::io::Error::new( std::io::ErrorKind::InvalidData, "invalid hex data", )), Ok(tx_data) => match encode::deserialize(&tx_data) { Err(_) => Err(std::io::Error::new( std::io::ErrorKind::InvalidData, "invalid transaction", )), Ok(tx) => Ok(tx), }, } } } #[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 serde_json::value::Number; use serde_json::Value; /// Converts from `BlockHeaderData` into a `GetHeaderResponse` JSON value. impl From for serde_json::Value { fn from(data: BlockHeaderData) -> Self { let BlockHeaderData { chainwork, height, header } = data; serde_json::json!({ "chainwork": chainwork.to_string()["0x".len()..], "height": height, "version": header.version, "merkleroot": header.merkle_root.to_hex(), "time": header.time, "nonce": header.nonce, "bits": header.bits.to_hex(), "previousblockhash": header.prev_blockhash.to_hex(), }) } } #[test] fn into_block_header_from_json_response_with_unexpected_type() { let response = JsonResponse(serde_json::json!(42)); match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "unexpected JSON type"); }, Ok(_) => panic!("Expected error"), } } #[test] fn into_block_header_from_json_response_with_unexpected_header_type() { let response = JsonResponse(serde_json::json!([42])); match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object"); }, Ok(_) => panic!("Expected error"), } } #[test] fn into_block_header_from_json_response_with_invalid_header_response() { let block = genesis_block(Network::Bitcoin); let mut response = JsonResponse(BlockHeaderData { chainwork: block.header.work(), height: 0, header: block.header }.into()); response.0["chainwork"].take(); match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "invalid header data"); }, Ok(_) => panic!("Expected error"), } } #[test] fn into_block_header_from_json_response_with_invalid_header_data() { let block = genesis_block(Network::Bitcoin); let mut response = JsonResponse(BlockHeaderData { chainwork: block.header.work(), height: 0, header: block.header }.into()); response.0["chainwork"] = serde_json::json!("foobar"); match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "invalid header data"); }, Ok(_) => panic!("Expected error"), } } #[test] fn into_block_header_from_json_response_with_valid_header() { let block = genesis_block(Network::Bitcoin); let response = JsonResponse(BlockHeaderData { chainwork: block.header.work(), height: 0, header: block.header }.into()); match TryInto::::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), Ok(data) => { assert_eq!(data.chainwork, block.header.work()); assert_eq!(data.height, 0); assert_eq!(data.header, block.header); }, } } #[test] fn into_block_header_from_json_response_with_valid_header_array() { let genesis_block = genesis_block(Network::Bitcoin); let best_block_header = BlockHeader { prev_blockhash: genesis_block.block_hash(), ..genesis_block.header }; let chainwork = genesis_block.header.work() + best_block_header.work(); let response = JsonResponse(serde_json::json!([ serde_json::Value::from(BlockHeaderData { chainwork, height: 1, header: best_block_header, }), serde_json::Value::from(BlockHeaderData { chainwork: genesis_block.header.work(), height: 0, header: genesis_block.header, }), ])); match TryInto::::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), Ok(data) => { assert_eq!(data.chainwork, chainwork); assert_eq!(data.height, 1); assert_eq!(data.header, best_block_header); }, } } #[test] fn into_block_header_from_json_response_without_previous_block_hash() { let block = genesis_block(Network::Bitcoin); let mut response = JsonResponse(BlockHeaderData { chainwork: block.header.work(), height: 0, header: block.header }.into()); response.0.as_object_mut().unwrap().remove("previousblockhash"); match TryInto::::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), Ok(BlockHeaderData { chainwork: _, height: _, header }) => { assert_eq!(header, block.header); }, } } #[test] fn into_block_from_invalid_binary_response() { let response = BinaryResponse(b"foo".to_vec()); match TryInto::::try_into(response) { Err(_) => {}, Ok(_) => panic!("Expected error"), } } #[test] fn into_block_from_valid_binary_response() { let genesis_block = genesis_block(Network::Bitcoin); let response = BinaryResponse(encode::serialize(&genesis_block)); match TryInto::::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), Ok(block) => assert_eq!(block, genesis_block), } } #[test] fn into_block_from_json_response_with_unexpected_type() { let response = JsonResponse(serde_json::json!({ "result": "foo" })); match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string"); }, Ok(_) => panic!("Expected error"), } } #[test] fn into_block_from_json_response_with_invalid_hex_data() { let response = JsonResponse(serde_json::json!("foobar")); 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"); }, Ok(_) => panic!("Expected error"), } } #[test] fn into_block_from_json_response_with_invalid_block_data() { let response = JsonResponse(serde_json::json!("abcd")); match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "invalid block data"); }, Ok(_) => panic!("Expected error"), } } #[test] fn into_block_from_json_response_with_valid_block_data() { let genesis_block = genesis_block(Network::Bitcoin); let response = JsonResponse(serde_json::json!(encode::serialize_hex(&genesis_block))); match TryInto::::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), Ok(block) => assert_eq!(block, genesis_block), } } #[test] fn into_block_hash_from_json_response_with_unexpected_type() { let response = JsonResponse(serde_json::json!("foo")); match TryInto::<(BlockHash, Option)>::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object"); }, Ok(_) => panic!("Expected error"), } } #[test] fn into_block_hash_from_json_response_with_unexpected_bestblockhash_type() { let response = JsonResponse(serde_json::json!({ "bestblockhash": 42 })); match TryInto::<(BlockHash, Option)>::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string"); }, Ok(_) => panic!("Expected error"), } } #[test] fn into_block_hash_from_json_response_with_invalid_hex_data() { let response = JsonResponse(serde_json::json!({ "bestblockhash": "foobar"} )); match TryInto::<(BlockHash, Option)>::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data"); }, Ok(_) => panic!("Expected error"), } } #[test] 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(), })); match TryInto::<(BlockHash, Option)>::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), Ok((hash, height)) => { assert_eq!(hash, block.block_hash()); assert!(height.is_none()); }, } } #[test] 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(), "blocks": "foo", })); match TryInto::<(BlockHash, Option)>::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON number"); }, Ok(_) => panic!("Expected error"), } } #[test] 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(), "blocks": std::u64::MAX, })); match TryInto::<(BlockHash, Option)>::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "invalid height"); }, Ok(_) => panic!("Expected error"), } } #[test] 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(), "blocks": 1, })); match TryInto::<(BlockHash, Option)>::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), Ok((hash, height)) => { assert_eq!(hash, block.block_hash()); assert_eq!(height.unwrap(), 1); }, } } #[test] fn into_txid_from_json_response_with_unexpected_type() { let response = JsonResponse(serde_json::json!({ "result": "foo" })); match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string"); } Ok(_) => panic!("Expected error"), } } #[test] fn into_txid_from_json_response_with_invalid_hex_data() { let response = JsonResponse(serde_json::json!("foobar")); 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"); } Ok(_) => panic!("Expected error"), } } #[test] fn into_txid_from_json_response_with_invalid_txid_data() { let response = JsonResponse(serde_json::json!("abcd")); 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"); } Ok(_) => panic!("Expected error"), } } #[test] fn into_txid_from_json_response_with_valid_txid_data() { let target_txid = Txid::from_slice(&[1; 32]).unwrap(); let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_txid))); match TryInto::::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), Ok(txid) => assert_eq!(txid, target_txid), } } // 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) // (e.g. as a result of signrawtransactionwithwallet). // plain hex transaction #[test] fn into_tx_from_json_response_with_invalid_hex_data() { let response = JsonResponse(serde_json::json!("foobar")); 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"); } Ok(_) => panic!("Expected error"), } } #[test] fn into_tx_from_json_response_with_invalid_data_type() { let response = JsonResponse(Value::Number(Number::from_f64(1.0).unwrap())); match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string"); } Ok(_) => panic!("Expected error"), } } #[test] fn into_tx_from_json_response_with_invalid_tx_data() { let response = JsonResponse(serde_json::json!("abcd")); match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!(e.get_ref().unwrap().to_string(), "invalid transaction"); } Ok(_) => panic!("Expected error"), } } #[test] fn into_tx_from_json_response_with_valid_tx_data_plain() { let genesis_block = genesis_block(Network::Bitcoin); let target_tx = genesis_block.txdata.get(0).unwrap(); let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_tx))); match TryInto::::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), Ok(tx) => assert_eq!(&tx, target_tx), } } #[test] fn into_tx_from_json_response_with_valid_tx_data_hex_field() { let genesis_block = genesis_block(Network::Bitcoin); let target_tx = genesis_block.txdata.get(0).unwrap(); let response = JsonResponse(serde_json::json!({"hex": encode::serialize_hex(&target_tx)})); match TryInto::::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), Ok(tx) => assert_eq!(&tx, target_tx), } } // transaction in hex field of JSON object #[test] fn into_tx_from_json_response_with_no_hex_field() { let response = JsonResponse(serde_json::json!({ "error": "foo" })); match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert_eq!( e.get_ref().unwrap().to_string(), "expected JSON string" ); } Ok(_) => panic!("Expected error"), } } #[test] fn into_tx_from_json_response_not_signed() { let response = JsonResponse(serde_json::json!({ "hex": "foo", "complete": false })); match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); assert!( e.get_ref().unwrap().to_string().contains( "transaction couldn't be signed") ); } Ok(_) => panic!("Expected error"), } } }