Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 96 additions & 3 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@ use std::{
fs::File,
io::{BufRead, BufReader},
path::PathBuf,
str::FromStr,
};

use crate::error::Error;
use crate::jsonrpc::minreq_http::Builder;
use corepc_types::bitcoin::BlockHash;
use jsonrpc::{serde, serde_json, Transport};
use corepc_types::{
bitcoin::{
block::Header, consensus::deserialize, hex::FromHex, Block, BlockHash, Transaction, Txid,
},
model::{GetBlockCount, GetBlockFilter, GetBlockVerboseOne, GetRawMempool},
};
use jsonrpc::{
serde,
serde_json::{self, json},
Transport,
};

/// client authentication methods
#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
Expand Down Expand Up @@ -101,11 +111,94 @@ impl Client {

// `bitcoind` RPC methods
impl Client {
/// Get best block hash.
/// Get block
pub fn get_block(&self, block_hash: &BlockHash) -> Result<Block, Error> {
let hex_string: String = self.call("getblock", &[json!(block_hash), json!(0)])?;

let bytes: Vec<u8> = Vec::<u8>::from_hex(&hex_string).map_err(Error::HexToBytes)?;

let block: Block = deserialize(&bytes)
.map_err(|e| Error::InvalidResponse(format!("failed to deserialize block: {e}")))?;

Ok(block)
Comment on lines +118 to +123
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For things that can be consensus decoded directly from a hex string, like Block, Header, and Transaction, I think it will be easier to use deserialize_hex and have a new Error::DecodeHex error that wraps the FromHexError.

Suggested change
let bytes: Vec<u8> = Vec::<u8>::from_hex(&hex_string).map_err(Error::HexToBytes)?;
let block: Block = deserialize(&bytes)
.map_err(|e| Error::InvalidResponse(format!("failed to deserialize block: {e}")))?;
Ok(block)
bitcoin::consensus::encode::deserialize_hex(&hex_string).map_err(Error::DecodeHex)

}

/// Get block verboseone
pub fn get_block_verbose(&self, block_hash: &BlockHash) -> Result<GetBlockVerboseOne, Error> {
let res: GetBlockVerboseOne = self.call("getblock", &[json!(block_hash), json!(1)])?;
Ok(res)
}
Comment on lines +127 to +130
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume we first want to deserialize the response as a type from corepc_types (e.g. v29::GetBlockVerboseOne), and then call into_model on it. Also we'll need a test for it.


/// Get best block hash
pub fn get_best_block_hash(&self) -> Result<BlockHash, Error> {
let res: String = self.call("getbestblockhash", &[])?;
Ok(res.parse()?)
}

/// Get block count
pub fn get_block_count(&self) -> Result<u64, Error> {
let res: GetBlockCount = self.call("getblockcount", &[])?;
Ok(res.0)
}

/// Get block hash
pub fn get_block_hash(&self, height: u32) -> Result<BlockHash, Error> {
let raw: serde_json::Value = self.call("getblockhash", &[json!(height)])?;

let hash_str = match raw {
serde_json::Value::String(s) => s,
serde_json::Value::Object(obj) => obj
.get("hash")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::InvalidResponse("getblockhash: missing 'hash' field".into()))?
.to_string(),
_ => {
return Err(Error::InvalidResponse(
"getblockhash: unexpected response type".into(),
));
}
};

BlockHash::from_str(&hash_str).map_err(Error::HexToArray)
Comment on lines +146 to +162
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems complicated. Don't we always expect the value to be String?

Suggested change
let raw: serde_json::Value = self.call("getblockhash", &[json!(height)])?;
let hash_str = match raw {
serde_json::Value::String(s) => s,
serde_json::Value::Object(obj) => obj
.get("hash")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::InvalidResponse("getblockhash: missing 'hash' field".into()))?
.to_string(),
_ => {
return Err(Error::InvalidResponse(
"getblockhash: unexpected response type".into(),
));
}
};
BlockHash::from_str(&hash_str).map_err(Error::HexToArray)
let hex: String = self.call("getblockhash", &[json!(height)])?;
Ok(hex.parse()?)

}

/// Get block filter
pub fn get_block_filter(&self, block_hash: BlockHash) -> Result<GetBlockFilter, Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency we should decide whether the API can take a block hash by value or by reference.

Suggested change
pub fn get_block_filter(&self, block_hash: BlockHash) -> Result<GetBlockFilter, Error> {
pub fn get_block_filter(&self, block_hash: &BlockHash) -> Result<GetBlockFilter, Error> {

let res: GetBlockFilter = self.call("getblockfilter", &[json!(block_hash)])?;
Ok(res)
}

/// Get block header
pub fn get_block_header(&self, block_hash: &BlockHash) -> Result<Header, Error> {
let hex_string: String = self.call("getblockheader", &[json!(block_hash), json!(false)])?;

let bytes = Vec::<u8>::from_hex(&hex_string).map_err(Error::HexToBytes)?;

let header = deserialize(&bytes).map_err(|e| {
Error::InvalidResponse(format!("failed to deserialize block header: {e}"))
})?;

Ok(header)
}

/// Get raw mempool
pub fn get_raw_mempool(&self) -> Result<Vec<Txid>, Error> {
let res: GetRawMempool = self.call("getrawmempool", &[])?;
Ok(res.0)
}

/// Get raw transaction
pub fn get_raw_transaction(&self, txid: &Txid) -> Result<Transaction, Error> {
let hex_string: String = self.call("getrawtransaction", &[json!(txid)])?;

let bytes = Vec::<u8>::from_hex(&hex_string).map_err(Error::HexToBytes)?;

let transaction = deserialize(&bytes).map_err(|e| {
Error::InvalidResponse(format!("transaction deserialization failed: {e}"))
})?;

Ok(transaction)
}
}

#[cfg(test)]
Expand Down
7 changes: 6 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::{fmt, io};

use corepc_types::bitcoin::hex::HexToArrayError;
use corepc_types::bitcoin::hex::{HexToArrayError, HexToBytesError};
use jsonrpc::serde_json;

/// Result type alias for the RPC client.
Expand All @@ -23,6 +23,9 @@ pub enum Error {
/// JSON-RPC error from the server.
JsonRpc(jsonrpc::Error),

/// Hex decoding error for byte vectors (used in get_block, etc.)
HexToBytes(HexToBytesError),

/// Hash parsing error.
HexToArray(HexToArrayError),

Expand All @@ -41,6 +44,7 @@ impl fmt::Display for Error {
}
Error::InvalidCookieFile => write!(f, "invalid cookie file"),
Error::InvalidResponse(e) => write!(f, "invalid response: {e}"),
Error::HexToBytes(e) => write!(f, "Hex to bytes error: {e}"),
Error::HexToArray(e) => write!(f, "Hash parsing eror: {e}"),
Error::JsonRpc(e) => write!(f, "JSON-RPC error: {e}"),
Error::Json(e) => write!(f, "JSON error: {e}"),
Expand All @@ -55,6 +59,7 @@ impl std::error::Error for Error {
Error::JsonRpc(e) => Some(e),
Error::Json(e) => Some(e),
Error::Io(e) => Some(e),
Error::HexToBytes(e) => Some(e),
Error::HexToArray(e) => Some(e),
_ => None,
}
Expand Down
Loading