Skip to content

Commit b27af91

Browse files
committed
Make async corepc-client
Edit the copy of the sync client created in the previous commit to be async. Update the readme and cargo.toml files. Create a new module for the bdk client that has the required RPCs in it that all return the non-version specific model types.
1 parent 700e847 commit b27af91

8 files changed

Lines changed: 349 additions & 93 deletions

File tree

client/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ allowed_duplicates = ["base64"]
2323
[features]
2424
# Enable this feature to get a blocking JSON-RPC client.
2525
client-sync = ["jsonrpc", "jsonrpc/bitreq_http_sync"]
26+
# Enable this feature to get an async JSON-RPC client.
27+
client-async = ["jsonrpc", "jsonrpc/bitreq_http_async", "jsonrpc/client_async"]
2628

2729
[dependencies]
2830
bitcoin = { version = "0.32.0", default-features = false, features = ["std", "serde"] }

client/README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
# corepc-client
22

3-
Rust client for the Bitcoin Core daemon's JSON-RPC API. Currently this
4-
is only a blocking client and is intended to be used in integration testing.
3+
Rust client for the Bitcoin Core daemon's JSON-RPC API.
4+
5+
This crate provides:
6+
7+
- A blocking client intended for integration testing (`client-sync`).
8+
- An async client intended for production (`client-async`).
9+
10+
## Features
11+
12+
- `client-sync`: Blocking JSON-RPC client.
13+
- `client-async`: Async JSON-RPC client.
514

615
## Minimum Supported Rust Version (MSRV)
716

client/src/bdk_client/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/bdk_client/mod.rs

Lines changed: 66 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,16 @@
11
// SPDX-License-Identifier: CC0-1.0
22

3-
//! JSON-RPC clients for testing against specific versions of Bitcoin Core.
3+
//! Async JSON-RPC clients for Bitcoin Core v25 to v30.
44
55
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;
20-
pub mod v31;
6+
mod rpcs;
217

8+
use std::fmt;
229
use std::fs::File;
2310
use std::io::{BufRead, BufReader};
2411
use std::path::PathBuf;
2512

26-
pub use crate::client_sync::error::Error;
13+
pub use crate::bdk_client::error::Error;
2714

2815
/// Crate-specific Result type.
2916
///
@@ -56,77 +43,63 @@ impl Auth {
5643
}
5744
}
5845

59-
/// Defines a `jsonrpc::Client` using `bitreq`.
60-
#[macro_export]
61-
macro_rules! define_jsonrpc_bitreq_client {
62-
($version:literal) => {
63-
use std::fmt;
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+
}
6450

65-
use $crate::client_sync::{log_response, Auth, Result};
66-
use $crate::client_sync::error::Error;
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+
}
6756

68-
/// Client implements a JSON-RPC client for the Bitcoin Core daemon or compatible APIs.
69-
pub struct Client {
70-
inner: jsonrpc::client::Client,
71-
}
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+
}
7269

73-
impl fmt::Debug for Client {
74-
fn fmt(&self, f: &mut fmt::Formatter) -> core::fmt::Result {
75-
write!(
76-
f,
77-
"corepc_client::client_sync::{}::Client({:?})", $version, self.inner
78-
)
79-
}
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);
8074
}
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+
}
8187

82-
impl Client {
83-
/// Creates a client to a bitcoind JSON-RPC server without authentication.
84-
pub fn new(url: &str) -> Self {
85-
let transport = jsonrpc::http::bitreq_http::Builder::new()
86-
.url(url)
87-
.expect("jsonrpc v0.19, this function does not error")
88-
.timeout(std::time::Duration::from_secs(60))
89-
.build();
90-
let inner = jsonrpc::client::Client::with_transport(transport);
91-
92-
Self { inner }
93-
}
94-
95-
/// Creates a client to a bitcoind JSON-RPC server with authentication.
96-
pub fn new_with_auth(url: &str, auth: Auth) -> Result<Self> {
97-
if matches!(auth, Auth::None) {
98-
return Err(Error::MissingUserPassword);
99-
}
100-
let (user, pass) = auth.get_user_pass()?;
101-
102-
let transport = jsonrpc::http::bitreq_http::Builder::new()
103-
.url(url)
104-
.expect("jsonrpc v0.19, this function does not error")
105-
.timeout(std::time::Duration::from_secs(60))
106-
.basic_auth(user.unwrap(), pass)
107-
.build();
108-
let inner = jsonrpc::client::Client::with_transport(transport);
109-
110-
Ok(Self { inner })
111-
}
112-
113-
/// Call an RPC `method` with given `args` list.
114-
pub fn call<T: for<'a> serde::de::Deserialize<'a>>(
115-
&self,
116-
method: &str,
117-
args: &[serde_json::Value],
118-
) -> Result<T> {
119-
let raw = serde_json::value::to_raw_value(args)?;
120-
let req = self.inner.build_request(&method, Some(&*raw));
121-
if log::log_enabled!(log::Level::Debug) {
122-
log::debug!(target: "corepc", "request: {} {}", method, serde_json::Value::from(args));
123-
}
124-
125-
let resp = self.inner.send_request(req).map_err(Error::from);
126-
log_response(method, &resp);
127-
Ok(resp?.result()?)
128-
}
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));
12998
}
99+
100+
let resp = self.inner.send_request(req).await.map_err(Error::from);
101+
log_response(method, &resp);
102+
Ok(resp?.result()?)
130103
}
131104
}
132105

@@ -139,14 +112,14 @@ macro_rules! define_jsonrpc_bitreq_client {
139112
///
140113
/// - `$expected_versions`: An vector of expected server versions e.g., `[230100, 230200]`.
141114
#[macro_export]
142-
macro_rules! impl_client_check_expected_server_version {
115+
macro_rules! impl_async_client_check_expected_server_version {
143116
($expected_versions:expr) => {
144117
impl Client {
145118
/// Checks that the JSON-RPC endpoint is for a `bitcoind` instance with the expected version.
146-
pub fn check_expected_server_version(&self) -> Result<()> {
147-
let server_version = self.server_version()?;
119+
pub async fn check_expected_server_version(&self) -> Result<()> {
120+
let server_version = self.server_version().await?;
148121
if !$expected_versions.contains(&server_version) {
149-
return Err($crate::client_sync::error::UnexpectedServerVersionError {
122+
return Err($crate::bdk_client::error::UnexpectedServerVersionError {
150123
got: server_version,
151124
expected: $expected_versions.to_vec(),
152125
})?;
@@ -158,7 +131,7 @@ macro_rules! impl_client_check_expected_server_version {
158131
}
159132

160133
/// Shorthand for converting a variable into a `serde_json::Value`.
161-
fn into_json<T>(val: T) -> Result<serde_json::Value>
134+
pub(crate) fn into_json<T>(val: T) -> Result<serde_json::Value>
162135
where
163136
T: serde::ser::Serialize,
164137
{
@@ -181,10 +154,12 @@ fn log_response(method: &str, resp: &Result<jsonrpc::Response>) {
181154
log::debug!(target: "corepc", "response error for {}: {:?}", method, e);
182155
}
183156
} else if log::log_enabled!(Trace) {
184-
let def =
185-
serde_json::value::to_raw_value(&serde_json::value::Value::Null).unwrap();
186-
let result = resp.result.as_ref().unwrap_or(&def);
187-
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+
}
188163
},
189164
}
190165
}

client/src/bdk_client/rpcs.rs

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

client/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ pub extern crate types;
1111
#[cfg(feature = "client-sync")]
1212
#[macro_use]
1313
pub mod client_sync;
14+
15+
#[cfg(feature = "client-async")]
16+
pub mod bdk_client;

0 commit comments

Comments
 (0)