|
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,77 @@ impl TryInto<Txid> for JsonResponse {
|
181 | 181 | }
|
182 | 182 | }
|
183 | 183 |
|
| 184 | +/// Converts a JSON value into a transaction. WATCH OUT! this cannot be used for zero-input transactions |
| 185 | +/// (e.g. createrawtransaction). See https://github.com/rust-bitcoin/rust-bitcoincore-rpc/issues/197 |
| 186 | +impl TryInto<Transaction> for JsonResponse { |
| 187 | + type Error = std::io::Error; |
| 188 | + fn try_into(self) -> std::io::Result<Transaction> { |
| 189 | + let hex_tx = if self.0.is_object() { |
| 190 | + // result is json encoded |
| 191 | + match &self.0["hex"] { |
| 192 | + // result has hex field |
| 193 | + serde_json::Value::String(hex_data) => match self.0["complete"] { |
| 194 | + // result may or may not be signed (e.g. signrawtransactionwithwallet) |
| 195 | + serde_json::Value::Bool(x) => { |
| 196 | + if x == false { |
| 197 | + let reason = match &self.0["errors"][0]["error"] { |
| 198 | + serde_json::Value::String(x) => x.as_str(), |
| 199 | + _ => "Unknown error", |
| 200 | + }; |
| 201 | + |
| 202 | + return Err(std::io::Error::new( |
| 203 | + std::io::ErrorKind::InvalidData, |
| 204 | + format!("transaction couldn't be signed. {}", reason), |
| 205 | + )); |
| 206 | + } else { |
| 207 | + hex_data |
| 208 | + } |
| 209 | + } |
| 210 | + // result is a complete transaction (e.g. getrawtranaction verbose) |
| 211 | + _ => hex_data, |
| 212 | + }, |
| 213 | + _ => return Err(std::io::Error::new( |
| 214 | + std::io::ErrorKind::InvalidData, |
| 215 | + "expected JSON string", |
| 216 | + )), |
| 217 | + } |
| 218 | + } else { |
| 219 | + // result is plain text (e.g. getrawtransaction no verbose) |
| 220 | + match self.0.as_str() { |
| 221 | + Some(hex_tx) => hex_tx, |
| 222 | + None => { |
| 223 | + return Err(std::io::Error::new( |
| 224 | + std::io::ErrorKind::InvalidData, |
| 225 | + "expected JSON string", |
| 226 | + )) |
| 227 | + } |
| 228 | + } |
| 229 | + }; |
| 230 | + |
| 231 | + match Vec::<u8>::from_hex(hex_tx) { |
| 232 | + Err(_) => Err(std::io::Error::new( |
| 233 | + std::io::ErrorKind::InvalidData, |
| 234 | + "invalid hex data", |
| 235 | + )), |
| 236 | + Ok(tx_data) => match encode::deserialize(&tx_data) { |
| 237 | + Err(_) => Err(std::io::Error::new( |
| 238 | + std::io::ErrorKind::InvalidData, |
| 239 | + "invalid transaction", |
| 240 | + )), |
| 241 | + Ok(tx) => Ok(tx), |
| 242 | + }, |
| 243 | + } |
| 244 | + } |
| 245 | +} |
| 246 | + |
184 | 247 | #[cfg(test)]
|
185 | 248 | pub(crate) mod tests {
|
186 | 249 | use super::*;
|
187 | 250 | use bitcoin::blockdata::constants::genesis_block;
|
188 |
| - use bitcoin::consensus::encode; |
189 | 251 | use bitcoin::hashes::Hash;
|
190 | 252 | use bitcoin::network::constants::Network;
|
| 253 | + use serde_json::value::Number; |
| 254 | + use serde_json::Value; |
191 | 255 |
|
192 | 256 | /// Converts from `BlockHeaderData` into a `GetHeaderResponse` JSON value.
|
193 | 257 | impl From<BlockHeaderData> for serde_json::Value {
|
@@ -541,4 +605,101 @@ pub(crate) mod tests {
|
541 | 605 | Ok(txid) => assert_eq!(txid, target_txid),
|
542 | 606 | }
|
543 | 607 | }
|
| 608 | + |
| 609 | + // TryInto<Transaction> can be used in two ways, first with plain hex response where data is |
| 610 | + // the hex encoded transaction (e.g. as a result of getrawtransaction) or as a JSON object |
| 611 | + // where the hex encoded transaction can be found in the hex field of the object (if present) |
| 612 | + // (e.g. as a result of signrawtransactionwithwallet). |
| 613 | + |
| 614 | + // plain hex transaction |
| 615 | + |
| 616 | + #[test] |
| 617 | + fn into_tx_from_json_response_with_invalid_hex_data() { |
| 618 | + let response = JsonResponse(serde_json::json!("foobar")); |
| 619 | + match TryInto::<Transaction>::try_into(response) { |
| 620 | + Err(e) => { |
| 621 | + assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); |
| 622 | + assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data"); |
| 623 | + } |
| 624 | + Ok(_) => panic!("Expected error"), |
| 625 | + } |
| 626 | + } |
| 627 | + |
| 628 | + #[test] |
| 629 | + fn into_tx_from_json_response_with_invalid_data_type() { |
| 630 | + let response = JsonResponse(Value::Number(Number::from_f64(1.0).unwrap())); |
| 631 | + match TryInto::<Transaction>::try_into(response) { |
| 632 | + Err(e) => { |
| 633 | + assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); |
| 634 | + assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string"); |
| 635 | + } |
| 636 | + Ok(_) => panic!("Expected error"), |
| 637 | + } |
| 638 | + } |
| 639 | + |
| 640 | + #[test] |
| 641 | + fn into_tx_from_json_response_with_invalid_tx_data() { |
| 642 | + let response = JsonResponse(serde_json::json!("abcd")); |
| 643 | + match TryInto::<Transaction>::try_into(response) { |
| 644 | + Err(e) => { |
| 645 | + assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); |
| 646 | + assert_eq!(e.get_ref().unwrap().to_string(), "invalid transaction"); |
| 647 | + } |
| 648 | + Ok(_) => panic!("Expected error"), |
| 649 | + } |
| 650 | + } |
| 651 | + |
| 652 | + #[test] |
| 653 | + fn into_tx_from_json_response_with_valid_tx_data_plain() { |
| 654 | + let genesis_block = genesis_block(Network::Bitcoin); |
| 655 | + let target_tx = genesis_block.txdata.get(0).unwrap(); |
| 656 | + let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_tx))); |
| 657 | + match TryInto::<Transaction>::try_into(response) { |
| 658 | + Err(e) => panic!("Unexpected error: {:?}", e), |
| 659 | + Ok(tx) => assert_eq!(&tx, target_tx), |
| 660 | + } |
| 661 | + } |
| 662 | + |
| 663 | + #[test] |
| 664 | + fn into_tx_from_json_response_with_valid_tx_data_hex_field() { |
| 665 | + let genesis_block = genesis_block(Network::Bitcoin); |
| 666 | + let target_tx = genesis_block.txdata.get(0).unwrap(); |
| 667 | + let response = JsonResponse(serde_json::json!({"hex": encode::serialize_hex(&target_tx)})); |
| 668 | + match TryInto::<Transaction>::try_into(response) { |
| 669 | + Err(e) => panic!("Unexpected error: {:?}", e), |
| 670 | + Ok(tx) => assert_eq!(&tx, target_tx), |
| 671 | + } |
| 672 | + } |
| 673 | + |
| 674 | + // transaction in hex field of JSON object |
| 675 | + |
| 676 | + #[test] |
| 677 | + fn into_tx_from_json_response_with_no_hex_field() { |
| 678 | + let response = JsonResponse(serde_json::json!({ "error": "foo" })); |
| 679 | + match TryInto::<Transaction>::try_into(response) { |
| 680 | + Err(e) => { |
| 681 | + assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); |
| 682 | + assert_eq!( |
| 683 | + e.get_ref().unwrap().to_string(), |
| 684 | + "expected JSON string" |
| 685 | + ); |
| 686 | + } |
| 687 | + Ok(_) => panic!("Expected error"), |
| 688 | + } |
| 689 | + } |
| 690 | + |
| 691 | + #[test] |
| 692 | + fn into_tx_from_json_response_not_signed() { |
| 693 | + let response = JsonResponse(serde_json::json!({ "hex": "foo", "complete": false })); |
| 694 | + match TryInto::<Transaction>::try_into(response) { |
| 695 | + Err(e) => { |
| 696 | + assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); |
| 697 | + assert!( |
| 698 | + e.get_ref().unwrap().to_string().contains( |
| 699 | + "transaction couldn't be signed") |
| 700 | + ); |
| 701 | + } |
| 702 | + Ok(_) => panic!("Expected error"), |
| 703 | + } |
| 704 | + } |
544 | 705 | }
|
0 commit comments