lightning_block_sync/
convert.rs

1use crate::http::{BinaryResponse, JsonResponse};
2use crate::utils::hex_to_work;
3use crate::{BlockHeaderData, BlockSourceError};
4
5use bitcoin::block::{Block, Header};
6use bitcoin::consensus::encode;
7use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid};
8use bitcoin::hex::FromHex;
9use bitcoin::Transaction;
10
11use serde_json;
12
13use bitcoin::hashes::Hash;
14use std::convert::From;
15use std::convert::TryFrom;
16use std::convert::TryInto;
17use std::io;
18use std::str::FromStr;
19
20impl TryInto<serde_json::Value> for JsonResponse {
21	type Error = io::Error;
22	fn try_into(self) -> Result<serde_json::Value, io::Error> {
23		Ok(self.0)
24	}
25}
26
27/// Conversion from `io::Error` into `BlockSourceError`.
28impl From<io::Error> for BlockSourceError {
29	fn from(e: io::Error) -> BlockSourceError {
30		match e.kind() {
31			io::ErrorKind::InvalidData => BlockSourceError::persistent(e),
32			io::ErrorKind::InvalidInput => BlockSourceError::persistent(e),
33			_ => BlockSourceError::transient(e),
34		}
35	}
36}
37
38/// Parses binary data as a block.
39impl TryInto<Block> for BinaryResponse {
40	type Error = io::Error;
41
42	fn try_into(self) -> io::Result<Block> {
43		match encode::deserialize(&self.0) {
44			Err(_) => return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid block data")),
45			Ok(block) => Ok(block),
46		}
47	}
48}
49
50/// Parses binary data as a block hash.
51impl TryInto<BlockHash> for BinaryResponse {
52	type Error = io::Error;
53
54	fn try_into(self) -> io::Result<BlockHash> {
55		BlockHash::from_slice(&self.0)
56			.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "bad block hash length"))
57	}
58}
59
60/// Converts a JSON value into block header data. The JSON value may be an object representing a
61/// block header or an array of such objects. In the latter case, the first object is converted.
62impl TryInto<BlockHeaderData> for JsonResponse {
63	type Error = io::Error;
64
65	fn try_into(self) -> io::Result<BlockHeaderData> {
66		let header = match self.0 {
67			serde_json::Value::Array(mut array) if !array.is_empty() => {
68				array.drain(..).next().unwrap()
69			},
70			serde_json::Value::Object(_) => self.0,
71			_ => return Err(io::Error::new(io::ErrorKind::InvalidData, "unexpected JSON type")),
72		};
73
74		if !header.is_object() {
75			return Err(io::Error::new(io::ErrorKind::InvalidData, "expected JSON object"));
76		}
77
78		// Add an empty previousblockhash for the genesis block.
79		match header.try_into() {
80			Err(_) => Err(io::Error::new(io::ErrorKind::InvalidData, "invalid header data")),
81			Ok(header) => Ok(header),
82		}
83	}
84}
85
86impl TryFrom<serde_json::Value> for BlockHeaderData {
87	type Error = ();
88
89	fn try_from(response: serde_json::Value) -> Result<Self, ()> {
90		macro_rules! get_field {
91			($name: expr, $ty_access: tt) => {
92				response.get($name).ok_or(())?.$ty_access().ok_or(())?
93			};
94		}
95
96		Ok(BlockHeaderData {
97			header: Header {
98				version: bitcoin::block::Version::from_consensus(
99					get_field!("version", as_i64).try_into().map_err(|_| ())?,
100				),
101				prev_blockhash: if let Some(hash_str) = response.get("previousblockhash") {
102					BlockHash::from_str(hash_str.as_str().ok_or(())?).map_err(|_| ())?
103				} else {
104					BlockHash::all_zeros()
105				},
106				merkle_root: TxMerkleNode::from_str(get_field!("merkleroot", as_str))
107					.map_err(|_| ())?,
108				time: get_field!("time", as_u64).try_into().map_err(|_| ())?,
109				bits: bitcoin::CompactTarget::from_consensus(u32::from_be_bytes(
110					<[u8; 4]>::from_hex(get_field!("bits", as_str)).map_err(|_| ())?,
111				)),
112				nonce: get_field!("nonce", as_u64).try_into().map_err(|_| ())?,
113			},
114			chainwork: hex_to_work(get_field!("chainwork", as_str)).map_err(|_| ())?,
115			height: get_field!("height", as_u64).try_into().map_err(|_| ())?,
116		})
117	}
118}
119
120/// Converts a JSON value into a block. Assumes the block is hex-encoded in a JSON string.
121impl TryInto<Block> for JsonResponse {
122	type Error = io::Error;
123
124	fn try_into(self) -> io::Result<Block> {
125		match self.0.as_str() {
126			None => Err(io::Error::new(io::ErrorKind::InvalidData, "expected JSON string")),
127			Some(hex_data) => match Vec::<u8>::from_hex(hex_data) {
128				Err(_) => Err(io::Error::new(io::ErrorKind::InvalidData, "invalid hex data")),
129				Ok(block_data) => match encode::deserialize(&block_data) {
130					Err(_) => Err(io::Error::new(io::ErrorKind::InvalidData, "invalid block data")),
131					Ok(block) => Ok(block),
132				},
133			},
134		}
135	}
136}
137
138/// Converts a JSON value into the best block hash and optional height.
139impl TryInto<(BlockHash, Option<u32>)> for JsonResponse {
140	type Error = io::Error;
141
142	fn try_into(self) -> io::Result<(BlockHash, Option<u32>)> {
143		if !self.0.is_object() {
144			return Err(io::Error::new(io::ErrorKind::InvalidData, "expected JSON object"));
145		}
146
147		let hash = match &self.0["bestblockhash"] {
148			serde_json::Value::String(hex_data) => match BlockHash::from_str(&hex_data) {
149				Err(_) => {
150					return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid hex data"))
151				},
152				Ok(block_hash) => block_hash,
153			},
154			_ => return Err(io::Error::new(io::ErrorKind::InvalidData, "expected JSON string")),
155		};
156
157		let height = match &self.0["blocks"] {
158			serde_json::Value::Null => None,
159			serde_json::Value::Number(height) => match height.as_u64() {
160				None => return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid height")),
161				Some(height) => match height.try_into() {
162					Err(_) => {
163						return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid height"))
164					},
165					Ok(height) => Some(height),
166				},
167			},
168			_ => return Err(io::Error::new(io::ErrorKind::InvalidData, "expected JSON number")),
169		};
170
171		Ok((hash, height))
172	}
173}
174
175impl TryInto<Txid> for JsonResponse {
176	type Error = io::Error;
177	fn try_into(self) -> io::Result<Txid> {
178		let hex_data = self
179			.0
180			.as_str()
181			.ok_or(io::Error::new(io::ErrorKind::InvalidData, "expected JSON string"))?;
182		Txid::from_str(hex_data)
183			.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))
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>
189impl TryInto<Transaction> for JsonResponse {
190	type Error = io::Error;
191	fn try_into(self) -> 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(io::Error::new(
206								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				_ => {
217					return Err(io::Error::new(io::ErrorKind::InvalidData, "expected JSON string"));
218				},
219			}
220		} else {
221			// result is plain text (e.g. getrawtransaction no verbose)
222			match self.0.as_str() {
223				Some(hex_tx) => hex_tx,
224				None => {
225					return Err(io::Error::new(io::ErrorKind::InvalidData, "expected JSON string"));
226				},
227			}
228		};
229
230		match Vec::<u8>::from_hex(hex_tx) {
231			Err(_) => Err(io::Error::new(io::ErrorKind::InvalidData, "invalid hex data")),
232			Ok(tx_data) => match encode::deserialize(&tx_data) {
233				Err(_) => Err(io::Error::new(io::ErrorKind::InvalidData, "invalid transaction")),
234				Ok(tx) => Ok(tx),
235			},
236		}
237	}
238}
239
240impl TryInto<BlockHash> for JsonResponse {
241	type Error = io::Error;
242
243	fn try_into(self) -> io::Result<BlockHash> {
244		match self.0.as_str() {
245			None => Err(io::Error::new(io::ErrorKind::InvalidData, "expected JSON string")),
246			Some(hex_data) if hex_data.len() != 64 => {
247				Err(io::Error::new(io::ErrorKind::InvalidData, "invalid hash length"))
248			},
249			Some(hex_data) => BlockHash::from_str(hex_data)
250				.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid hex data")),
251		}
252	}
253}
254
255/// The REST `getutxos` endpoint retuns a whole pile of data we don't care about and one bit we do
256/// - whether the `hit bitmap` field had any entries. Thus we condense the result down into only
257/// that.
258#[cfg(feature = "rest-client")]
259pub(crate) struct GetUtxosResponse {
260	pub(crate) hit_bitmap_nonempty: bool,
261}
262
263#[cfg(feature = "rest-client")]
264impl TryInto<GetUtxosResponse> for JsonResponse {
265	type Error = io::Error;
266
267	fn try_into(self) -> io::Result<GetUtxosResponse> {
268		let obj_err = || io::Error::new(io::ErrorKind::InvalidData, "expected an object");
269		let bitmap_err = || io::Error::new(io::ErrorKind::InvalidData, "missing bitmap field");
270		let bitstr_err = || io::Error::new(io::ErrorKind::InvalidData, "bitmap should be an str");
271		let bitmap_str = self
272			.0
273			.as_object()
274			.ok_or_else(obj_err)?
275			.get("bitmap")
276			.ok_or_else(bitmap_err)?
277			.as_str()
278			.ok_or_else(bitstr_err)?;
279		let mut hit_bitmap_nonempty = false;
280		for c in bitmap_str.chars() {
281			if c < '0' || c > '9' {
282				return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid byte"));
283			}
284			if c > '0' {
285				hit_bitmap_nonempty = true;
286			}
287		}
288		Ok(GetUtxosResponse { hit_bitmap_nonempty })
289	}
290}
291
292#[cfg(test)]
293pub(crate) mod tests {
294	use super::*;
295	use bitcoin::constants::genesis_block;
296	use bitcoin::hashes::Hash;
297	use bitcoin::hex::DisplayHex;
298	use bitcoin::network::Network;
299	use serde_json::value::Number;
300	use serde_json::Value;
301
302	/// Converts from `BlockHeaderData` into a `GetHeaderResponse` JSON value.
303	impl From<BlockHeaderData> for serde_json::Value {
304		fn from(data: BlockHeaderData) -> Self {
305			let BlockHeaderData { chainwork, height, header } = data;
306			serde_json::json!({
307				"chainwork": chainwork.to_be_bytes().as_hex().to_string(),
308				"height": height,
309				"version": header.version.to_consensus(),
310				"merkleroot": header.merkle_root.to_string(),
311				"time": header.time,
312				"nonce": header.nonce,
313				"bits": header.bits.to_consensus().to_be_bytes().as_hex().to_string(),
314				"previousblockhash": header.prev_blockhash.to_string(),
315			})
316		}
317	}
318
319	#[test]
320	fn into_block_header_from_json_response_with_unexpected_type() {
321		let response = JsonResponse(serde_json::json!(42));
322		match TryInto::<BlockHeaderData>::try_into(response) {
323			Err(e) => {
324				assert_eq!(e.kind(), io::ErrorKind::InvalidData);
325				assert_eq!(e.get_ref().unwrap().to_string(), "unexpected JSON type");
326			},
327			Ok(_) => panic!("Expected error"),
328		}
329	}
330
331	#[test]
332	fn into_block_header_from_json_response_with_unexpected_header_type() {
333		let response = JsonResponse(serde_json::json!([42]));
334		match TryInto::<BlockHeaderData>::try_into(response) {
335			Err(e) => {
336				assert_eq!(e.kind(), io::ErrorKind::InvalidData);
337				assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
338			},
339			Ok(_) => panic!("Expected error"),
340		}
341	}
342
343	#[test]
344	fn into_block_header_from_json_response_with_invalid_header_response() {
345		let block = genesis_block(Network::Bitcoin);
346		let mut response = JsonResponse(
347			BlockHeaderData { chainwork: block.header.work(), height: 0, header: block.header }
348				.into(),
349		);
350		response.0["chainwork"].take();
351
352		match TryInto::<BlockHeaderData>::try_into(response) {
353			Err(e) => {
354				assert_eq!(e.kind(), io::ErrorKind::InvalidData);
355				assert_eq!(e.get_ref().unwrap().to_string(), "invalid header data");
356			},
357			Ok(_) => panic!("Expected error"),
358		}
359	}
360
361	#[test]
362	fn into_block_header_from_json_response_with_invalid_header_data() {
363		let block = genesis_block(Network::Bitcoin);
364		let mut response = JsonResponse(
365			BlockHeaderData { chainwork: block.header.work(), height: 0, header: block.header }
366				.into(),
367		);
368		response.0["chainwork"] = serde_json::json!("foobar");
369
370		match TryInto::<BlockHeaderData>::try_into(response) {
371			Err(e) => {
372				assert_eq!(e.kind(), 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(
383			BlockHeaderData { chainwork: block.header.work(), height: 0, header: block.header }
384				.into(),
385		);
386
387		match TryInto::<BlockHeaderData>::try_into(response) {
388			Err(e) => panic!("Unexpected error: {:?}", e),
389			Ok(data) => {
390				assert_eq!(data.chainwork, block.header.work());
391				assert_eq!(data.height, 0);
392				assert_eq!(data.header, block.header);
393			},
394		}
395	}
396
397	#[test]
398	fn into_block_header_from_json_response_with_valid_header_array() {
399		let genesis_block = genesis_block(Network::Bitcoin);
400		let best_block_header =
401			Header { prev_blockhash: genesis_block.block_hash(), ..genesis_block.header };
402		let chainwork = genesis_block.header.work() + best_block_header.work();
403		let response = JsonResponse(serde_json::json!([
404			serde_json::Value::from(BlockHeaderData {
405				chainwork,
406				height: 1,
407				header: best_block_header,
408			}),
409			serde_json::Value::from(BlockHeaderData {
410				chainwork: genesis_block.header.work(),
411				height: 0,
412				header: genesis_block.header,
413			}),
414		]));
415
416		match TryInto::<BlockHeaderData>::try_into(response) {
417			Err(e) => panic!("Unexpected error: {:?}", e),
418			Ok(data) => {
419				assert_eq!(data.chainwork, chainwork);
420				assert_eq!(data.height, 1);
421				assert_eq!(data.header, best_block_header);
422			},
423		}
424	}
425
426	#[test]
427	fn into_block_header_from_json_response_without_previous_block_hash() {
428		let block = genesis_block(Network::Bitcoin);
429		let mut response = JsonResponse(
430			BlockHeaderData { chainwork: block.header.work(), height: 0, header: block.header }
431				.into(),
432		);
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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), io::ErrorKind::InvalidData);
625				assert_eq!(e.get_ref().unwrap().to_string(), "failed to parse hex");
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(), io::ErrorKind::InvalidData);
637				assert_eq!(e.get_ref().unwrap().to_string(), "failed to parse hex");
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	#[test]
654	fn into_txid_from_bitcoind_rpc_json_response() {
655		let mut rpc_response = serde_json::json!(
656			{"error": "", "id": "770", "result": "7934f775149929a8b742487129a7c3a535dfb612f0b726cc67bc10bc2628f906"}
657
658		);
659		let r: io::Result<Txid> =
660			JsonResponse(rpc_response.get_mut("result").unwrap().take()).try_into();
661		assert_eq!(
662			r.unwrap().to_string(),
663			"7934f775149929a8b742487129a7c3a535dfb612f0b726cc67bc10bc2628f906"
664		);
665	}
666
667	// TryInto<Transaction> can be used in two ways, first with plain hex response where data is
668	// the hex encoded transaction (e.g. as a result of getrawtransaction) or as a JSON object
669	// where the hex encoded transaction can be found in the hex field of the object (if present)
670	// (e.g. as a result of signrawtransactionwithwallet).
671
672	// plain hex transaction
673
674	#[test]
675	fn into_tx_from_json_response_with_invalid_hex_data() {
676		let response = JsonResponse(serde_json::json!("foobar"));
677		match TryInto::<Transaction>::try_into(response) {
678			Err(e) => {
679				assert_eq!(e.kind(), io::ErrorKind::InvalidData);
680				assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
681			},
682			Ok(_) => panic!("Expected error"),
683		}
684	}
685
686	#[test]
687	fn into_tx_from_json_response_with_invalid_data_type() {
688		let response = JsonResponse(Value::Number(Number::from_f64(1.0).unwrap()));
689		match TryInto::<Transaction>::try_into(response) {
690			Err(e) => {
691				assert_eq!(e.kind(), io::ErrorKind::InvalidData);
692				assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
693			},
694			Ok(_) => panic!("Expected error"),
695		}
696	}
697
698	#[test]
699	fn into_tx_from_json_response_with_invalid_tx_data() {
700		let response = JsonResponse(serde_json::json!("abcd"));
701		match TryInto::<Transaction>::try_into(response) {
702			Err(e) => {
703				assert_eq!(e.kind(), io::ErrorKind::InvalidData);
704				assert_eq!(e.get_ref().unwrap().to_string(), "invalid transaction");
705			},
706			Ok(_) => panic!("Expected error"),
707		}
708	}
709
710	#[test]
711	fn into_tx_from_json_response_with_valid_tx_data_plain() {
712		let genesis_block = genesis_block(Network::Bitcoin);
713		let target_tx = genesis_block.txdata.get(0).unwrap();
714		let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_tx)));
715		match TryInto::<Transaction>::try_into(response) {
716			Err(e) => panic!("Unexpected error: {:?}", e),
717			Ok(tx) => assert_eq!(&tx, target_tx),
718		}
719	}
720
721	#[test]
722	fn into_tx_from_json_response_with_valid_tx_data_hex_field() {
723		let genesis_block = genesis_block(Network::Bitcoin);
724		let target_tx = genesis_block.txdata.get(0).unwrap();
725		let response =
726			JsonResponse(serde_json::json!({ "hex": encode::serialize_hex(&target_tx) }));
727		match TryInto::<Transaction>::try_into(response) {
728			Err(e) => panic!("Unexpected error: {:?}", e),
729			Ok(tx) => assert_eq!(&tx, target_tx),
730		}
731	}
732
733	// transaction in hex field of JSON object
734
735	#[test]
736	fn into_tx_from_json_response_with_no_hex_field() {
737		let response = JsonResponse(serde_json::json!({ "error": "foo" }));
738		match TryInto::<Transaction>::try_into(response) {
739			Err(e) => {
740				assert_eq!(e.kind(), io::ErrorKind::InvalidData);
741				assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
742			},
743			Ok(_) => panic!("Expected error"),
744		}
745	}
746
747	#[test]
748	fn into_tx_from_json_response_not_signed() {
749		let response = JsonResponse(serde_json::json!({ "hex": "foo", "complete": false }));
750		match TryInto::<Transaction>::try_into(response) {
751			Err(e) => {
752				assert_eq!(e.kind(), io::ErrorKind::InvalidData);
753				assert!(e
754					.get_ref()
755					.unwrap()
756					.to_string()
757					.contains("transaction couldn't be signed"));
758			},
759			Ok(_) => panic!("Expected error"),
760		}
761	}
762}