Skip to content

Commit 4bb7b97

Browse files
committed
Rewrite the async client
Remove the version folders with RPC macros. Rewrite the async client to have the base methods in the main module removing all the macroization. Create a new module for the bdk client that has the required RPCs in it that all return the non-version specific model types. Rewrite the tests to use the bdk client and use a similar structure to the sync tests.
1 parent 8a9d9f7 commit 4bb7b97

File tree

25 files changed

+283
-837
lines changed

25 files changed

+283
-837
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22

33
Cargo.lock
44
**/target
5+
.vscode/

.vscode/settings.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"rust-analyzer.cargo.features": [
3+
"client-async"
4+
],
5+
"rust-analyzer.check.features": [
6+
"client-async"
7+
]
8+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! Async JSON-RPC client with the RPC set used by BDK for Core versions 25 to 30.
4+
5+
use bitcoin::{block, Block, BlockHash, Transaction, Txid};
6+
7+
use crate::client_async::{into_json, Client, Result};
8+
use crate::types::model::{GetBlockFilter, GetBlockHeaderVerbose, GetBlockVerboseOne};
9+
10+
impl Client {
11+
/// Gets a block by blockhash.
12+
pub async fn get_block(&self, hash: &BlockHash) -> Result<Block> {
13+
let json: crate::types::v25::GetBlockVerboseZero =
14+
self.call("getblock", &[into_json(hash)?, into_json(0)?]).await?;
15+
Ok(json.into_model()?.0)
16+
}
17+
18+
/// Gets block count.
19+
pub async fn get_block_count(&self) -> Result<u64> {
20+
let json: crate::types::v25::GetBlockCount = self.call("getblockcount", &[]).await?;
21+
Ok(json.into_model().0)
22+
}
23+
24+
/// Gets block hash for a height.
25+
pub async fn get_block_hash(&self, height: u32) -> Result<BlockHash> {
26+
let json: crate::types::v25::GetBlockHash =
27+
self.call("getblockhash", &[into_json(height)?]).await?;
28+
Ok(json.into_model()?.0)
29+
}
30+
31+
/// Gets the hash of the chain tip.
32+
pub async fn get_best_block_hash(&self) -> Result<BlockHash> {
33+
let json: crate::types::v25::GetBestBlockHash = self.call("getbestblockhash", &[]).await?;
34+
Ok(json.into_model()?.0)
35+
}
36+
37+
/// Gets block header by blockhash.
38+
pub async fn get_block_header(&self, hash: &BlockHash) -> Result<block::Header> {
39+
let json: crate::types::v25::GetBlockHeader =
40+
self.call("getblockheader", &[into_json(hash)?, into_json(false)?]).await?;
41+
Ok(json.into_model()?.0)
42+
}
43+
44+
/// Gets block header with verbose output.
45+
pub async fn get_block_header_verbose(
46+
&self,
47+
hash: &BlockHash,
48+
) -> Result<GetBlockHeaderVerbose> {
49+
let response: serde_json::Value =
50+
self.call("getblockheader", &[into_json(hash)?, into_json(true)?]).await?;
51+
52+
if let Ok(json) =
53+
serde_json::from_value::<crate::types::v29::GetBlockHeaderVerbose>(response.clone())
54+
{
55+
Ok(json.into_model()?)
56+
} else {
57+
let json: crate::types::v25::GetBlockHeaderVerbose = serde_json::from_value(response)?;
58+
Ok(json.into_model()?)
59+
}
60+
}
61+
62+
/// Gets a block by blockhash with verbose set to 1.
63+
pub async fn get_block_verbose(&self, hash: &BlockHash) -> Result<GetBlockVerboseOne> {
64+
let response: serde_json::Value =
65+
self.call("getblock", &[into_json(hash)?, into_json(1)?]).await?;
66+
67+
if let Ok(json) =
68+
serde_json::from_value::<crate::types::v29::GetBlockVerboseOne>(response.clone())
69+
{
70+
Ok(json.into_model()?)
71+
} else {
72+
let json: crate::types::v25::GetBlockVerboseOne = serde_json::from_value(response)?;
73+
Ok(json.into_model()?)
74+
}
75+
}
76+
77+
/// Gets block filter for a blockhash.
78+
pub async fn get_block_filter(&self, hash: &BlockHash) -> Result<GetBlockFilter> {
79+
let json: crate::types::v25::GetBlockFilter =
80+
self.call("getblockfilter", &[into_json(hash)?]).await?;
81+
Ok(json.into_model()?)
82+
}
83+
84+
/// Gets transaction IDs currently in the mempool.
85+
pub async fn get_raw_mempool(&self) -> Result<Vec<Txid>> {
86+
let json: crate::types::v25::GetRawMempool = self.call("getrawmempool", &[]).await?;
87+
Ok(json.into_model()?.0)
88+
}
89+
90+
/// Gets raw transaction by txid.
91+
pub async fn get_raw_transaction(&self, txid: &Txid) -> Result<Transaction> {
92+
let json: crate::types::v25::GetRawTransaction =
93+
self.call("getrawtransaction", &[into_json(txid)?]).await?;
94+
Ok(json.into_model()?.0)
95+
}
96+
}

client/src/client_async/error.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
use std::{error, fmt, io};
44

55
use bitcoin::hex;
6+
use types::v17::{
7+
GetBlockHeaderError, GetBlockHeaderVerboseError, GetBlockVerboseOneError,
8+
GetRawTransactionVerboseError,
9+
};
10+
use types::v19::GetBlockFilterError;
11+
use types::v29::{
12+
GetBlockHeaderVerboseError as GetBlockHeaderVerboseErrorV29,
13+
GetBlockVerboseOneError as GetBlockVerboseOneErrorV29,
14+
};
615

716
/// The error type for errors produced in this library.
817
#[derive(Debug)]
@@ -48,6 +57,34 @@ impl From<io::Error> for Error {
4857
fn from(e: io::Error) -> Error { Error::Io(e) }
4958
}
5059

60+
impl From<GetBlockHeaderError> for Error {
61+
fn from(e: GetBlockHeaderError) -> Self { Self::Returned(e.to_string()) }
62+
}
63+
64+
impl From<GetBlockHeaderVerboseError> for Error {
65+
fn from(e: GetBlockHeaderVerboseError) -> Self { Self::Returned(e.to_string()) }
66+
}
67+
68+
impl From<GetBlockVerboseOneError> for Error {
69+
fn from(e: GetBlockVerboseOneError) -> Self { Self::Returned(e.to_string()) }
70+
}
71+
72+
impl From<GetRawTransactionVerboseError> for Error {
73+
fn from(e: GetRawTransactionVerboseError) -> Self { Self::Returned(e.to_string()) }
74+
}
75+
76+
impl From<GetBlockHeaderVerboseErrorV29> for Error {
77+
fn from(e: GetBlockHeaderVerboseErrorV29) -> Self { Self::Returned(e.to_string()) }
78+
}
79+
80+
impl From<GetBlockVerboseOneErrorV29> for Error {
81+
fn from(e: GetBlockVerboseOneErrorV29) -> Self { Self::Returned(e.to_string()) }
82+
}
83+
84+
impl From<GetBlockFilterError> for Error {
85+
fn from(e: GetBlockFilterError) -> Self { Self::Returned(e.to_string()) }
86+
}
87+
5188
impl fmt::Display for Error {
5289
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5390
use Error::*;

client/src/client_async/mod.rs

Lines changed: 61 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,10 @@
22

33
//! Async JSON-RPC clients for specific versions of Bitcoin Core.
44
5+
pub mod bdk_client;
56
mod error;
6-
pub mod v17;
7-
pub mod v18;
8-
pub mod v19;
9-
pub mod v20;
10-
pub mod v21;
11-
pub mod v22;
12-
pub mod v23;
13-
pub mod v24;
14-
pub mod v25;
15-
pub mod v26;
16-
pub mod v27;
17-
pub mod v28;
18-
pub mod v29;
19-
pub mod v30;
207

8+
use std::fmt;
219
use std::fs::File;
2210
use std::io::{BufRead, BufReader};
2311
use std::path::PathBuf;
@@ -55,76 +43,63 @@ impl Auth {
5543
}
5644
}
5745

58-
/// Defines a async `jsonrpc::Client` using `bitreq`.
59-
#[macro_export]
60-
macro_rules! define_jsonrpc_bitreq_async_client {
61-
($version:literal) => {
62-
use std::fmt;
63-
use $crate::client_async::{log_response, Auth, Result};
64-
use $crate::client_async::error::Error;
65-
66-
/// Client implements an async JSON-RPC client for the Bitcoin Core daemon or compatible APIs.
67-
pub struct Client {
68-
inner: jsonrpc::client_async::Client,
69-
}
70-
71-
impl fmt::Debug for Client {
72-
fn fmt(&self, f: &mut fmt::Formatter) -> core::fmt::Result {
73-
write!(
74-
f,
75-
"corepc_client::client_async::{}::Client({:?})",
76-
$version, self.inner
77-
)
78-
}
79-
}
46+
/// Client implements an async JSON-RPC client for the Bitcoin Core daemon or compatible APIs.
47+
pub struct Client {
48+
pub(crate) inner: jsonrpc::client_async::Client,
49+
}
8050

81-
impl Client {
82-
/// Creates a client to a bitcoind JSON-RPC server without authentication.
83-
pub fn new(url: &str) -> Self {
84-
let transport = jsonrpc::bitreq_http_async::Builder::new()
85-
.url(url)
86-
.expect("jsonrpc v0.19, this function does not error")
87-
.timeout(std::time::Duration::from_secs(60))
88-
.build();
89-
let inner = jsonrpc::client_async::Client::with_transport(transport);
90-
91-
Self { inner }
92-
}
51+
impl fmt::Debug for Client {
52+
fn fmt(&self, f: &mut fmt::Formatter) -> core::fmt::Result {
53+
write!(f, "corepc_client::client_async::Client({:?})", self.inner)
54+
}
55+
}
9356

94-
/// Creates a client to a bitcoind JSON-RPC server with authentication.
95-
pub fn new_with_auth(url: &str, auth: Auth) -> Result<Self> {
96-
if matches!(auth, Auth::None) {
97-
return Err(Error::MissingUserPassword);
98-
}
99-
let (user, pass) = auth.get_user_pass()?;
100-
let transport = jsonrpc::bitreq_http_async::Builder::new()
101-
.url(url)
102-
.expect("jsonrpc v0.19, this function does not error")
103-
.timeout(std::time::Duration::from_secs(60))
104-
.basic_auth(user.unwrap(), pass)
105-
.build();
106-
let inner = jsonrpc::client_async::Client::with_transport(transport);
107-
108-
Ok(Self { inner })
109-
}
57+
impl Client {
58+
/// Creates a client to a bitcoind JSON-RPC server without authentication.
59+
pub fn new(url: &str) -> Self {
60+
let transport = jsonrpc::bitreq_http_async::Builder::new()
61+
.url(url)
62+
.expect("jsonrpc v0.19, this function does not error")
63+
.timeout(std::time::Duration::from_secs(60))
64+
.build();
65+
let inner = jsonrpc::client_async::Client::with_transport(transport);
66+
67+
Self { inner }
68+
}
11069

111-
/// Call an RPC `method` with given `args` list.
112-
pub async fn call<T: for<'a> serde::de::Deserialize<'a>>(
113-
&self,
114-
method: &str,
115-
args: &[serde_json::Value],
116-
) -> Result<T> {
117-
let raw = serde_json::value::to_raw_value(args)?;
118-
let req = self.inner.build_request(&method, Some(&*raw));
119-
if log::log_enabled!(log::Level::Debug) {
120-
log::debug!(target: "corepc", "request: {} {}", method, serde_json::Value::from(args));
121-
}
70+
/// Creates a client to a bitcoind JSON-RPC server with authentication.
71+
pub fn new_with_auth(url: &str, auth: Auth) -> Result<Self> {
72+
if matches!(auth, Auth::None) {
73+
return Err(Error::MissingUserPassword);
74+
}
75+
let (user, pass) = auth.get_user_pass()?;
76+
let user = user.ok_or(Error::MissingUserPassword)?;
77+
let transport = jsonrpc::bitreq_http_async::Builder::new()
78+
.url(url)
79+
.expect("jsonrpc v0.19, this function does not error")
80+
.timeout(std::time::Duration::from_secs(60))
81+
.basic_auth(user, pass)
82+
.build();
83+
let inner = jsonrpc::client_async::Client::with_transport(transport);
84+
85+
Ok(Self { inner })
86+
}
12287

123-
let resp = self.inner.send_request(req).await.map_err(Error::from);
124-
log_response(method, &resp);
125-
Ok(resp?.result()?)
126-
}
88+
/// Call an RPC `method` with given `args` list.
89+
pub async fn call<T: for<'a> serde::de::Deserialize<'a>>(
90+
&self,
91+
method: &str,
92+
args: &[serde_json::Value],
93+
) -> Result<T> {
94+
let raw = serde_json::value::to_raw_value(args)?;
95+
let req = self.inner.build_request(method, Some(&*raw));
96+
if log::log_enabled!(log::Level::Debug) {
97+
log::debug!(target: "corepc", "request: {} {}", method, serde_json::Value::from(args));
12798
}
99+
100+
let resp = self.inner.send_request(req).await.map_err(Error::from);
101+
log_response(method, &resp);
102+
Ok(resp?.result()?)
128103
}
129104
}
130105

@@ -156,7 +131,7 @@ macro_rules! impl_async_client_check_expected_server_version {
156131
}
157132

158133
/// Shorthand for converting a variable into a `serde_json::Value`.
159-
fn into_json<T>(val: T) -> Result<serde_json::Value>
134+
pub(crate) fn into_json<T>(val: T) -> Result<serde_json::Value>
160135
where
161136
T: serde::ser::Serialize,
162137
{
@@ -179,10 +154,12 @@ fn log_response(method: &str, resp: &Result<jsonrpc::Response>) {
179154
log::debug!(target: "corepc", "response error for {}: {:?}", method, e);
180155
}
181156
} else if log::log_enabled!(Trace) {
182-
let def =
183-
serde_json::value::to_raw_value(&serde_json::value::Value::Null).unwrap();
184-
let result = resp.result.as_ref().unwrap_or(&def);
185-
log::trace!(target: "corepc", "response for {}: {}", method, result);
157+
if let Ok(def) =
158+
serde_json::value::to_raw_value(&serde_json::value::Value::Null)
159+
{
160+
let result = resp.result.as_ref().unwrap_or(&def);
161+
log::trace!(target: "corepc", "response for {}: {}", method, result);
162+
}
186163
},
187164
}
188165
}

0 commit comments

Comments
 (0)