Adds Txid to lighting-block-sync::convert
[rust-lightning] / lightning-block-sync / src / convert.rs
1 use crate::{BlockHeaderData, BlockSourceError};
2 use crate::http::{BinaryResponse, JsonResponse};
3 use crate::utils::hex_to_uint256;
4
5 use bitcoin::blockdata::block::{Block, BlockHeader};
6 use bitcoin::consensus::encode;
7 use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid};
8 use bitcoin::hashes::hex::{ToHex, FromHex};
9
10 use serde::Deserialize;
11
12 use serde_json;
13
14 use std::convert::From;
15 use std::convert::TryFrom;
16 use std::convert::TryInto;
17
18 /// Conversion from `std::io::Error` into `BlockSourceError`.
19 impl From<std::io::Error> for BlockSourceError {
20         fn from(e: std::io::Error) -> BlockSourceError {
21                 match e.kind() {
22                         std::io::ErrorKind::InvalidData => BlockSourceError::persistent(e),
23                         std::io::ErrorKind::InvalidInput => BlockSourceError::persistent(e),
24                         _ => BlockSourceError::transient(e),
25                 }
26         }
27 }
28
29 /// Parses binary data as a block.
30 impl TryInto<Block> for BinaryResponse {
31         type Error = std::io::Error;
32
33         fn try_into(self) -> std::io::Result<Block> {
34                 match encode::deserialize(&self.0) {
35                         Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid block data")),
36                         Ok(block) => Ok(block),
37                 }
38         }
39 }
40
41 /// Converts a JSON value into block header data. The JSON value may be an object representing a
42 /// block header or an array of such objects. In the latter case, the first object is converted.
43 impl TryInto<BlockHeaderData> for JsonResponse {
44         type Error = std::io::Error;
45
46         fn try_into(self) -> std::io::Result<BlockHeaderData> {
47                 let mut header = match self.0 {
48                         serde_json::Value::Array(mut array) if !array.is_empty() => array.drain(..).next().unwrap(),
49                         serde_json::Value::Object(_) => self.0,
50                         _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unexpected JSON type")),
51                 };
52
53                 if !header.is_object() {
54                         return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object"));
55                 }
56
57                 // Add an empty previousblockhash for the genesis block.
58                 if let None = header.get("previousblockhash") {
59                         let hash: BlockHash = Default::default();
60                         header.as_object_mut().unwrap().insert("previousblockhash".to_string(), serde_json::json!(hash.to_hex()));
61                 }
62
63                 match serde_json::from_value::<GetHeaderResponse>(header) {
64                         Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid header response")),
65                         Ok(response) => match response.try_into() {
66                                 Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid header data")),
67                                 Ok(header) => Ok(header),
68                         },
69                 }
70         }
71 }
72
73 /// Response data from `getblockheader` RPC and `headers` REST requests.
74 #[derive(Deserialize)]
75 struct GetHeaderResponse {
76         pub version: i32,
77         pub merkleroot: String,
78         pub time: u32,
79         pub nonce: u32,
80         pub bits: String,
81         pub previousblockhash: String,
82
83         pub chainwork: String,
84         pub height: u32,
85 }
86
87 /// Converts from `GetHeaderResponse` to `BlockHeaderData`.
88 impl TryFrom<GetHeaderResponse> for BlockHeaderData {
89         type Error = bitcoin::hashes::hex::Error;
90
91         fn try_from(response: GetHeaderResponse) -> Result<Self, bitcoin::hashes::hex::Error> {
92                 Ok(BlockHeaderData {
93                         header: BlockHeader {
94                                 version: response.version,
95                                 prev_blockhash: BlockHash::from_hex(&response.previousblockhash)?,
96                                 merkle_root: TxMerkleNode::from_hex(&response.merkleroot)?,
97                                 time: response.time,
98                                 bits: u32::from_be_bytes(<[u8; 4]>::from_hex(&response.bits)?),
99                                 nonce: response.nonce,
100                         },
101                         chainwork: hex_to_uint256(&response.chainwork)?,
102                         height: response.height,
103                 })
104         }
105 }
106
107
108 /// Converts a JSON value into a block. Assumes the block is hex-encoded in a JSON string.
109 impl TryInto<Block> for JsonResponse {
110         type Error = std::io::Error;
111
112         fn try_into(self) -> std::io::Result<Block> {
113                 match self.0.as_str() {
114                         None => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string")),
115                         Some(hex_data) => match Vec::<u8>::from_hex(hex_data) {
116                                 Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")),
117                                 Ok(block_data) => match encode::deserialize(&block_data) {
118                                         Err(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid block data")),
119                                         Ok(block) => Ok(block),
120                                 },
121                         },
122                 }
123         }
124 }
125
126 /// Converts a JSON value into the best block hash and optional height.
127 impl TryInto<(BlockHash, Option<u32>)> for JsonResponse {
128         type Error = std::io::Error;
129
130         fn try_into(self) -> std::io::Result<(BlockHash, Option<u32>)> {
131                 if !self.0.is_object() {
132                         return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object"));
133                 }
134
135                 let hash = match &self.0["bestblockhash"] {
136                         serde_json::Value::String(hex_data) => match BlockHash::from_hex(&hex_data) {
137                                 Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")),
138                                 Ok(block_hash) => block_hash,
139                         },
140                         _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string")),
141                 };
142
143                 let height = match &self.0["blocks"] {
144                         serde_json::Value::Null => None,
145                         serde_json::Value::Number(height) => match height.as_u64() {
146                                 None => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid height")),
147                                 Some(height) => match height.try_into() {
148                                         Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid height")),
149                                         Ok(height) => Some(height),
150                                 }
151                         },
152                         _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON number")),
153                 };
154
155                 Ok((hash, height))
156         }
157 }
158
159 impl TryInto<Txid> for JsonResponse {
160         type Error = std::io::Error;
161         fn try_into(self) -> std::io::Result<Txid> {
162                 match self.0.as_str() {
163                         None => Err(std::io::Error::new(
164                                 std::io::ErrorKind::InvalidData,
165                                 "expected JSON string",
166                         )),
167                         Some(hex_data) => match Vec::<u8>::from_hex(hex_data) {
168                                 Err(_) => Err(std::io::Error::new(
169                                         std::io::ErrorKind::InvalidData,
170                                         "invalid hex data",
171                                 )),
172                                 Ok(txid_data) => match encode::deserialize(&txid_data) {
173                                         Err(_) => Err(std::io::Error::new(
174                                                 std::io::ErrorKind::InvalidData,
175                                                 "invalid txid",
176                                         )),
177                                         Ok(txid) => Ok(txid),
178                                 },
179                         },
180                 }
181         }
182 }
183
184 #[cfg(test)]
185 pub(crate) mod tests {
186         use super::*;
187         use bitcoin::blockdata::constants::genesis_block;
188         use bitcoin::consensus::encode;
189         use bitcoin::hashes::Hash;
190         use bitcoin::network::constants::Network;
191
192         /// Converts from `BlockHeaderData` into a `GetHeaderResponse` JSON value.
193         impl From<BlockHeaderData> for serde_json::Value {
194                 fn from(data: BlockHeaderData) -> Self {
195                         let BlockHeaderData { chainwork, height, header } = data;
196                         serde_json::json!({
197                                 "chainwork": chainwork.to_string()["0x".len()..],
198                                 "height": height,
199                                 "version": header.version,
200                                 "merkleroot": header.merkle_root.to_hex(),
201                                 "time": header.time,
202                                 "nonce": header.nonce,
203                                 "bits": header.bits.to_hex(),
204                                 "previousblockhash": header.prev_blockhash.to_hex(),
205                         })
206                 }
207         }
208
209         #[test]
210         fn into_block_header_from_json_response_with_unexpected_type() {
211                 let response = JsonResponse(serde_json::json!(42));
212                 match TryInto::<BlockHeaderData>::try_into(response) {
213                         Err(e) => {
214                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
215                                 assert_eq!(e.get_ref().unwrap().to_string(), "unexpected JSON type");
216                         },
217                         Ok(_) => panic!("Expected error"),
218                 }
219         }
220
221         #[test]
222         fn into_block_header_from_json_response_with_unexpected_header_type() {
223                 let response = JsonResponse(serde_json::json!([42]));
224                 match TryInto::<BlockHeaderData>::try_into(response) {
225                         Err(e) => {
226                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
227                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
228                         },
229                         Ok(_) => panic!("Expected error"),
230                 }
231         }
232
233         #[test]
234         fn into_block_header_from_json_response_with_invalid_header_response() {
235                 let block = genesis_block(Network::Bitcoin);
236                 let mut response = JsonResponse(BlockHeaderData {
237                         chainwork: block.header.work(),
238                         height: 0,
239                         header: block.header
240                 }.into());
241                 response.0["chainwork"].take();
242
243                 match TryInto::<BlockHeaderData>::try_into(response) {
244                         Err(e) => {
245                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
246                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid header response");
247                         },
248                         Ok(_) => panic!("Expected error"),
249                 }
250         }
251
252         #[test]
253         fn into_block_header_from_json_response_with_invalid_header_data() {
254                 let block = genesis_block(Network::Bitcoin);
255                 let mut response = JsonResponse(BlockHeaderData {
256                         chainwork: block.header.work(),
257                         height: 0,
258                         header: block.header
259                 }.into());
260                 response.0["chainwork"] = serde_json::json!("foobar");
261
262                 match TryInto::<BlockHeaderData>::try_into(response) {
263                         Err(e) => {
264                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
265                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid header data");
266                         },
267                         Ok(_) => panic!("Expected error"),
268                 }
269         }
270
271         #[test]
272         fn into_block_header_from_json_response_with_valid_header() {
273                 let block = genesis_block(Network::Bitcoin);
274                 let response = JsonResponse(BlockHeaderData {
275                         chainwork: block.header.work(),
276                         height: 0,
277                         header: block.header
278                 }.into());
279
280                 match TryInto::<BlockHeaderData>::try_into(response) {
281                         Err(e) => panic!("Unexpected error: {:?}", e),
282                         Ok(data) => {
283                                 assert_eq!(data.chainwork, block.header.work());
284                                 assert_eq!(data.height, 0);
285                                 assert_eq!(data.header, block.header);
286                         },
287                 }
288         }
289
290         #[test]
291         fn into_block_header_from_json_response_with_valid_header_array() {
292                 let genesis_block = genesis_block(Network::Bitcoin);
293                 let best_block_header = BlockHeader {
294                         prev_blockhash: genesis_block.block_hash(),
295                         ..genesis_block.header
296                 };
297                 let chainwork = genesis_block.header.work() + best_block_header.work();
298                 let response = JsonResponse(serde_json::json!([
299                                 serde_json::Value::from(BlockHeaderData {
300                                         chainwork, height: 1, header: best_block_header,
301                                 }),
302                                 serde_json::Value::from(BlockHeaderData {
303                                         chainwork: genesis_block.header.work(), height: 0, header: genesis_block.header,
304                                 }),
305                 ]));
306
307                 match TryInto::<BlockHeaderData>::try_into(response) {
308                         Err(e) => panic!("Unexpected error: {:?}", e),
309                         Ok(data) => {
310                                 assert_eq!(data.chainwork, chainwork);
311                                 assert_eq!(data.height, 1);
312                                 assert_eq!(data.header, best_block_header);
313                         },
314                 }
315         }
316
317         #[test]
318         fn into_block_header_from_json_response_without_previous_block_hash() {
319                 let block = genesis_block(Network::Bitcoin);
320                 let mut response = JsonResponse(BlockHeaderData {
321                         chainwork: block.header.work(),
322                         height: 0,
323                         header: block.header
324                 }.into());
325                 response.0.as_object_mut().unwrap().remove("previousblockhash");
326
327                 match TryInto::<BlockHeaderData>::try_into(response) {
328                         Err(e) => panic!("Unexpected error: {:?}", e),
329                         Ok(BlockHeaderData { chainwork: _, height: _, header }) => {
330                                 assert_eq!(header, block.header);
331                         },
332                 }
333         }
334
335         #[test]
336         fn into_block_from_invalid_binary_response() {
337                 let response = BinaryResponse(b"foo".to_vec());
338                 match TryInto::<Block>::try_into(response) {
339                         Err(_) => {},
340                         Ok(_) => panic!("Expected error"),
341                 }
342         }
343
344         #[test]
345         fn into_block_from_valid_binary_response() {
346                 let genesis_block = genesis_block(Network::Bitcoin);
347                 let response = BinaryResponse(encode::serialize(&genesis_block));
348                 match TryInto::<Block>::try_into(response) {
349                         Err(e) => panic!("Unexpected error: {:?}", e),
350                         Ok(block) => assert_eq!(block, genesis_block),
351                 }
352         }
353
354         #[test]
355         fn into_block_from_json_response_with_unexpected_type() {
356                 let response = JsonResponse(serde_json::json!({ "result": "foo" }));
357                 match TryInto::<Block>::try_into(response) {
358                         Err(e) => {
359                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
360                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
361                         },
362                         Ok(_) => panic!("Expected error"),
363                 }
364         }
365
366         #[test]
367         fn into_block_from_json_response_with_invalid_hex_data() {
368                 let response = JsonResponse(serde_json::json!("foobar"));
369                 match TryInto::<Block>::try_into(response) {
370                         Err(e) => {
371                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
372                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
373                         },
374                         Ok(_) => panic!("Expected error"),
375                 }
376         }
377
378         #[test]
379         fn into_block_from_json_response_with_invalid_block_data() {
380                 let response = JsonResponse(serde_json::json!("abcd"));
381                 match TryInto::<Block>::try_into(response) {
382                         Err(e) => {
383                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
384                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid block data");
385                         },
386                         Ok(_) => panic!("Expected error"),
387                 }
388         }
389
390         #[test]
391         fn into_block_from_json_response_with_valid_block_data() {
392                 let genesis_block = genesis_block(Network::Bitcoin);
393                 let response = JsonResponse(serde_json::json!(encode::serialize_hex(&genesis_block)));
394                 match TryInto::<Block>::try_into(response) {
395                         Err(e) => panic!("Unexpected error: {:?}", e),
396                         Ok(block) => assert_eq!(block, genesis_block),
397                 }
398         }
399
400         #[test]
401         fn into_block_hash_from_json_response_with_unexpected_type() {
402                 let response = JsonResponse(serde_json::json!("foo"));
403                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
404                         Err(e) => {
405                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
406                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
407                         },
408                         Ok(_) => panic!("Expected error"),
409                 }
410         }
411
412         #[test]
413         fn into_block_hash_from_json_response_with_unexpected_bestblockhash_type() {
414                 let response = JsonResponse(serde_json::json!({ "bestblockhash": 42 }));
415                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
416                         Err(e) => {
417                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
418                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
419                         },
420                         Ok(_) => panic!("Expected error"),
421                 }
422         }
423
424         #[test]
425         fn into_block_hash_from_json_response_with_invalid_hex_data() {
426                 let response = JsonResponse(serde_json::json!({ "bestblockhash": "foobar"} ));
427                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
428                         Err(e) => {
429                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
430                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
431                         },
432                         Ok(_) => panic!("Expected error"),
433                 }
434         }
435
436         #[test]
437         fn into_block_hash_from_json_response_without_height() {
438                 let block = genesis_block(Network::Bitcoin);
439                 let response = JsonResponse(serde_json::json!({
440                         "bestblockhash": block.block_hash().to_hex(),
441                 }));
442                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
443                         Err(e) => panic!("Unexpected error: {:?}", e),
444                         Ok((hash, height)) => {
445                                 assert_eq!(hash, block.block_hash());
446                                 assert!(height.is_none());
447                         },
448                 }
449         }
450
451         #[test]
452         fn into_block_hash_from_json_response_with_unexpected_blocks_type() {
453                 let block = genesis_block(Network::Bitcoin);
454                 let response = JsonResponse(serde_json::json!({
455                         "bestblockhash": block.block_hash().to_hex(),
456                         "blocks": "foo",
457                 }));
458                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
459                         Err(e) => {
460                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
461                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON number");
462                         },
463                         Ok(_) => panic!("Expected error"),
464                 }
465         }
466
467         #[test]
468         fn into_block_hash_from_json_response_with_invalid_height() {
469                 let block = genesis_block(Network::Bitcoin);
470                 let response = JsonResponse(serde_json::json!({
471                         "bestblockhash": block.block_hash().to_hex(),
472                         "blocks": std::u64::MAX,
473                 }));
474                 match TryInto::<(BlockHash, Option<u32>)>::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 height");
478                         },
479                         Ok(_) => panic!("Expected error"),
480                 }
481         }
482
483         #[test]
484         fn into_block_hash_from_json_response_with_height() {
485                 let block = genesis_block(Network::Bitcoin);
486                 let response = JsonResponse(serde_json::json!({
487                         "bestblockhash": block.block_hash().to_hex(),
488                         "blocks": 1,
489                 }));
490                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
491                         Err(e) => panic!("Unexpected error: {:?}", e),
492                         Ok((hash, height)) => {
493                                 assert_eq!(hash, block.block_hash());
494                                 assert_eq!(height.unwrap(), 1);
495                         },
496                 }
497         }
498
499         #[test]
500         fn into_txid_from_json_response_with_unexpected_type() {
501                 let response = JsonResponse(serde_json::json!({ "result": "foo" }));
502                 match TryInto::<Txid>::try_into(response) {
503                         Err(e) => {
504                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
505                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
506                         }
507                         Ok(_) => panic!("Expected error"),
508                 }
509         }
510
511         #[test]
512         fn into_txid_from_json_response_with_invalid_hex_data() {
513                 let response = JsonResponse(serde_json::json!("foobar"));
514                 match TryInto::<Txid>::try_into(response) {
515                         Err(e) => {
516                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
517                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
518                         }
519                         Ok(_) => panic!("Expected error"),
520                 }
521         }
522
523         #[test]
524         fn into_txid_from_json_response_with_invalid_txid_data() {
525                 let response = JsonResponse(serde_json::json!("abcd"));
526                 match TryInto::<Txid>::try_into(response) {
527                         Err(e) => {
528                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
529                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid txid");
530                         }
531                         Ok(_) => panic!("Expected error"),
532                 }
533         }
534
535         #[test]
536         fn into_txid_from_json_response_with_valid_txid_data() {
537                 let target_txid = Txid::from_slice(&[1; 32]).unwrap();
538                 let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_txid)));
539                 match TryInto::<Txid>::try_into(response) {
540                         Err(e) => panic!("Unexpected error: {:?}", e),
541                         Ok(txid) => assert_eq!(txid, target_txid),
542                 }
543         }
544 }