37b2c4323972fb1152fb27b1ee2c195859806104
[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};
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 #[cfg(test)]
160 pub(crate) mod tests {
161         use super::*;
162         use bitcoin::blockdata::constants::genesis_block;
163         use bitcoin::consensus::encode;
164         use bitcoin::network::constants::Network;
165
166         /// Converts from `BlockHeaderData` into a `GetHeaderResponse` JSON value.
167         impl From<BlockHeaderData> for serde_json::Value {
168                 fn from(data: BlockHeaderData) -> Self {
169                         let BlockHeaderData { chainwork, height, header } = data;
170                         serde_json::json!({
171                                 "chainwork": chainwork.to_string()["0x".len()..],
172                                 "height": height,
173                                 "version": header.version,
174                                 "merkleroot": header.merkle_root.to_hex(),
175                                 "time": header.time,
176                                 "nonce": header.nonce,
177                                 "bits": header.bits.to_hex(),
178                                 "previousblockhash": header.prev_blockhash.to_hex(),
179                         })
180                 }
181         }
182
183         #[test]
184         fn into_block_header_from_json_response_with_unexpected_type() {
185                 let response = JsonResponse(serde_json::json!(42));
186                 match TryInto::<BlockHeaderData>::try_into(response) {
187                         Err(e) => {
188                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
189                                 assert_eq!(e.get_ref().unwrap().to_string(), "unexpected JSON type");
190                         },
191                         Ok(_) => panic!("Expected error"),
192                 }
193         }
194
195         #[test]
196         fn into_block_header_from_json_response_with_unexpected_header_type() {
197                 let response = JsonResponse(serde_json::json!([42]));
198                 match TryInto::<BlockHeaderData>::try_into(response) {
199                         Err(e) => {
200                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
201                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
202                         },
203                         Ok(_) => panic!("Expected error"),
204                 }
205         }
206
207         #[test]
208         fn into_block_header_from_json_response_with_invalid_header_response() {
209                 let block = genesis_block(Network::Bitcoin);
210                 let mut response = JsonResponse(BlockHeaderData {
211                         chainwork: block.header.work(),
212                         height: 0,
213                         header: block.header
214                 }.into());
215                 response.0["chainwork"].take();
216
217                 match TryInto::<BlockHeaderData>::try_into(response) {
218                         Err(e) => {
219                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
220                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid header response");
221                         },
222                         Ok(_) => panic!("Expected error"),
223                 }
224         }
225
226         #[test]
227         fn into_block_header_from_json_response_with_invalid_header_data() {
228                 let block = genesis_block(Network::Bitcoin);
229                 let mut response = JsonResponse(BlockHeaderData {
230                         chainwork: block.header.work(),
231                         height: 0,
232                         header: block.header
233                 }.into());
234                 response.0["chainwork"] = serde_json::json!("foobar");
235
236                 match TryInto::<BlockHeaderData>::try_into(response) {
237                         Err(e) => {
238                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
239                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid header data");
240                         },
241                         Ok(_) => panic!("Expected error"),
242                 }
243         }
244
245         #[test]
246         fn into_block_header_from_json_response_with_valid_header() {
247                 let block = genesis_block(Network::Bitcoin);
248                 let response = JsonResponse(BlockHeaderData {
249                         chainwork: block.header.work(),
250                         height: 0,
251                         header: block.header
252                 }.into());
253
254                 match TryInto::<BlockHeaderData>::try_into(response) {
255                         Err(e) => panic!("Unexpected error: {:?}", e),
256                         Ok(data) => {
257                                 assert_eq!(data.chainwork, block.header.work());
258                                 assert_eq!(data.height, 0);
259                                 assert_eq!(data.header, block.header);
260                         },
261                 }
262         }
263
264         #[test]
265         fn into_block_header_from_json_response_with_valid_header_array() {
266                 let genesis_block = genesis_block(Network::Bitcoin);
267                 let best_block_header = BlockHeader {
268                         prev_blockhash: genesis_block.block_hash(),
269                         ..genesis_block.header
270                 };
271                 let chainwork = genesis_block.header.work() + best_block_header.work();
272                 let response = JsonResponse(serde_json::json!([
273                                 serde_json::Value::from(BlockHeaderData {
274                                         chainwork, height: 1, header: best_block_header,
275                                 }),
276                                 serde_json::Value::from(BlockHeaderData {
277                                         chainwork: genesis_block.header.work(), height: 0, header: genesis_block.header,
278                                 }),
279                 ]));
280
281                 match TryInto::<BlockHeaderData>::try_into(response) {
282                         Err(e) => panic!("Unexpected error: {:?}", e),
283                         Ok(data) => {
284                                 assert_eq!(data.chainwork, chainwork);
285                                 assert_eq!(data.height, 1);
286                                 assert_eq!(data.header, best_block_header);
287                         },
288                 }
289         }
290
291         #[test]
292         fn into_block_header_from_json_response_without_previous_block_hash() {
293                 let block = genesis_block(Network::Bitcoin);
294                 let mut response = JsonResponse(BlockHeaderData {
295                         chainwork: block.header.work(),
296                         height: 0,
297                         header: block.header
298                 }.into());
299                 response.0.as_object_mut().unwrap().remove("previousblockhash");
300
301                 match TryInto::<BlockHeaderData>::try_into(response) {
302                         Err(e) => panic!("Unexpected error: {:?}", e),
303                         Ok(BlockHeaderData { chainwork: _, height: _, header }) => {
304                                 assert_eq!(header, block.header);
305                         },
306                 }
307         }
308
309         #[test]
310         fn into_block_from_invalid_binary_response() {
311                 let response = BinaryResponse(b"foo".to_vec());
312                 match TryInto::<Block>::try_into(response) {
313                         Err(_) => {},
314                         Ok(_) => panic!("Expected error"),
315                 }
316         }
317
318         #[test]
319         fn into_block_from_valid_binary_response() {
320                 let genesis_block = genesis_block(Network::Bitcoin);
321                 let response = BinaryResponse(encode::serialize(&genesis_block));
322                 match TryInto::<Block>::try_into(response) {
323                         Err(e) => panic!("Unexpected error: {:?}", e),
324                         Ok(block) => assert_eq!(block, genesis_block),
325                 }
326         }
327
328         #[test]
329         fn into_block_from_json_response_with_unexpected_type() {
330                 let response = JsonResponse(serde_json::json!({ "result": "foo" }));
331                 match TryInto::<Block>::try_into(response) {
332                         Err(e) => {
333                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
334                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
335                         },
336                         Ok(_) => panic!("Expected error"),
337                 }
338         }
339
340         #[test]
341         fn into_block_from_json_response_with_invalid_hex_data() {
342                 let response = JsonResponse(serde_json::json!("foobar"));
343                 match TryInto::<Block>::try_into(response) {
344                         Err(e) => {
345                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
346                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
347                         },
348                         Ok(_) => panic!("Expected error"),
349                 }
350         }
351
352         #[test]
353         fn into_block_from_json_response_with_invalid_block_data() {
354                 let response = JsonResponse(serde_json::json!("abcd"));
355                 match TryInto::<Block>::try_into(response) {
356                         Err(e) => {
357                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
358                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid block data");
359                         },
360                         Ok(_) => panic!("Expected error"),
361                 }
362         }
363
364         #[test]
365         fn into_block_from_json_response_with_valid_block_data() {
366                 let genesis_block = genesis_block(Network::Bitcoin);
367                 let response = JsonResponse(serde_json::json!(encode::serialize_hex(&genesis_block)));
368                 match TryInto::<Block>::try_into(response) {
369                         Err(e) => panic!("Unexpected error: {:?}", e),
370                         Ok(block) => assert_eq!(block, genesis_block),
371                 }
372         }
373
374         #[test]
375         fn into_block_hash_from_json_response_with_unexpected_type() {
376                 let response = JsonResponse(serde_json::json!("foo"));
377                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
378                         Err(e) => {
379                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
380                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON object");
381                         },
382                         Ok(_) => panic!("Expected error"),
383                 }
384         }
385
386         #[test]
387         fn into_block_hash_from_json_response_with_unexpected_bestblockhash_type() {
388                 let response = JsonResponse(serde_json::json!({ "bestblockhash": 42 }));
389                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
390                         Err(e) => {
391                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
392                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
393                         },
394                         Ok(_) => panic!("Expected error"),
395                 }
396         }
397
398         #[test]
399         fn into_block_hash_from_json_response_with_invalid_hex_data() {
400                 let response = JsonResponse(serde_json::json!({ "bestblockhash": "foobar"} ));
401                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
402                         Err(e) => {
403                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
404                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
405                         },
406                         Ok(_) => panic!("Expected error"),
407                 }
408         }
409
410         #[test]
411         fn into_block_hash_from_json_response_without_height() {
412                 let block = genesis_block(Network::Bitcoin);
413                 let response = JsonResponse(serde_json::json!({
414                         "bestblockhash": block.block_hash().to_hex(),
415                 }));
416                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
417                         Err(e) => panic!("Unexpected error: {:?}", e),
418                         Ok((hash, height)) => {
419                                 assert_eq!(hash, block.block_hash());
420                                 assert!(height.is_none());
421                         },
422                 }
423         }
424
425         #[test]
426         fn into_block_hash_from_json_response_with_unexpected_blocks_type() {
427                 let block = genesis_block(Network::Bitcoin);
428                 let response = JsonResponse(serde_json::json!({
429                         "bestblockhash": block.block_hash().to_hex(),
430                         "blocks": "foo",
431                 }));
432                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
433                         Err(e) => {
434                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
435                                 assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON number");
436                         },
437                         Ok(_) => panic!("Expected error"),
438                 }
439         }
440
441         #[test]
442         fn into_block_hash_from_json_response_with_invalid_height() {
443                 let block = genesis_block(Network::Bitcoin);
444                 let response = JsonResponse(serde_json::json!({
445                         "bestblockhash": block.block_hash().to_hex(),
446                         "blocks": std::u64::MAX,
447                 }));
448                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
449                         Err(e) => {
450                                 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
451                                 assert_eq!(e.get_ref().unwrap().to_string(), "invalid height");
452                         },
453                         Ok(_) => panic!("Expected error"),
454                 }
455         }
456
457         #[test]
458         fn into_block_hash_from_json_response_with_height() {
459                 let block = genesis_block(Network::Bitcoin);
460                 let response = JsonResponse(serde_json::json!({
461                         "bestblockhash": block.block_hash().to_hex(),
462                         "blocks": 1,
463                 }));
464                 match TryInto::<(BlockHash, Option<u32>)>::try_into(response) {
465                         Err(e) => panic!("Unexpected error: {:?}", e),
466                         Ok((hash, height)) => {
467                                 assert_eq!(hash, block.block_hash());
468                                 assert_eq!(height.unwrap(), 1);
469                         },
470                 }
471         }
472 }