Skip to content

Commit 685e258

Browse files
committed
feat: Add integration tests
- add integration tests for blockchain methods
1 parent 6f95a7f commit 685e258

File tree

3 files changed

+362
-48
lines changed

3 files changed

+362
-48
lines changed

src/client.rs

Lines changed: 93 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,43 @@ use std::{
22
fs::File,
33
io::{BufRead, BufReader},
44
path::PathBuf,
5-
str::FromStr,
65
};
76

87
use crate::error::Error;
98
use crate::jsonrpc::minreq_http::Builder;
109
use corepc_types::{
1110
bitcoin::{
12-
Block, BlockHash, Transaction, Txid, block::Header, consensus::deserialize, hex::FromHex,
11+
block::Header,
12+
consensus::{deserialize, encode::deserialize_hex},
13+
hex::FromHex,
14+
Block, BlockHash, Transaction, Txid,
1315
},
14-
model::{GetBlockCount, GetBlockFilter, GetBlockVerboseOne, GetRawMempool},
16+
model::{GetBlockCount, GetBlockFilter, GetRawMempool},
17+
v29::GetBlockVerboseOne,
1518
};
1619
use jsonrpc::{
17-
Transport, serde,
20+
serde,
1821
serde_json::{self, json},
22+
Transport,
1923
};
2024

21-
/// client authentication methods
25+
/// Client authentication methods for the Bitcoin Core JSON-RPC server
2226
#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
2327
pub enum Auth {
28+
/// No authentication (not recommended)
2429
None,
30+
/// Username and password authentication (RPC user/pass)
2531
UserPass(String, String),
32+
/// Authentication via a cookie file
2633
CookieFile(PathBuf),
2734
}
2835

2936
impl Auth {
30-
/// Convert into the arguments that jsonrpc::Client needs.
37+
/// Converts `Auth` enum into the optional username and password strings
38+
/// required by JSON-RPC client transport.
39+
///
40+
/// # Errors
41+
/// Returns an error if the `CookieFile` cannot be read or invalid
3142
pub fn get_user_pass(self) -> Result<(Option<String>, Option<String>), Error> {
3243
match self {
3344
Auth::None => Ok((None, None)),
@@ -44,18 +55,28 @@ impl Auth {
4455
}
4556
}
4657

47-
// RPC Client.
58+
/// Bitcoin Core JSON-RPC Client.
59+
///
60+
/// A wrapper for JSON-RPC client for interacting with the `bitcoind` RPC interface.
4861
#[derive(Debug)]
4962
pub struct Client {
5063
/// The inner JSON-RPC client.
5164
inner: jsonrpc::Client,
5265
}
5366

5467
impl Client {
55-
/// Creates a client to a bitcoind JSON-RPC server.
68+
/// Creates a client connection to a bitcoind JSON-RPC server with authentication
5669
///
5770
/// Requires authentication via username/password or cookie file.
5871
/// For connections without authentication, use `with_transport` instead.
72+
/// # Arguments
73+
/// * `url` - URL of the RPC server
74+
/// * `auth` - authentication method (`UserPass` or `CookieFile`).
75+
///
76+
/// # Errors
77+
/// * Returns `Error::MissingAuthentication` if `Auth::None` is provided.
78+
/// * Returns `Error::InvalidResponse` if the URL is invalid.
79+
/// * Returns errors related to reading the cookie file.
5980
pub fn with_auth(url: &str, auth: Auth) -> Result<Self, Error> {
6081
if matches!(auth, Auth::None) {
6182
return Err(Error::MissingAuthentication);
@@ -95,7 +116,9 @@ impl Client {
95116
}
96117
}
97118

98-
/// Calls the RPC `method` with a given `args` list.
119+
/// Calls the underlying RPC `method` with given `args` list
120+
///
121+
/// This is the generic function used by all specific RPC methods.
99122
pub fn call<T>(&self, method: &str, args: &[serde_json::Value]) -> Result<T, Error>
100123
where
101124
T: for<'de> serde::Deserialize<'de>,
@@ -108,66 +131,82 @@ impl Client {
108131
}
109132
}
110133

111-
// `bitcoind` RPC methods
134+
// `bitcoind` RPC methods implementation for `Client`
112135
impl Client {
113-
/// Get block
136+
/// Retrieves the raw block data for a given block hash (verbosity 0)
137+
///
138+
/// # Arguments
139+
/// * `block_hash`: The hash of the block to retrieve.
140+
///
141+
/// # Returns
142+
/// The deserialized `Block` struct.
114143
pub fn get_block(&self, block_hash: &BlockHash) -> Result<Block, Error> {
115144
let hex_string: String = self.call("getblock", &[json!(block_hash), json!(0)])?;
116-
117-
let bytes: Vec<u8> = Vec::<u8>::from_hex(&hex_string).map_err(Error::HexToBytes)?;
118-
119-
let block: Block = deserialize(&bytes)
120-
.map_err(|e| Error::InvalidResponse(format!("failed to deserialize block: {e}")))?;
121-
145+
let block = deserialize_hex(&hex_string).map_err(Error::DecodeHex)?;
122146
Ok(block)
123147
}
124148

125-
/// Get block verboseone
149+
/// Retrieves the verbose JSON representation of a block (verbosity 1)
150+
/// # Arguments
151+
/// * `block_hash`: The hash of the block to retrieve.
152+
///
153+
/// # Returns
154+
/// The verbose block data as a `GetBlockVerboseOne` struct.
126155
pub fn get_block_verbose(&self, block_hash: &BlockHash) -> Result<GetBlockVerboseOne, Error> {
127-
let res: GetBlockVerboseOne = self.call("getblock", &[json!(block_hash), json!(1)])?;
128-
Ok(res)
156+
let block_verbose_one: GetBlockVerboseOne =
157+
self.call("getblock", &[json!(block_hash), json!(1)])?;
158+
Ok(block_verbose_one)
129159
}
130160

131-
/// Get best block hash
161+
/// Retrieves the hash of the tip of the best block chain.
162+
///
163+
/// # Returns
164+
/// The `BlockHash` of the chain tip.
132165
pub fn get_best_block_hash(&self) -> Result<BlockHash, Error> {
133166
let res: String = self.call("getbestblockhash", &[])?;
134167
Ok(res.parse()?)
135168
}
136169

137-
/// Get block count
170+
/// Retrieves the number of blocks in the longest chain
171+
///
172+
/// # Returns
173+
/// The block count as a `u64`
138174
pub fn get_block_count(&self) -> Result<u64, Error> {
139175
let res: GetBlockCount = self.call("getblockcount", &[])?;
140176
Ok(res.0)
141177
}
142178

143-
/// Get block hash
179+
/// Retrieves the block hash at a given height
180+
///
181+
/// # Arguments
182+
/// * `height`: The block height
183+
///
184+
/// # Returns
185+
/// The `BlockHash` for the given height
144186
pub fn get_block_hash(&self, height: u32) -> Result<BlockHash, Error> {
145-
let raw: serde_json::Value = self.call("getblockhash", &[json!(height)])?;
146-
147-
let hash_str = match raw {
148-
serde_json::Value::String(s) => s,
149-
serde_json::Value::Object(obj) => obj
150-
.get("hash")
151-
.and_then(|v| v.as_str())
152-
.ok_or_else(|| Error::InvalidResponse("getblockhash: missing 'hash' field".into()))?
153-
.to_string(),
154-
_ => {
155-
return Err(Error::InvalidResponse(
156-
"getblockhash: unexpected response type".into(),
157-
));
158-
}
159-
};
160-
161-
BlockHash::from_str(&hash_str).map_err(Error::HexToArray)
187+
let hex: String = self.call("getblockhash", &[json!(height)])?;
188+
Ok(hex.parse()?)
162189
}
163190

164-
/// Get block filter
165-
pub fn get_block_filter(&self, block_hash: BlockHash) -> Result<GetBlockFilter, Error> {
191+
/// Retrieves the compact block filter for a given block
192+
///
193+
/// # Arguments
194+
/// * `block_hash`: The hash of the block whose filter is requested
195+
///
196+
/// # Returns
197+
/// The `GetBlockFilter` structure containing the filter data
198+
pub fn get_block_filter(&self, block_hash: &BlockHash) -> Result<GetBlockFilter, Error> {
166199
let res: GetBlockFilter = self.call("getblockfilter", &[json!(block_hash)])?;
167200
Ok(res)
168201
}
169202

170-
/// Get block header
203+
/// Retrieves the raw block header for a given block hash.
204+
///
205+
/// # Arguments
206+
/// * `block_hash`: The hash of the block whose header is requested.
207+
///
208+
/// # Returns
209+
/// The deserialized `Header` struct
171210
pub fn get_block_header(&self, block_hash: &BlockHash) -> Result<Header, Error> {
172211
let hex_string: String = self.call("getblockheader", &[json!(block_hash), json!(false)])?;
173212

@@ -180,13 +219,22 @@ impl Client {
180219
Ok(header)
181220
}
182221

183-
/// Get raw mempool
222+
/// Retrieves the transaction IDs of all transactions currently in the mempool
223+
///
224+
/// # Returns
225+
/// A vector of `Txid`s in the raw mempool
184226
pub fn get_raw_mempool(&self) -> Result<Vec<Txid>, Error> {
185227
let res: GetRawMempool = self.call("getrawmempool", &[])?;
186228
Ok(res.0)
187229
}
188230

189-
/// Get raw transaction
231+
/// Retrieves the raw transaction data for a given transaction ID.
232+
///
233+
/// # Arguments
234+
/// * `txid`: The transaction ID to retrieve.
235+
///
236+
/// # Returns
237+
/// The deserialized `Transaction` struct
190238
pub fn get_raw_transaction(&self, txid: &Txid) -> Result<Transaction, Error> {
191239
let hex_string: String = self.call("getrawtransaction", &[json!(txid)])?;
192240

src/error.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
33
use std::{fmt, io};
44

5-
use corepc_types::bitcoin::hex::{HexToArrayError, HexToBytesError};
5+
use corepc_types::bitcoin::{
6+
consensus,
7+
hex::{HexToArrayError, HexToBytesError},
8+
};
69
use jsonrpc::serde_json;
710

811
/// Result type alias for the RPC client.
@@ -11,6 +14,9 @@ pub type Result<T> = std::result::Result<T, Error>;
1114
/// Errors that can occur when using the Bitcoin RPC client.
1215
#[derive(Debug)]
1316
pub enum Error {
17+
/// Hex deserialization error
18+
DecodeHex(consensus::encode::FromHexError),
19+
1420
/// Missing authentication credentials.
1521
MissingAuthentication,
1622

@@ -49,6 +55,7 @@ impl fmt::Display for Error {
4955
Error::JsonRpc(e) => write!(f, "JSON-RPC error: {e}"),
5056
Error::Json(e) => write!(f, "JSON error: {e}"),
5157
Error::Io(e) => write!(f, "I/O error: {e}"),
58+
Error::DecodeHex(e) => write!(f, "Hex deserialization error: {e}"),
5259
}
5360
}
5461
}
@@ -61,6 +68,7 @@ impl std::error::Error for Error {
6168
Error::Io(e) => Some(e),
6269
Error::HexToBytes(e) => Some(e),
6370
Error::HexToArray(e) => Some(e),
71+
Error::DecodeHex(e) => Some(e),
6472
_ => None,
6573
}
6674
}

0 commit comments

Comments
 (0)