bitcoind_client/
convert.rs

1#![allow(missing_docs)]
2use std::convert::{TryFrom, TryInto};
3use std::str::FromStr;
4
5use bitcoin::block::Version;
6use bitcoin::consensus::encode;
7use bitcoin::hashes::hex::FromHex;
8use bitcoin::hashes::Hash;
9use bitcoin::{Block, BlockHash, CompactTarget, Work};
10use bitcoin::blockdata::block::Header as BlockHeader;
11use bitcoin::hash_types::TxMerkleNode;
12use serde::Deserialize;
13
14use crate::bitcoind_client::BlockHeaderData;
15
16/// Response from RPC calls
17pub struct JsonResponse(pub serde_json::Value);
18
19/// Response from getblockchaininfo RPC
20#[derive(Debug)]
21pub struct BlockchainInfo {
22    pub latest_height: usize,
23    pub latest_blockhash: BlockHash,
24}
25
26impl TryFrom<JsonResponse> for BlockchainInfo {
27    type Error = std::io::Error;
28    fn try_from(item: JsonResponse) -> std::io::Result<Self> {
29        Ok(Self {
30            latest_height: item.0["blocks"].as_u64().unwrap() as usize,
31            latest_blockhash: BlockHash::from_str(item.0["bestblockhash"].as_str().unwrap())
32                .unwrap(),
33        })
34    }
35}
36
37impl TryInto<Option<BlockHash>> for JsonResponse {
38    type Error = std::io::Error;
39
40    fn try_into(self) -> Result<Option<BlockHash>, Self::Error> {
41        match self.0.as_str() {
42            None => Ok(None),
43            Some(s) => Ok(Some(BlockHash::from_str(s).unwrap())),
44        }
45    }
46}
47
48// FIXME: check if this is no longer needed
49pub fn hex_to_work(hex: &str) -> Result<Work, bitcoin::hashes::hex::HexToArrayError> {
50    let bytes = <[u8; 32]>::from_hex(hex)?;
51    Ok(Work::from_be_bytes(bytes))
52}
53
54/// Response data from `getblockheader` RPC and `headers` REST requests.
55#[derive(Deserialize)]
56struct GetHeaderResponse {
57    pub version: i32,
58    pub merkleroot: String,
59    pub time: u32,
60    pub nonce: u32,
61    pub bits: String,
62    pub previousblockhash: String,
63
64    pub chainwork: String,
65    pub height: u32,
66}
67
68/// Converts from `GetHeaderResponse` to `BlockHeaderData`.
69impl TryFrom<GetHeaderResponse> for BlockHeaderData {
70    type Error = bitcoin::hashes::hex::HexToArrayError;
71
72    fn try_from(response: GetHeaderResponse) -> Result<Self, Self::Error> {
73        Ok(BlockHeaderData {
74            header: BlockHeader {
75                version: Version::from_consensus(response.version),
76                prev_blockhash: BlockHash::from_str(&response.previousblockhash)?,
77                merkle_root: TxMerkleNode::from_str(&response.merkleroot)?,
78                time: response.time,
79                bits: CompactTarget::from_unprefixed_hex(&response.bits).expect("error while parsing bits"),
80                nonce: response.nonce,
81            },
82            chainwork: hex_to_work(&response.chainwork)?,
83            height: response.height,
84        })
85    }
86}
87
88/// Converts a JSON value into block header data. The JSON value may be an object representing a
89/// block header or an array of such objects. In the latter case, the first object is converted.
90impl TryInto<BlockHeaderData> for JsonResponse {
91    type Error = std::io::Error;
92
93    fn try_into(self) -> std::io::Result<BlockHeaderData> {
94        let mut header = match self.0 {
95            serde_json::Value::Array(mut array) if !array.is_empty() => {
96                array.drain(..).next().unwrap()
97            }
98            serde_json::Value::Object(_) => self.0,
99            _ => {
100                return Err(std::io::Error::new(
101                    std::io::ErrorKind::InvalidData,
102                    "unexpected JSON type",
103                ))
104            }
105        };
106
107        if !header.is_object() {
108            return Err(std::io::Error::new(
109                std::io::ErrorKind::InvalidData,
110                "expected JSON object",
111            ));
112        }
113
114        // Add an empty previousblockhash for the genesis block.
115        if let None = header.get("previousblockhash") {
116            let hash: BlockHash = BlockHash::all_zeros();
117            header.as_object_mut().unwrap().insert(
118                "previousblockhash".to_string(),
119                serde_json::json!(hash.to_string()),
120            );
121        }
122
123        match serde_json::from_value::<GetHeaderResponse>(header) {
124            Err(_) => Err(std::io::Error::new(
125                std::io::ErrorKind::InvalidData,
126                "invalid header response",
127            )),
128            Ok(response) => match response.try_into() {
129                Err(_) => Err(std::io::Error::new(
130                    std::io::ErrorKind::InvalidData,
131                    "invalid header data",
132                )),
133                Ok(header) => Ok(header),
134            },
135        }
136    }
137}
138
139/// Converts a JSON value into a block. Assumes the block is hex-encoded in a JSON string.
140impl TryInto<Block> for JsonResponse {
141    type Error = std::io::Error;
142
143    fn try_into(self) -> std::io::Result<Block> {
144        match self.0.as_str() {
145            None => Err(std::io::Error::new(
146                std::io::ErrorKind::InvalidData,
147                "expected JSON string",
148            )),
149            Some(hex_data) => match Vec::<u8>::from_hex(hex_data) {
150                Err(_) => Err(std::io::Error::new(
151                    std::io::ErrorKind::InvalidData,
152                    "invalid hex data",
153                )),
154                Ok(block_data) => match encode::deserialize(&block_data) {
155                    Err(_) => Err(std::io::Error::new(
156                        std::io::ErrorKind::InvalidData,
157                        "invalid block data",
158                    )),
159                    Ok(block) => Ok(block),
160                },
161            },
162        }
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use std::convert::TryFrom;
170
171    #[test]
172    fn header_parse_test() {
173        let response = GetHeaderResponse {
174            bits: "207fffff".to_string(),
175            version: 1,
176            previousblockhash: "0000000000000000000000000000000000000000000000000000000000000000".to_string(),
177            merkleroot: "0000000000000000000000000000000000000000000000000000000000000000".to_string(),
178            time: 1231006505,
179            nonce: 2083236893,
180            chainwork: "000000000000000000000000000000000000000000000000000000000000000f".to_owned(),
181            height: 1,
182        };
183
184        // Attempt to convert the response, which should panic
185        BlockHeaderData::try_from(response).unwrap();
186    }
187}