bitcoind_client/
convert.rs#![allow(missing_docs)]
use std::convert::{TryFrom, TryInto};
use std::str::FromStr;
use bitcoin::block::Version;
use bitcoin::consensus::encode;
use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::Hash;
use bitcoin::{Block, BlockHash, CompactTarget, Work};
use bitcoin::blockdata::block::Header as BlockHeader;
use bitcoin::hash_types::TxMerkleNode;
use serde::Deserialize;
use crate::bitcoind_client::BlockHeaderData;
pub struct JsonResponse(pub serde_json::Value);
#[derive(Debug)]
pub struct BlockchainInfo {
pub latest_height: usize,
pub latest_blockhash: BlockHash,
}
impl TryFrom<JsonResponse> for BlockchainInfo {
type Error = std::io::Error;
fn try_from(item: JsonResponse) -> std::io::Result<Self> {
Ok(Self {
latest_height: item.0["blocks"].as_u64().unwrap() as usize,
latest_blockhash: BlockHash::from_str(item.0["bestblockhash"].as_str().unwrap())
.unwrap(),
})
}
}
impl TryInto<Option<BlockHash>> for JsonResponse {
type Error = std::io::Error;
fn try_into(self) -> Result<Option<BlockHash>, Self::Error> {
match self.0.as_str() {
None => Ok(None),
Some(s) => Ok(Some(BlockHash::from_str(s).unwrap())),
}
}
}
pub fn hex_to_work(hex: &str) -> Result<Work, bitcoin::hashes::hex::HexToArrayError> {
let bytes = <[u8; 32]>::from_hex(hex)?;
Ok(Work::from_be_bytes(bytes))
}
#[derive(Deserialize)]
struct GetHeaderResponse {
pub version: i32,
pub merkleroot: String,
pub time: u32,
pub nonce: u32,
pub bits: String,
pub previousblockhash: String,
pub chainwork: String,
pub height: u32,
}
impl TryFrom<GetHeaderResponse> for BlockHeaderData {
type Error = bitcoin::hashes::hex::HexToArrayError;
fn try_from(response: GetHeaderResponse) -> Result<Self, Self::Error> {
Ok(BlockHeaderData {
header: BlockHeader {
version: Version::from_consensus(response.version),
prev_blockhash: BlockHash::from_str(&response.previousblockhash)?,
merkle_root: TxMerkleNode::from_str(&response.merkleroot)?,
time: response.time,
bits: CompactTarget::from_unprefixed_hex(&response.bits).expect("error while parsing bits"),
nonce: response.nonce,
},
chainwork: hex_to_work(&response.chainwork)?,
height: response.height,
})
}
}
impl TryInto<BlockHeaderData> for JsonResponse {
type Error = std::io::Error;
fn try_into(self) -> std::io::Result<BlockHeaderData> {
let mut header = match self.0 {
serde_json::Value::Array(mut array) if !array.is_empty() => {
array.drain(..).next().unwrap()
}
serde_json::Value::Object(_) => self.0,
_ => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"unexpected JSON type",
))
}
};
if !header.is_object() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"expected JSON object",
));
}
if let None = header.get("previousblockhash") {
let hash: BlockHash = BlockHash::all_zeros();
header.as_object_mut().unwrap().insert(
"previousblockhash".to_string(),
serde_json::json!(hash.to_string()),
);
}
match serde_json::from_value::<GetHeaderResponse>(header) {
Err(_) => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"invalid header response",
)),
Ok(response) => match response.try_into() {
Err(_) => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"invalid header data",
)),
Ok(header) => Ok(header),
},
}
}
}
impl TryInto<Block> for JsonResponse {
type Error = std::io::Error;
fn try_into(self) -> std::io::Result<Block> {
match self.0.as_str() {
None => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"expected JSON string",
)),
Some(hex_data) => match Vec::<u8>::from_hex(hex_data) {
Err(_) => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"invalid hex data",
)),
Ok(block_data) => match encode::deserialize(&block_data) {
Err(_) => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"invalid block data",
)),
Ok(block) => Ok(block),
},
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryFrom;
#[test]
fn header_parse_test() {
let response = GetHeaderResponse {
bits: "207fffff".to_string(),
version: 1,
previousblockhash: "0000000000000000000000000000000000000000000000000000000000000000".to_string(),
merkleroot: "0000000000000000000000000000000000000000000000000000000000000000".to_string(),
time: 1231006505,
nonce: 2083236893,
chainwork: "000000000000000000000000000000000000000000000000000000000000000f".to_owned(),
height: 1,
};
BlockHeaderData::try_from(response).unwrap();
}
}