Skip to main content

lightning_block_sync/
convert.rs

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