Skip to content

Commit 1c174a6

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 1c174a6

22 files changed

Lines changed: 244 additions & 837 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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::{BlockHash, Txid};
6+
7+
use crate::client_async::{into_json, Client, Error, Result};
8+
use crate::types::model::{
9+
GetBestBlockHash, GetBlockCount, GetBlockFilter, GetBlockHash, GetBlockHeader,
10+
GetBlockHeaderVerbose, GetBlockVerboseOne, GetBlockVerboseZero, GetRawMempool,
11+
GetRawTransaction,
12+
};
13+
14+
const VERSION_WITH_TARGET_FIELD: usize = 290000;
15+
16+
impl Client {
17+
async fn bdk_server_version(&self) -> Result<usize> {
18+
let info: serde_json::Value = self.call("getnetworkinfo", &[]).await?;
19+
let version = info
20+
.get("version")
21+
.and_then(serde_json::Value::as_u64)
22+
.ok_or(Error::UnexpectedStructure)?;
23+
usize::try_from(version).map_err(|_| Error::UnexpectedStructure)
24+
}
25+
26+
/// Gets a block by blockhash.
27+
pub async fn get_block(&self, hash: &BlockHash) -> Result<GetBlockVerboseZero> {
28+
let json: crate::types::v25::GetBlockVerboseZero =
29+
self.call("getblock", &[into_json(hash)?, into_json(0)?]).await?;
30+
json.into_model().map_err(|e| Error::Returned(e.to_string()))
31+
}
32+
33+
/// Gets block count.
34+
pub async fn get_block_count(&self) -> Result<GetBlockCount> {
35+
let json: crate::types::v25::GetBlockCount = self.call("getblockcount", &[]).await?;
36+
Ok(json.into_model())
37+
}
38+
39+
/// Gets block hash for a height.
40+
pub async fn get_block_hash(&self, height: u32) -> Result<GetBlockHash> {
41+
let json: crate::types::v25::GetBlockHash =
42+
self.call("getblockhash", &[into_json(height)?]).await?;
43+
json.into_model().map_err(|e| Error::Returned(e.to_string()))
44+
}
45+
46+
/// Gets the hash of the chain tip.
47+
pub async fn get_best_block_hash(&self) -> Result<GetBestBlockHash> {
48+
let json: crate::types::v25::GetBestBlockHash = self.call("getbestblockhash", &[]).await?;
49+
json.into_model().map_err(|e| Error::Returned(e.to_string()))
50+
}
51+
52+
/// Gets block header by blockhash.
53+
pub async fn get_block_header(&self, hash: &BlockHash) -> Result<GetBlockHeader> {
54+
let json: crate::types::v25::GetBlockHeader =
55+
self.call("getblockheader", &[into_json(hash)?, into_json(false)?]).await?;
56+
json.into_model().map_err(|e| Error::Returned(e.to_string()))
57+
}
58+
59+
/// Gets block header with verbose output.
60+
pub async fn get_block_header_verbose(
61+
&self,
62+
hash: &BlockHash,
63+
) -> Result<GetBlockHeaderVerbose> {
64+
if self.bdk_server_version().await? >= VERSION_WITH_TARGET_FIELD {
65+
let json: crate::types::v29::GetBlockHeaderVerbose =
66+
self.call("getblockheader", &[into_json(hash)?]).await?;
67+
json.into_model().map_err(|e| Error::Returned(e.to_string()))
68+
} else {
69+
let json: crate::types::v25::GetBlockHeaderVerbose =
70+
self.call("getblockheader", &[into_json(hash)?]).await?;
71+
json.into_model().map_err(|e| Error::Returned(e.to_string()))
72+
}
73+
}
74+
75+
/// Gets a block by blockhash with verbose set to 1.
76+
pub async fn get_block_verbose(&self, hash: &BlockHash) -> Result<GetBlockVerboseOne> {
77+
if self.bdk_server_version().await? >= VERSION_WITH_TARGET_FIELD {
78+
let json: crate::types::v29::GetBlockVerboseOne =
79+
self.call("getblock", &[into_json(hash)?, into_json(1)?]).await?;
80+
json.into_model().map_err(|e| Error::Returned(e.to_string()))
81+
} else {
82+
let json: crate::types::v25::GetBlockVerboseOne =
83+
self.call("getblock", &[into_json(hash)?, into_json(1)?]).await?;
84+
json.into_model().map_err(|e| Error::Returned(e.to_string()))
85+
}
86+
}
87+
88+
/// Gets block filter for a blockhash.
89+
pub async fn get_block_filter(&self, hash: &BlockHash) -> Result<GetBlockFilter> {
90+
let json: crate::types::v25::GetBlockFilter =
91+
self.call("getblockfilter", &[into_json(hash)?]).await?;
92+
json.into_model().map_err(|e| Error::Returned(e.to_string()))
93+
}
94+
95+
/// Gets transaction IDs currently in the mempool.
96+
pub async fn get_raw_mempool(&self) -> Result<GetRawMempool> {
97+
let json: crate::types::v25::GetRawMempool = self.call("getrawmempool", &[]).await?;
98+
json.into_model().map_err(|e| Error::Returned(e.to_string()))
99+
}
100+
101+
/// Gets raw transaction by txid.
102+
pub async fn get_raw_transaction(&self, txid: &Txid) -> Result<GetRawTransaction> {
103+
let json: crate::types::v25::GetRawTransaction =
104+
self.call("getrawtransaction", &[into_json(txid)?]).await?;
105+
json.into_model().map_err(|e| Error::Returned(e.to_string()))
106+
}
107+
}

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)