|
1 | | -use crate::{BlockHeaderData, BlockSourceError}; |
2 | 1 | use crate::http::{BinaryResponse, JsonResponse}; |
3 | 2 | use crate::utils::hex_to_uint256; |
| 3 | +use crate::{BlockHeaderData, BlockSourceError}; |
4 | 4 |
|
5 | 5 | use bitcoin::blockdata::block::{Block, BlockHeader}; |
6 | 6 | use bitcoin::consensus::encode; |
7 | 7 | use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid}; |
8 | | -use bitcoin::hashes::hex::{ToHex, FromHex}; |
| 8 | +use bitcoin::hashes::hex::{FromHex, ToHex}; |
| 9 | +use bitcoin::Transaction; |
9 | 10 |
|
10 | 11 | use serde::Deserialize; |
11 | 12 |
|
@@ -104,7 +105,6 @@ impl TryFrom<GetHeaderResponse> for BlockHeaderData { |
104 | 105 | } |
105 | 106 | } |
106 | 107 |
|
107 | | - |
108 | 108 | /// Converts a JSON value into a block. Assumes the block is hex-encoded in a JSON string. |
109 | 109 | impl TryInto<Block> for JsonResponse { |
110 | 110 | type Error = std::io::Error; |
@@ -181,13 +181,88 @@ impl TryInto<Txid> for JsonResponse { |
181 | 181 | } |
182 | 182 | } |
183 | 183 |
|
| 184 | +impl TryInto<Transaction> for JsonResponse { |
| 185 | + type Error = std::io::Error; |
| 186 | + fn try_into(self) -> std::io::Result<Transaction> { |
| 187 | + let hex_tx = if self.0.is_object() { |
| 188 | + // result is json encoded |
| 189 | + match &self.0["hex"] { |
| 190 | + // result has hex field |
| 191 | + serde_json::Value::String(hex_data) => match self.0["complete"] { |
| 192 | + // result may or may not be signed (e.g. signrawtransactionwithwallet) |
| 193 | + serde_json::Value::Bool(x) => { |
| 194 | + if x == false { |
| 195 | + return Err(std::io::Error::new( |
| 196 | + std::io::ErrorKind::InvalidData, |
| 197 | + "transaction couldn't be signed", |
| 198 | + )); |
| 199 | + } else { |
| 200 | + hex_data |
| 201 | + } |
| 202 | + } |
| 203 | + // result if a complete transaction (e.g. getrawtranaction) |
| 204 | + _ => hex_data, |
| 205 | + }, |
| 206 | + |
| 207 | + // result is an error |
| 208 | + _ => match &self.0["error"] { |
| 209 | + serde_json::Value::String(error_data) => { |
| 210 | + return Err(std::io::Error::new( |
| 211 | + std::io::ErrorKind::InvalidData, |
| 212 | + format!( |
| 213 | + "trying to construct the transaction returned an error: {}", |
| 214 | + error_data |
| 215 | + ), |
| 216 | + )) |
| 217 | + } |
| 218 | + _ => { |
| 219 | + return Err(std::io::Error::new( |
| 220 | + std::io::ErrorKind::InvalidData, |
| 221 | + "no hex nor error fields in the provided JSON object", |
| 222 | + )) |
| 223 | + } |
| 224 | + }, |
| 225 | + } |
| 226 | + } else { |
| 227 | + // result is plain text |
| 228 | + match self.0.as_str() { |
| 229 | + Some(hex_tx) => hex_tx, |
| 230 | + |
| 231 | + None => { |
| 232 | + return Err(std::io::Error::new( |
| 233 | + std::io::ErrorKind::InvalidData, |
| 234 | + "expected JSON string", |
| 235 | + )) |
| 236 | + } |
| 237 | + } |
| 238 | + }; |
| 239 | + |
| 240 | + match Vec::<u8>::from_hex(hex_tx) { |
| 241 | + Err(_) => Err(std::io::Error::new( |
| 242 | + std::io::ErrorKind::InvalidData, |
| 243 | + "invalid hex data", |
| 244 | + )), |
| 245 | + |
| 246 | + Ok(tx_data) => match encode::deserialize(&tx_data) { |
| 247 | + Err(_) => Err(std::io::Error::new( |
| 248 | + std::io::ErrorKind::InvalidData, |
| 249 | + "invalid transaction", |
| 250 | + )), |
| 251 | + Ok(tx) => Ok(tx), |
| 252 | + }, |
| 253 | + } |
| 254 | + } |
| 255 | +} |
| 256 | + |
184 | 257 | #[cfg(test)] |
185 | 258 | pub(crate) mod tests { |
186 | 259 | use super::*; |
187 | 260 | use bitcoin::blockdata::constants::genesis_block; |
188 | | - use bitcoin::consensus::encode; |
| 261 | + use bitcoin::consensus::deserialize; |
189 | 262 | use bitcoin::hashes::Hash; |
190 | 263 | use bitcoin::network::constants::Network; |
| 264 | + use serde_json::value::Number; |
| 265 | + use serde_json::Value; |
191 | 266 |
|
192 | 267 | /// Converts from `BlockHeaderData` into a `GetHeaderResponse` JSON value. |
193 | 268 | impl From<BlockHeaderData> for serde_json::Value { |
@@ -541,4 +616,103 @@ pub(crate) mod tests { |
541 | 616 | Ok(txid) => assert_eq!(txid, target_txid), |
542 | 617 | } |
543 | 618 | } |
| 619 | + |
| 620 | + /// 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) |
| 621 | + /// 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). |
| 622 | +
|
| 623 | + // plain hex transaction |
| 624 | + |
| 625 | + #[test] |
| 626 | + fn into_tx_from_json_response_with_invalid_hex_data() { |
| 627 | + let response = JsonResponse(serde_json::json!("foobar")); |
| 628 | + match TryInto::<Transaction>::try_into(response) { |
| 629 | + Err(e) => { |
| 630 | + assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); |
| 631 | + assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data"); |
| 632 | + } |
| 633 | + Ok(_) => panic!("Expected error"), |
| 634 | + } |
| 635 | + } |
| 636 | + |
| 637 | + #[test] |
| 638 | + fn into_tx_from_json_response_with_invalid_data_type() { |
| 639 | + let response = JsonResponse(Value::Number(Number::from_f64(1.0).unwrap())); |
| 640 | + match TryInto::<Transaction>::try_into(response) { |
| 641 | + Err(e) => { |
| 642 | + assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); |
| 643 | + assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string"); |
| 644 | + } |
| 645 | + Ok(_) => panic!("Expected error"), |
| 646 | + } |
| 647 | + } |
| 648 | + |
| 649 | + #[test] |
| 650 | + fn into_tx_from_json_response_with_invalid_tx_data() { |
| 651 | + let response = JsonResponse(serde_json::json!("abcd")); |
| 652 | + match TryInto::<Transaction>::try_into(response) { |
| 653 | + Err(e) => { |
| 654 | + assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); |
| 655 | + assert_eq!(e.get_ref().unwrap().to_string(), "invalid transaction"); |
| 656 | + } |
| 657 | + Ok(_) => panic!("Expected error"), |
| 658 | + } |
| 659 | + } |
| 660 | + |
| 661 | + #[test] |
| 662 | + fn into_tx_from_json_response_with_valid_tx_data() { |
| 663 | + let tx_bytes = Vec::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap(); |
| 664 | + let target_tx = deserialize(&tx_bytes).unwrap(); |
| 665 | + let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_tx))); |
| 666 | + match TryInto::<Transaction>::try_into(response) { |
| 667 | + Err(e) => panic!("Unexpected error: {:?}", e), |
| 668 | + Ok(txid) => assert_eq!(txid, target_tx), |
| 669 | + } |
| 670 | + } |
| 671 | + |
| 672 | + // transaction in hex field of JSON object |
| 673 | + |
| 674 | + #[test] |
| 675 | + fn into_tx_from_json_response_with_no_hex_field() { |
| 676 | + let response = JsonResponse(serde_json::json!({ "error": "foo" })); |
| 677 | + match TryInto::<Transaction>::try_into(response) { |
| 678 | + Err(e) => { |
| 679 | + assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); |
| 680 | + assert_eq!( |
| 681 | + e.get_ref().unwrap().to_string(), |
| 682 | + "trying to construct the transaction returned an error: foo" |
| 683 | + ); |
| 684 | + } |
| 685 | + Ok(_) => panic!("Expected error"), |
| 686 | + } |
| 687 | + } |
| 688 | + |
| 689 | + #[test] |
| 690 | + fn into_tx_from_json_response_with_no_hex_nor_error_fields() { |
| 691 | + let response = JsonResponse(serde_json::json!({ "result": "foo" })); |
| 692 | + match TryInto::<Transaction>::try_into(response) { |
| 693 | + Err(e) => { |
| 694 | + assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); |
| 695 | + assert_eq!( |
| 696 | + e.get_ref().unwrap().to_string(), |
| 697 | + "no hex nor error fields in the provided JSON object" |
| 698 | + ); |
| 699 | + } |
| 700 | + Ok(_) => panic!("Expected error"), |
| 701 | + } |
| 702 | + } |
| 703 | + |
| 704 | + #[test] |
| 705 | + fn into_tx_from_json_response_not_signed() { |
| 706 | + let response = JsonResponse(serde_json::json!({ "hex": "foo", "complete": false })); |
| 707 | + match TryInto::<Transaction>::try_into(response) { |
| 708 | + Err(e) => { |
| 709 | + assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); |
| 710 | + assert_eq!( |
| 711 | + e.get_ref().unwrap().to_string(), |
| 712 | + "transaction couldn't be signed" |
| 713 | + ); |
| 714 | + } |
| 715 | + Ok(_) => panic!("Expected error"), |
| 716 | + } |
| 717 | + } |
544 | 718 | } |
0 commit comments