Feature-gate `GetUtxosResponse` in `lightning-block-sync`
[rust-lightning] / lightning-block-sync / src / convert.rs
1 use crate::http::{BinaryResponse, JsonResponse};
2 use crate::utils::hex_to_work;
3 use crate::{BlockHeaderData, BlockSourceError};
4
5 use bitcoin::blockdata::block::{Block, Header};
6 use bitcoin::consensus::encode;
7 use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid};
8 use bitcoin::hashes::hex::FromHex;
9 use bitcoin::Transaction;
10
11 use serde_json;
12
13 use std::convert::From;
14 use std::convert::TryFrom;
15 use std::convert::TryInto;
16 use std::str::FromStr;
17 use bitcoin::hashes::Hash;
18
19 impl TryInto<serde_json::Value> for JsonResponse {
20         type Error = std::io::Error;
21         fn try_into(self) -> Result<serde_json::Value, std::io::Error> { Ok(self.0) }
22 }
23
24 /// Conversion from `std::io::Error` into `BlockSourceError`.
25 impl From<std::io::Error> for BlockSourceError {
26         fn from(e: std::io::Error) -> BlockSourceError {
27                 match e.kind() {
28                         std::io::ErrorKind::InvalidData => BlockSourceError::persistent(e),
29                         std::io::ErrorKind::InvalidInput => BlockSourceError::persistent(e),
30                         _ => BlockSourceError::transient(e),
31                 }
32         }
33 }
34
35 /// Parses binary data as a block.
36 impl TryInto<Block> for BinaryResponse {
37         type Error = std::io::Error;
38
39         fn try_into(self) -> std::io::Result<Block> {
40                 match encode::deserialize(&self.0) {
41                         Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid block data")),
42                         Ok(block) => Ok(block),
43                 }
44         }
45 }
46
47 /// Parses binary data as a block hash.
48 impl TryInto<BlockHash> for BinaryResponse {
49         type Error = std::io::Error;
50
51         fn try_into(self) -> std::io::Result<BlockHash> {
52                 BlockHash::from_slice(&self.0).map_err(|_|
53                         std::io::Error::new(std::io::ErrorKind::InvalidData, "bad block hash length")
54                 )
55         }
56 }
57
58 /// Converts a JSON value into block header data. The JSON value may be an object representing a
59 /// block header or an array of such objects. In the latter case, the first object is converted.
60 impl TryInto<BlockHeaderData> for JsonResponse {
61         type Error = std::io::Error;
62
63         fn try_into(self) -> std::io::Result<BlockHeaderData> {
64                 let header = match self.0 {
65                         serde_json::Value::Array(mut array) if !array.is_empty() => array.drain(..).next().unwrap(),
66                         serde_json::Value::Object(_) => self.0,
67                         _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unexpected JSON type")),
68                 };
69
70                 if !header.is_object() {
71                         return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object"));
72                 }
73
74                 // Add an empty previousblockhash for the genesis block.
75                 match header.try_into() {
76                         Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid header data")),
77                         Ok(header) => Ok(header),
78                 }
79         }
80 }
81
82 impl TryFrom<serde_json::Value> for BlockHeaderData {
83         type Error = ();
84
85         fn try_from(response: serde_json::Value) -> Result<Self, ()> {
86                 macro_rules! get_field { ($name: expr, $ty_access: tt) => {
87                         response.get($name).ok_or(())?.$ty_access().ok_or(())?
88                 } }
89
90                 Ok(BlockHeaderData {
91                         header: Header {
92                                 version: bitcoin::blockdata::block::Version::from_consensus(
93                                         get_field!("version", as_i64).try_into().map_err(|_| ())?
94                                 ),
95                                 prev_blockhash: if let Some(hash_str) = response.get("previousblockhash") {
96                                                 BlockHash::from_str(hash_str.as_str().ok_or(())?).map_err(|_| ())?
97                                         } else { BlockHash::all_zeros() },
98                                 merkle_root: TxMerkleNode::from_str(get_field!("merkleroot", as_str)).map_err(|_| ())?,
99                                 time: get_field!("time", as_u64).try_into().map_err(|_| ())?,
100                                 bits: bitcoin::CompactTarget::from_consensus(
101                                         u32::from_be_bytes(<[u8; 4]>::from_hex(get_field!("bits", as_str)).map_err(|_| ())?)
102                                 ),
103                                 nonce: get_field!("nonce", as_u64).try_into().map_err(|_| ())?,
104                         },
105                         chainwork: hex_to_work(get_field!("chainwork", as_str)).map_err(|_| ())?,
106                         height: get_field!("height", as_u64).try_into().map_err(|_| ())?,
107                 })
108         }
109 }
110
111 /// Converts a JSON value into a block. Assumes the block is hex-encoded in a JSON string.
112 impl TryInto<Block> for JsonResponse {
113         type Error = std::io::Error;
114
115         fn try_into(self) -> std::io::Result<Block> {
116                 match self.0.as_str() {
117                         None => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string")),
118                         Some(hex_data) => match Vec::<u8>::from_hex(hex_data) {
119                                 Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")),
120                                 Ok(block_data) => match encode::deserialize(&block_data) {
121                                         Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid block data")),
122                                         Ok(block) => Ok(block),
123                                 },
124                         },
125                 }
126         }
127 }
128
129 /// Converts a JSON value into the best block hash and optional height.
130 impl TryInto<(BlockHash, Option<u32>)> for JsonResponse {
131         type Error = std::io::Error;
132
133         fn try_into(self) -> std::io::Result<(BlockHash, Option<u32>)> {
134                 if !self.0.is_object() {
135                         return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object"));
136                 }
137
138                 let hash = match &self.0["bestblockhash"] {
139                         serde_json::Value::String(hex_data) => match BlockHash::from_str(&hex_data) {
140                                 Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")),
141                                 Ok(block_hash) => block_hash,
142                         },
143                         _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string")),
144                 };
145
146                 let height = match &self.0["blocks"] {
147                         serde_json::Value::Null => None,
148                         serde_json::Value::Number(height) => match height.as_u64() {
149                                 None => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid height")),
150                                 Some(height) => match height.try_into() {
151                                         Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid height")),
152                                         Ok(height) => Some(height),
153                                 }
154                         },
155                         _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON number")),
156                 };
157
158                 Ok((hash, height))
159         }
160 }
161
162 impl TryInto<Txid> for JsonResponse {
163         type Error = std::io::Error;
164         fn try_into(self) -> std::io::Result<Txid> {
165                 let hex_data = self.0.as_str().ok_or(Self::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string" ))?;
166                 Txid::from_str(hex_data).map_err(|err|Self::Error::new(std::io::ErrorKind::InvalidData, err.to_string() ))
167         }
168 }
169
170 /// Converts a JSON value into a transaction. WATCH OUT! this cannot be used for zero-input transactions
171 /// (e.g. createrawtransaction). See <https://github.com/rust-bitcoin/rust-bitcoincore-rpc/issues/197>
172 impl TryInto<Transaction> for JsonResponse {
173         type Error = std::io::Error;
174         fn try_into(self) -> std::io::Result<Transaction> {
175                 let hex_tx = if self.0.is_object() {
176                         // result is json encoded
177                         match &self.0["hex"] {
178                                 // result has hex field
179                                 serde_json::Value::String(hex_data) => match self.0["complete"] {
180                                         // result may or may not be signed (e.g. signrawtransactionwithwallet)
181                                         serde_json::Value::Bool(x) => {
182                                                 if x == false {
183                                                         let reason = match &self.0["errors"][0]["error"] {
184                                                                 serde_json::Value::String(x) => x.as_str(),
185                                                                 _ => "Unknown error",
186                                                         };
187
188                                                         return Err(std::io::Error::new(
189                                                                 std::io::ErrorKind::InvalidData,
190                                                                 format!("transaction couldn't be signed. {}", reason),
191                                                         ));
192                                                 } else {
193                                                         hex_data
194                                                 }
195                                         }
196                                         // result is a complete transaction (e.g. getrawtranaction verbose)
197                                         _ => hex_data,
198                                 },
199                                 _ => return Err(std::io::Error::new(
200                                                         std::io::ErrorKind::InvalidData,
201                                                         "expected JSON string",
202                                         )),
203                         }
204                 } else {
205                         // result is plain text (e.g. getrawtransaction no verbose)
206                         match self.0.as_str() {
207                                 Some(hex_tx) => hex_tx,
208                                 None => {
209                                         return Err(std::io::Error::new(
210                                                 std::io::ErrorKind::InvalidData,
211                                                 "expected JSON string",
212                                         ))
213                                 }
214                         }
215                 };
216
217                 match Vec::<u8>::from_hex(hex_tx) {
218                         Err(_) => Err(std::io::Error::new(
219                                 std::io::ErrorKind::InvalidData,
220                                 "invalid hex data",
221                         )),
222                         Ok(tx_data) => match encode::deserialize(&tx_data) {
223                                 Err(_) => Err(std::io::Error::new(
224                                         std::io::ErrorKind::InvalidData,
225                                         "invalid transaction",
226                                 )),
227                                 Ok(tx) => Ok(tx),
228                         },
229                 }
230         }
231 }
232
233 impl TryInto<BlockHash> for JsonResponse {
234         type Error = std::io::Error;
235
236         fn try_into(self) -> std::io::Result<BlockHash> {
237                 match self.0.as_str() {
238                         None => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string")),
239                         Some(hex_data) if hex_data.len() != 64 =>
240                                 Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hash length")),
241                         Some(hex_data) => BlockHash::from_str(hex_data)
242                                 .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")),
243                 }
244         }
245 }
246
247 /// The REST `getutxos` endpoint retuns a whole pile of data we don't care about and one bit we do
248 /// - whether the `hit bitmap` field had any entries. Thus we condense the result down into only
249 /// that.
250 #[cfg(feature = "rest-client")]
251 pub(crate) struct GetUtxosResponse {
252         pub(crate) hit_bitmap_nonempty: bool
253 }
254
255 #[cfg(feature = "rest-client")]
256 impl TryInto<GetUtxosResponse> for JsonResponse {
257         type Error = std::io::Error;
258
259         fn try_into(self) -> std::io::Result<GetUtxosResponse> {
260                 let bitmap_str =
261                         self.0.as_object().ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected an object"))?
262                         .get("bitmap").ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "missing bitmap field"))?
263                         .as_str().ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "bitmap should be an str"))?;
264                         let mut hit_bitmap_nonempty = false;
265                         for c in bitmap_str.chars() {
266                                 if c < '0' || c > '9' {
267                                         return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid byte"));
268                                 }
269                                 if c > '0' { hit_bitmap_nonempty = true; }
270                         }
271                         Ok(GetUtxosResponse { hit_bitmap_nonempty })
272         }
273 }
274
275 #[cfg(test)]
276 pub(crate) mod tests {
277         use super::*;
278         use bitcoin::blockdata::constants::genesis_block;
279         use bitcoin::hashes::Hash;
280         use bitcoin::network::constants::Network;
281         use hex::DisplayHex;
282         use serde_json::value::Number;
283         use serde_json::Value;
284
285         /// Converts from `BlockHeaderData` into a `GetHeaderResponse` JSON value.
286         impl From<BlockHeaderData> for serde_json::Value {
287                 fn from(data: BlockHeaderData) -> Self {
288                         let BlockHeaderData { chainwork, height, header } = data;
289                         serde_json::json!({
290                                 "chainwork": chainwork.to_be_bytes().as_hex().to_string(),
291                                 "height": height,
292                                 "version": header.version.to_consensus(),
293                                 "merkleroot": header.merkle_root.to_string(),
294                                 "time": header.time,
295                                 "nonce": header.nonce,
296                                 "bits": header.bits.to_consensus().to_be_bytes().as_hex().to_string(),
297                                 "previousblockhash": header.prev_blockhash.to_string(),
298                         })
299                 }
300         }
301
302         #[test]
303         fn into_block_header_from_json_response_with_unexpected_type() {
304                 let response = JsonResponse(serde_json::json!(42));
305                 match TryInto::<BlockHeaderData>::try_into(response) {
306                         Err(e) => {
307                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
308                                 assert_eq!(e.get_ref().unwrap().to_string(), "unexpected JSON type");
309                         },
310                         Ok(_) => panic!("Expected error"),
311                 }
312         }
313
314         #[test]
315         fn into_block_header_from_json_response_with_unexpected_header_type() {
316                 let response = JsonResponse(serde_json::json!([42]));
317                 match TryInto::<BlockHeaderData>::try_into(response) {
318                         Err(e) => {
319                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
320                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
321                         },
322                         Ok(_) => panic!("Expected error"),
323                 }
324         }
325
326         #[test]
327         fn into_block_header_from_json_response_with_invalid_header_response() {
328                 let block = genesis_block(Network::Bitcoin);
329                 let mut response = JsonResponse(BlockHeaderData {
330                         chainwork: block.header.work(),
331                         height: 0,
332                         header: block.header
333                 }.into());
334                 response.0["chainwork"].take();
335
336                 match TryInto::<BlockHeaderData>::try_into(response) {
337                         Err(e) => {
338                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
339                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid header data");
340                         },
341                         Ok(_) => panic!("Expected error"),
342                 }
343         }
344
345         #[test]
346         fn into_block_header_from_json_response_with_invalid_header_data() {
347                 let block = genesis_block(Network::Bitcoin);
348                 let mut response = JsonResponse(BlockHeaderData {
349                         chainwork: block.header.work(),
350                         height: 0,
351                         header: block.header
352                 }.into());
353                 response.0["chainwork"] = serde_json::json!("foobar");
354
355                 match TryInto::<BlockHeaderData>::try_into(response) {
356                         Err(e) => {
357                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
358                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid header data");
359                         },
360                         Ok(_) => panic!("Expected error"),
361                 }
362         }
363
364         #[test]
365         fn into_block_header_from_json_response_with_valid_header() {
366                 let block = genesis_block(Network::Bitcoin);
367                 let response = JsonResponse(BlockHeaderData {
368                         chainwork: block.header.work(),
369                         height: 0,
370                         header: block.header
371                 }.into());
372
373                 match TryInto::<BlockHeaderData>::try_into(response) {
374                         Err(e) => panic!("Unexpected error: {:?}", e),
375                         Ok(data) => {
376                                 assert_eq!(data.chainwork, block.header.work());
377                                 assert_eq!(data.height, 0);
378                                 assert_eq!(data.header, block.header);
379                         },
380                 }
381         }
382
383         #[test]
384         fn into_block_header_from_json_response_with_valid_header_array() {
385                 let genesis_block = genesis_block(Network::Bitcoin);
386                 let best_block_header = Header {
387                         prev_blockhash: genesis_block.block_hash(),
388                         ..genesis_block.header
389                 };
390                 let chainwork = genesis_block.header.work() + best_block_header.work();
391                 let response = JsonResponse(serde_json::json!([
392                                 serde_json::Value::from(BlockHeaderData {
393                                         chainwork, height: 1, header: best_block_header,
394                                 }),
395                                 serde_json::Value::from(BlockHeaderData {
396                                         chainwork: genesis_block.header.work(), height: 0, header: genesis_block.header,
397                                 }),
398                 ]));
399
400                 match TryInto::<BlockHeaderData>::try_into(response) {
401                         Err(e) => panic!("Unexpected error: {:?}", e),
402                         Ok(data) => {
403                                 assert_eq!(data.chainwork, chainwork);
404                                 assert_eq!(data.height, 1);
405                                 assert_eq!(data.header, best_block_header);
406                         },
407                 }
408         }
409
410         #[test]
411         fn into_block_header_from_json_response_without_previous_block_hash() {
412                 let block = genesis_block(Network::Bitcoin);
413                 let mut response = JsonResponse(BlockHeaderData {
414                         chainwork: block.header.work(),
415                         height: 0,
416                         header: block.header
417                 }.into());
418                 response.0.as_object_mut().unwrap().remove("previousblockhash");
419
420                 match TryInto::<BlockHeaderData>::try_into(response) {
421                         Err(e) => panic!("Unexpected error: {:?}", e),
422                         Ok(BlockHeaderData { chainwork: _, height: _, header }) => {
423                                 assert_eq!(header, block.header);
424                         },
425                 }
426         }
427
428         #[test]
429         fn into_block_from_invalid_binary_response() {
430                 let response = BinaryResponse(b"foo".to_vec());
431                 match TryInto::<Block>::try_into(response) {
432                         Err(_) => {},
433                         Ok(_) => panic!("Expected error"),
434                 }
435         }
436
437         #[test]
438         fn into_block_from_valid_binary_response() {
439                 let genesis_block = genesis_block(Network::Bitcoin);
440                 let response = BinaryResponse(encode::serialize(&genesis_block));
441                 match TryInto::<Block>::try_into(response) {
442                         Err(e) => panic!("Unexpected error: {:?}", e),
443                         Ok(block) => assert_eq!(block, genesis_block),
444                 }
445         }
446
447         #[test]
448         fn into_block_from_json_response_with_unexpected_type() {
449                 let response = JsonResponse(serde_json::json!({ "result": "foo" }));
450                 match TryInto::<Block>::try_into(response) {
451                         Err(e) => {
452                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
453                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
454                         },
455                         Ok(_) => panic!("Expected error"),
456                 }
457         }
458
459         #[test]
460         fn into_block_from_json_response_with_invalid_hex_data() {
461                 let response = JsonResponse(serde_json::json!("foobar"));
462                 match TryInto::<Block>::try_into(response) {
463                         Err(e) => {
464                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
465                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
466                         },
467                         Ok(_) => panic!("Expected error"),
468                 }
469         }
470
471         #[test]
472         fn into_block_from_json_response_with_invalid_block_data() {
473                 let response = JsonResponse(serde_json::json!("abcd"));
474                 match TryInto::<Block>::try_into(response) {
475                         Err(e) => {
476                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
477                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid block data");
478                         },
479                         Ok(_) => panic!("Expected error"),
480                 }
481         }
482
483         #[test]
484         fn into_block_from_json_response_with_valid_block_data() {
485                 let genesis_block = genesis_block(Network::Bitcoin);
486                 let response = JsonResponse(serde_json::json!(encode::serialize_hex(&genesis_block)));
487                 match TryInto::<Block>::try_into(response) {
488                         Err(e) => panic!("Unexpected error: {:?}", e),
489                         Ok(block) => assert_eq!(block, genesis_block),
490                 }
491         }
492
493         #[test]
494         fn into_block_hash_from_json_response_with_unexpected_type() {
495                 let response = JsonResponse(serde_json::json!("foo"));
496                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
497                         Err(e) => {
498                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
499                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
500                         },
501                         Ok(_) => panic!("Expected error"),
502                 }
503         }
504
505         #[test]
506         fn into_block_hash_from_json_response_with_unexpected_bestblockhash_type() {
507                 let response = JsonResponse(serde_json::json!({ "bestblockhash": 42 }));
508                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
509                         Err(e) => {
510                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
511                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
512                         },
513                         Ok(_) => panic!("Expected error"),
514                 }
515         }
516
517         #[test]
518         fn into_block_hash_from_json_response_with_invalid_hex_data() {
519                 let response = JsonResponse(serde_json::json!({ "bestblockhash": "foobar"} ));
520                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
521                         Err(e) => {
522                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
523                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
524                         },
525                         Ok(_) => panic!("Expected error"),
526                 }
527         }
528
529         #[test]
530         fn into_block_hash_from_json_response_without_height() {
531                 let block = genesis_block(Network::Bitcoin);
532                 let response = JsonResponse(serde_json::json!({
533                         "bestblockhash": block.block_hash().to_string(),
534                 }));
535                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
536                         Err(e) => panic!("Unexpected error: {:?}", e),
537                         Ok((hash, height)) => {
538                                 assert_eq!(hash, block.block_hash());
539                                 assert!(height.is_none());
540                         },
541                 }
542         }
543
544         #[test]
545         fn into_block_hash_from_json_response_with_unexpected_blocks_type() {
546                 let block = genesis_block(Network::Bitcoin);
547                 let response = JsonResponse(serde_json::json!({
548                         "bestblockhash": block.block_hash().to_string(),
549                         "blocks": "foo",
550                 }));
551                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
552                         Err(e) => {
553                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
554                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON number");
555                         },
556                         Ok(_) => panic!("Expected error"),
557                 }
558         }
559
560         #[test]
561         fn into_block_hash_from_json_response_with_invalid_height() {
562                 let block = genesis_block(Network::Bitcoin);
563                 let response = JsonResponse(serde_json::json!({
564                         "bestblockhash": block.block_hash().to_string(),
565                         "blocks": std::u64::MAX,
566                 }));
567                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
568                         Err(e) => {
569                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
570                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid height");
571                         },
572                         Ok(_) => panic!("Expected error"),
573                 }
574         }
575
576         #[test]
577         fn into_block_hash_from_json_response_with_height() {
578                 let block = genesis_block(Network::Bitcoin);
579                 let response = JsonResponse(serde_json::json!({
580                         "bestblockhash": block.block_hash().to_string(),
581                         "blocks": 1,
582                 }));
583                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
584                         Err(e) => panic!("Unexpected error: {:?}", e),
585                         Ok((hash, height)) => {
586                                 assert_eq!(hash, block.block_hash());
587                                 assert_eq!(height.unwrap(), 1);
588                         },
589                 }
590         }
591
592         #[test]
593         fn into_txid_from_json_response_with_unexpected_type() {
594                 let response = JsonResponse(serde_json::json!({ "result": "foo" }));
595                 match TryInto::<Txid>::try_into(response) {
596                         Err(e) => {
597                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
598                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
599                         }
600                         Ok(_) => panic!("Expected error"),
601                 }
602         }
603
604         #[test]
605         fn into_txid_from_json_response_with_invalid_hex_data() {
606                 let response = JsonResponse(serde_json::json!("foobar"));
607                 match TryInto::<Txid>::try_into(response) {
608                         Err(e) => {
609                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
610                                 assert_eq!(e.get_ref().unwrap().to_string(), "bad hex string length 6 (expected 64)");
611                         }
612                         Ok(_) => panic!("Expected error"),
613                 }
614         }
615
616         #[test]
617         fn into_txid_from_json_response_with_invalid_txid_data() {
618                 let response = JsonResponse(serde_json::json!("abcd"));
619                 match TryInto::<Txid>::try_into(response) {
620                         Err(e) => {
621                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
622                                 assert_eq!(e.get_ref().unwrap().to_string(), "bad hex string length 4 (expected 64)");
623                         }
624                         Ok(_) => panic!("Expected error"),
625                 }
626         }
627
628         #[test]
629         fn into_txid_from_json_response_with_valid_txid_data() {
630                 let target_txid = Txid::from_slice(&[1; 32]).unwrap();
631                 let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_txid)));
632                 match TryInto::<Txid>::try_into(response) {
633                         Err(e) => panic!("Unexpected error: {:?}", e),
634                         Ok(txid) => assert_eq!(txid, target_txid),
635                 }
636         }
637
638         #[test]
639         fn into_txid_from_bitcoind_rpc_json_response() {
640                 let mut rpc_response = serde_json::json!(
641             {"error": "", "id": "770", "result": "7934f775149929a8b742487129a7c3a535dfb612f0b726cc67bc10bc2628f906"}
642
643         );
644         let r: std::io::Result<Txid> = JsonResponse(rpc_response.get_mut("result").unwrap().take())
645             .try_into();
646         assert_eq!(
647             r.unwrap().to_string(),
648             "7934f775149929a8b742487129a7c3a535dfb612f0b726cc67bc10bc2628f906"
649         );
650         }
651
652         // TryInto<Transaction> can be used in two ways, first with plain hex response where data is
653         // the hex encoded transaction (e.g. as a result of getrawtransaction) or as a JSON object
654         // where the hex encoded transaction can be found in the hex field of the object (if present)
655         // (e.g. as a result of signrawtransactionwithwallet).
656
657         // plain hex transaction
658
659         #[test]
660         fn into_tx_from_json_response_with_invalid_hex_data() {
661                 let response = JsonResponse(serde_json::json!("foobar"));
662                 match TryInto::<Transaction>::try_into(response) {
663                         Err(e) => {
664                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
665                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
666                         }
667                         Ok(_) => panic!("Expected error"),
668                 }
669         }
670
671         #[test]
672         fn into_tx_from_json_response_with_invalid_data_type() {
673                 let response = JsonResponse(Value::Number(Number::from_f64(1.0).unwrap()));
674                 match TryInto::<Transaction>::try_into(response) {
675                         Err(e) => {
676                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
677                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
678                         }
679                         Ok(_) => panic!("Expected error"),
680                 }
681         }
682
683         #[test]
684         fn into_tx_from_json_response_with_invalid_tx_data() {
685                 let response = JsonResponse(serde_json::json!("abcd"));
686                 match TryInto::<Transaction>::try_into(response) {
687                         Err(e) => {
688                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
689                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid transaction");
690                         }
691                         Ok(_) => panic!("Expected error"),
692                 }
693         }
694
695         #[test]
696         fn into_tx_from_json_response_with_valid_tx_data_plain() {
697                 let genesis_block = genesis_block(Network::Bitcoin);
698                 let target_tx = genesis_block.txdata.get(0).unwrap();
699                 let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_tx)));
700                 match TryInto::<Transaction>::try_into(response) {
701                         Err(e) => panic!("Unexpected error: {:?}", e),
702                         Ok(tx) => assert_eq!(&tx, target_tx),
703                 }
704         }
705
706         #[test]
707         fn into_tx_from_json_response_with_valid_tx_data_hex_field() {
708                 let genesis_block = genesis_block(Network::Bitcoin);
709                 let target_tx = genesis_block.txdata.get(0).unwrap();
710                 let response = JsonResponse(serde_json::json!({"hex": encode::serialize_hex(&target_tx)}));
711                 match TryInto::<Transaction>::try_into(response) {
712                         Err(e) => panic!("Unexpected error: {:?}", e),
713                         Ok(tx) => assert_eq!(&tx, target_tx),
714                 }
715         }
716
717         // transaction in hex field of JSON object
718
719         #[test]
720         fn into_tx_from_json_response_with_no_hex_field() {
721                 let response = JsonResponse(serde_json::json!({ "error": "foo" }));
722                 match TryInto::<Transaction>::try_into(response) {
723                         Err(e) => {
724                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
725                                 assert_eq!(
726                                         e.get_ref().unwrap().to_string(),
727                                         "expected JSON string"
728                                 );
729                         }
730                         Ok(_) => panic!("Expected error"),
731                 }
732         }
733
734         #[test]
735         fn into_tx_from_json_response_not_signed() {
736                 let response = JsonResponse(serde_json::json!({ "hex": "foo", "complete": false }));
737                 match TryInto::<Transaction>::try_into(response) {
738                         Err(e) => {
739                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
740                                 assert!(
741                                         e.get_ref().unwrap().to_string().contains(
742                                         "transaction couldn't be signed")
743                                 );
744                         }
745                         Ok(_) => panic!("Expected error"),
746                 }
747         }
748 }