0f9ab8c43baadcc8cb72eec2375a063e9c4ffb37
[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                 match self.0.as_str() {
166                         None => Err(std::io::Error::new(
167                                 std::io::ErrorKind::InvalidData,
168                                 "expected JSON string",
169                         )),
170                         Some(hex_data) => match Vec::<u8>::from_hex(hex_data) {
171                                 Err(_) => Err(std::io::Error::new(
172                                         std::io::ErrorKind::InvalidData,
173                                         "invalid hex data",
174                                 )),
175                                 Ok(txid_data) => match encode::deserialize(&txid_data) {
176                                         Err(_) => Err(std::io::Error::new(
177                                                 std::io::ErrorKind::InvalidData,
178                                                 "invalid txid",
179                                         )),
180                                         Ok(txid) => Ok(txid),
181                                 },
182                         },
183                 }
184         }
185 }
186
187 /// Converts a JSON value into a transaction. WATCH OUT! this cannot be used for zero-input transactions
188 /// (e.g. createrawtransaction). See <https://github.com/rust-bitcoin/rust-bitcoincore-rpc/issues/197>
189 impl TryInto<Transaction> for JsonResponse {
190         type Error = std::io::Error;
191         fn try_into(self) -> std::io::Result<Transaction> {
192                 let hex_tx = if self.0.is_object() {
193                         // result is json encoded
194                         match &self.0["hex"] {
195                                 // result has hex field
196                                 serde_json::Value::String(hex_data) => match self.0["complete"] {
197                                         // result may or may not be signed (e.g. signrawtransactionwithwallet)
198                                         serde_json::Value::Bool(x) => {
199                                                 if x == false {
200                                                         let reason = match &self.0["errors"][0]["error"] {
201                                                                 serde_json::Value::String(x) => x.as_str(),
202                                                                 _ => "Unknown error",
203                                                         };
204
205                                                         return Err(std::io::Error::new(
206                                                                 std::io::ErrorKind::InvalidData,
207                                                                 format!("transaction couldn't be signed. {}", reason),
208                                                         ));
209                                                 } else {
210                                                         hex_data
211                                                 }
212                                         }
213                                         // result is a complete transaction (e.g. getrawtranaction verbose)
214                                         _ => hex_data,
215                                 },
216                                 _ => return Err(std::io::Error::new(
217                                                         std::io::ErrorKind::InvalidData,
218                                                         "expected JSON string",
219                                         )),
220                         }
221                 } else {
222                         // result is plain text (e.g. getrawtransaction no verbose)
223                         match self.0.as_str() {
224                                 Some(hex_tx) => hex_tx,
225                                 None => {
226                                         return Err(std::io::Error::new(
227                                                 std::io::ErrorKind::InvalidData,
228                                                 "expected JSON string",
229                                         ))
230                                 }
231                         }
232                 };
233
234                 match Vec::<u8>::from_hex(hex_tx) {
235                         Err(_) => Err(std::io::Error::new(
236                                 std::io::ErrorKind::InvalidData,
237                                 "invalid hex data",
238                         )),
239                         Ok(tx_data) => match encode::deserialize(&tx_data) {
240                                 Err(_) => Err(std::io::Error::new(
241                                         std::io::ErrorKind::InvalidData,
242                                         "invalid transaction",
243                                 )),
244                                 Ok(tx) => Ok(tx),
245                         },
246                 }
247         }
248 }
249
250 impl TryInto<BlockHash> for JsonResponse {
251         type Error = std::io::Error;
252
253         fn try_into(self) -> std::io::Result<BlockHash> {
254                 match self.0.as_str() {
255                         None => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string")),
256                         Some(hex_data) if hex_data.len() != 64 =>
257                                 Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hash length")),
258                         Some(hex_data) => BlockHash::from_str(hex_data)
259                                 .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")),
260                 }
261         }
262 }
263
264 /// The REST `getutxos` endpoint retuns a whole pile of data we don't care about and one bit we do
265 /// - whether the `hit bitmap` field had any entries. Thus we condense the result down into only
266 /// that.
267 pub(crate) struct GetUtxosResponse {
268         pub(crate) hit_bitmap_nonempty: bool
269 }
270
271 impl TryInto<GetUtxosResponse> for JsonResponse {
272         type Error = std::io::Error;
273
274         fn try_into(self) -> std::io::Result<GetUtxosResponse> {
275                 let bitmap_str =
276                         self.0.as_object().ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected an object"))?
277                         .get("bitmap").ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "missing bitmap field"))?
278                         .as_str().ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "bitmap should be an str"))?;
279                         let mut hit_bitmap_nonempty = false;
280                         for c in bitmap_str.chars() {
281                                 if c < '0' || c > '9' {
282                                         return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid byte"));
283                                 }
284                                 if c > '0' { hit_bitmap_nonempty = true; }
285                         }
286                         Ok(GetUtxosResponse { hit_bitmap_nonempty })
287         }
288 }
289
290 #[cfg(test)]
291 pub(crate) mod tests {
292         use super::*;
293         use bitcoin::blockdata::constants::genesis_block;
294         use bitcoin::hashes::Hash;
295         use bitcoin::network::constants::Network;
296         use hex::DisplayHex;
297         use serde_json::value::Number;
298         use serde_json::Value;
299
300         /// Converts from `BlockHeaderData` into a `GetHeaderResponse` JSON value.
301         impl From<BlockHeaderData> for serde_json::Value {
302                 fn from(data: BlockHeaderData) -> Self {
303                         let BlockHeaderData { chainwork, height, header } = data;
304                         serde_json::json!({
305                                 "chainwork": chainwork.to_be_bytes().as_hex().to_string(),
306                                 "height": height,
307                                 "version": header.version.to_consensus(),
308                                 "merkleroot": header.merkle_root.to_string(),
309                                 "time": header.time,
310                                 "nonce": header.nonce,
311                                 "bits": header.bits.to_consensus().to_be_bytes().as_hex().to_string(),
312                                 "previousblockhash": header.prev_blockhash.to_string(),
313                         })
314                 }
315         }
316
317         #[test]
318         fn into_block_header_from_json_response_with_unexpected_type() {
319                 let response = JsonResponse(serde_json::json!(42));
320                 match TryInto::<BlockHeaderData>::try_into(response) {
321                         Err(e) => {
322                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
323                                 assert_eq!(e.get_ref().unwrap().to_string(), "unexpected JSON type");
324                         },
325                         Ok(_) => panic!("Expected error"),
326                 }
327         }
328
329         #[test]
330         fn into_block_header_from_json_response_with_unexpected_header_type() {
331                 let response = JsonResponse(serde_json::json!([42]));
332                 match TryInto::<BlockHeaderData>::try_into(response) {
333                         Err(e) => {
334                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
335                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
336                         },
337                         Ok(_) => panic!("Expected error"),
338                 }
339         }
340
341         #[test]
342         fn into_block_header_from_json_response_with_invalid_header_response() {
343                 let block = genesis_block(Network::Bitcoin);
344                 let mut response = JsonResponse(BlockHeaderData {
345                         chainwork: block.header.work(),
346                         height: 0,
347                         header: block.header
348                 }.into());
349                 response.0["chainwork"].take();
350
351                 match TryInto::<BlockHeaderData>::try_into(response) {
352                         Err(e) => {
353                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
354                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid header data");
355                         },
356                         Ok(_) => panic!("Expected error"),
357                 }
358         }
359
360         #[test]
361         fn into_block_header_from_json_response_with_invalid_header_data() {
362                 let block = genesis_block(Network::Bitcoin);
363                 let mut response = JsonResponse(BlockHeaderData {
364                         chainwork: block.header.work(),
365                         height: 0,
366                         header: block.header
367                 }.into());
368                 response.0["chainwork"] = serde_json::json!("foobar");
369
370                 match TryInto::<BlockHeaderData>::try_into(response) {
371                         Err(e) => {
372                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
373                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid header data");
374                         },
375                         Ok(_) => panic!("Expected error"),
376                 }
377         }
378
379         #[test]
380         fn into_block_header_from_json_response_with_valid_header() {
381                 let block = genesis_block(Network::Bitcoin);
382                 let response = JsonResponse(BlockHeaderData {
383                         chainwork: block.header.work(),
384                         height: 0,
385                         header: block.header
386                 }.into());
387
388                 match TryInto::<BlockHeaderData>::try_into(response) {
389                         Err(e) => panic!("Unexpected error: {:?}", e),
390                         Ok(data) => {
391                                 assert_eq!(data.chainwork, block.header.work());
392                                 assert_eq!(data.height, 0);
393                                 assert_eq!(data.header, block.header);
394                         },
395                 }
396         }
397
398         #[test]
399         fn into_block_header_from_json_response_with_valid_header_array() {
400                 let genesis_block = genesis_block(Network::Bitcoin);
401                 let best_block_header = Header {
402                         prev_blockhash: genesis_block.block_hash(),
403                         ..genesis_block.header
404                 };
405                 let chainwork = genesis_block.header.work() + best_block_header.work();
406                 let response = JsonResponse(serde_json::json!([
407                                 serde_json::Value::from(BlockHeaderData {
408                                         chainwork, height: 1, header: best_block_header,
409                                 }),
410                                 serde_json::Value::from(BlockHeaderData {
411                                         chainwork: genesis_block.header.work(), height: 0, header: genesis_block.header,
412                                 }),
413                 ]));
414
415                 match TryInto::<BlockHeaderData>::try_into(response) {
416                         Err(e) => panic!("Unexpected error: {:?}", e),
417                         Ok(data) => {
418                                 assert_eq!(data.chainwork, chainwork);
419                                 assert_eq!(data.height, 1);
420                                 assert_eq!(data.header, best_block_header);
421                         },
422                 }
423         }
424
425         #[test]
426         fn into_block_header_from_json_response_without_previous_block_hash() {
427                 let block = genesis_block(Network::Bitcoin);
428                 let mut response = JsonResponse(BlockHeaderData {
429                         chainwork: block.header.work(),
430                         height: 0,
431                         header: block.header
432                 }.into());
433                 response.0.as_object_mut().unwrap().remove("previousblockhash");
434
435                 match TryInto::<BlockHeaderData>::try_into(response) {
436                         Err(e) => panic!("Unexpected error: {:?}", e),
437                         Ok(BlockHeaderData { chainwork: _, height: _, header }) => {
438                                 assert_eq!(header, block.header);
439                         },
440                 }
441         }
442
443         #[test]
444         fn into_block_from_invalid_binary_response() {
445                 let response = BinaryResponse(b"foo".to_vec());
446                 match TryInto::<Block>::try_into(response) {
447                         Err(_) => {},
448                         Ok(_) => panic!("Expected error"),
449                 }
450         }
451
452         #[test]
453         fn into_block_from_valid_binary_response() {
454                 let genesis_block = genesis_block(Network::Bitcoin);
455                 let response = BinaryResponse(encode::serialize(&genesis_block));
456                 match TryInto::<Block>::try_into(response) {
457                         Err(e) => panic!("Unexpected error: {:?}", e),
458                         Ok(block) => assert_eq!(block, genesis_block),
459                 }
460         }
461
462         #[test]
463         fn into_block_from_json_response_with_unexpected_type() {
464                 let response = JsonResponse(serde_json::json!({ "result": "foo" }));
465                 match TryInto::<Block>::try_into(response) {
466                         Err(e) => {
467                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
468                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
469                         },
470                         Ok(_) => panic!("Expected error"),
471                 }
472         }
473
474         #[test]
475         fn into_block_from_json_response_with_invalid_hex_data() {
476                 let response = JsonResponse(serde_json::json!("foobar"));
477                 match TryInto::<Block>::try_into(response) {
478                         Err(e) => {
479                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
480                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
481                         },
482                         Ok(_) => panic!("Expected error"),
483                 }
484         }
485
486         #[test]
487         fn into_block_from_json_response_with_invalid_block_data() {
488                 let response = JsonResponse(serde_json::json!("abcd"));
489                 match TryInto::<Block>::try_into(response) {
490                         Err(e) => {
491                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
492                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid block data");
493                         },
494                         Ok(_) => panic!("Expected error"),
495                 }
496         }
497
498         #[test]
499         fn into_block_from_json_response_with_valid_block_data() {
500                 let genesis_block = genesis_block(Network::Bitcoin);
501                 let response = JsonResponse(serde_json::json!(encode::serialize_hex(&genesis_block)));
502                 match TryInto::<Block>::try_into(response) {
503                         Err(e) => panic!("Unexpected error: {:?}", e),
504                         Ok(block) => assert_eq!(block, genesis_block),
505                 }
506         }
507
508         #[test]
509         fn into_block_hash_from_json_response_with_unexpected_type() {
510                 let response = JsonResponse(serde_json::json!("foo"));
511                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
512                         Err(e) => {
513                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
514                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
515                         },
516                         Ok(_) => panic!("Expected error"),
517                 }
518         }
519
520         #[test]
521         fn into_block_hash_from_json_response_with_unexpected_bestblockhash_type() {
522                 let response = JsonResponse(serde_json::json!({ "bestblockhash": 42 }));
523                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
524                         Err(e) => {
525                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
526                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
527                         },
528                         Ok(_) => panic!("Expected error"),
529                 }
530         }
531
532         #[test]
533         fn into_block_hash_from_json_response_with_invalid_hex_data() {
534                 let response = JsonResponse(serde_json::json!({ "bestblockhash": "foobar"} ));
535                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
536                         Err(e) => {
537                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
538                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
539                         },
540                         Ok(_) => panic!("Expected error"),
541                 }
542         }
543
544         #[test]
545         fn into_block_hash_from_json_response_without_height() {
546                 let block = genesis_block(Network::Bitcoin);
547                 let response = JsonResponse(serde_json::json!({
548                         "bestblockhash": block.block_hash().to_string(),
549                 }));
550                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
551                         Err(e) => panic!("Unexpected error: {:?}", e),
552                         Ok((hash, height)) => {
553                                 assert_eq!(hash, block.block_hash());
554                                 assert!(height.is_none());
555                         },
556                 }
557         }
558
559         #[test]
560         fn into_block_hash_from_json_response_with_unexpected_blocks_type() {
561                 let block = genesis_block(Network::Bitcoin);
562                 let response = JsonResponse(serde_json::json!({
563                         "bestblockhash": block.block_hash().to_string(),
564                         "blocks": "foo",
565                 }));
566                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
567                         Err(e) => {
568                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
569                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON number");
570                         },
571                         Ok(_) => panic!("Expected error"),
572                 }
573         }
574
575         #[test]
576         fn into_block_hash_from_json_response_with_invalid_height() {
577                 let block = genesis_block(Network::Bitcoin);
578                 let response = JsonResponse(serde_json::json!({
579                         "bestblockhash": block.block_hash().to_string(),
580                         "blocks": std::u64::MAX,
581                 }));
582                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
583                         Err(e) => {
584                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
585                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid height");
586                         },
587                         Ok(_) => panic!("Expected error"),
588                 }
589         }
590
591         #[test]
592         fn into_block_hash_from_json_response_with_height() {
593                 let block = genesis_block(Network::Bitcoin);
594                 let response = JsonResponse(serde_json::json!({
595                         "bestblockhash": block.block_hash().to_string(),
596                         "blocks": 1,
597                 }));
598                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
599                         Err(e) => panic!("Unexpected error: {:?}", e),
600                         Ok((hash, height)) => {
601                                 assert_eq!(hash, block.block_hash());
602                                 assert_eq!(height.unwrap(), 1);
603                         },
604                 }
605         }
606
607         #[test]
608         fn into_txid_from_json_response_with_unexpected_type() {
609                 let response = JsonResponse(serde_json::json!({ "result": "foo" }));
610                 match TryInto::<Txid>::try_into(response) {
611                         Err(e) => {
612                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
613                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
614                         }
615                         Ok(_) => panic!("Expected error"),
616                 }
617         }
618
619         #[test]
620         fn into_txid_from_json_response_with_invalid_hex_data() {
621                 let response = JsonResponse(serde_json::json!("foobar"));
622                 match TryInto::<Txid>::try_into(response) {
623                         Err(e) => {
624                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
625                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
626                         }
627                         Ok(_) => panic!("Expected error"),
628                 }
629         }
630
631         #[test]
632         fn into_txid_from_json_response_with_invalid_txid_data() {
633                 let response = JsonResponse(serde_json::json!("abcd"));
634                 match TryInto::<Txid>::try_into(response) {
635                         Err(e) => {
636                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
637                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid txid");
638                         }
639                         Ok(_) => panic!("Expected error"),
640                 }
641         }
642
643         #[test]
644         fn into_txid_from_json_response_with_valid_txid_data() {
645                 let target_txid = Txid::from_slice(&[1; 32]).unwrap();
646                 let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_txid)));
647                 match TryInto::<Txid>::try_into(response) {
648                         Err(e) => panic!("Unexpected error: {:?}", e),
649                         Ok(txid) => assert_eq!(txid, target_txid),
650                 }
651         }
652
653         // TryInto<Transaction> can be used in two ways, first with plain hex response where data is
654         // the hex encoded transaction (e.g. as a result of getrawtransaction) or as a JSON object
655         // where the hex encoded transaction can be found in the hex field of the object (if present)
656         // (e.g. as a result of signrawtransactionwithwallet).
657
658         // plain hex transaction
659
660         #[test]
661         fn into_tx_from_json_response_with_invalid_hex_data() {
662                 let response = JsonResponse(serde_json::json!("foobar"));
663                 match TryInto::<Transaction>::try_into(response) {
664                         Err(e) => {
665                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
666                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
667                         }
668                         Ok(_) => panic!("Expected error"),
669                 }
670         }
671
672         #[test]
673         fn into_tx_from_json_response_with_invalid_data_type() {
674                 let response = JsonResponse(Value::Number(Number::from_f64(1.0).unwrap()));
675                 match TryInto::<Transaction>::try_into(response) {
676                         Err(e) => {
677                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
678                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
679                         }
680                         Ok(_) => panic!("Expected error"),
681                 }
682         }
683
684         #[test]
685         fn into_tx_from_json_response_with_invalid_tx_data() {
686                 let response = JsonResponse(serde_json::json!("abcd"));
687                 match TryInto::<Transaction>::try_into(response) {
688                         Err(e) => {
689                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
690                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid transaction");
691                         }
692                         Ok(_) => panic!("Expected error"),
693                 }
694         }
695
696         #[test]
697         fn into_tx_from_json_response_with_valid_tx_data_plain() {
698                 let genesis_block = genesis_block(Network::Bitcoin);
699                 let target_tx = genesis_block.txdata.get(0).unwrap();
700                 let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_tx)));
701                 match TryInto::<Transaction>::try_into(response) {
702                         Err(e) => panic!("Unexpected error: {:?}", e),
703                         Ok(tx) => assert_eq!(&tx, target_tx),
704                 }
705         }
706
707         #[test]
708         fn into_tx_from_json_response_with_valid_tx_data_hex_field() {
709                 let genesis_block = genesis_block(Network::Bitcoin);
710                 let target_tx = genesis_block.txdata.get(0).unwrap();
711                 let response = JsonResponse(serde_json::json!({"hex": encode::serialize_hex(&target_tx)}));
712                 match TryInto::<Transaction>::try_into(response) {
713                         Err(e) => panic!("Unexpected error: {:?}", e),
714                         Ok(tx) => assert_eq!(&tx, target_tx),
715                 }
716         }
717
718         // transaction in hex field of JSON object
719
720         #[test]
721         fn into_tx_from_json_response_with_no_hex_field() {
722                 let response = JsonResponse(serde_json::json!({ "error": "foo" }));
723                 match TryInto::<Transaction>::try_into(response) {
724                         Err(e) => {
725                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
726                                 assert_eq!(
727                                         e.get_ref().unwrap().to_string(),
728                                         "expected JSON string"
729                                 );
730                         }
731                         Ok(_) => panic!("Expected error"),
732                 }
733         }
734
735         #[test]
736         fn into_tx_from_json_response_not_signed() {
737                 let response = JsonResponse(serde_json::json!({ "hex": "foo", "complete": false }));
738                 match TryInto::<Transaction>::try_into(response) {
739                         Err(e) => {
740                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
741                                 assert!(
742                                         e.get_ref().unwrap().to_string().contains(
743                                         "transaction couldn't be signed")
744                                 );
745                         }
746                         Ok(_) => panic!("Expected error"),
747                 }
748         }
749 }