Skip to content

Commit 95997c2

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. Replace macros with functions. There is only one async client so the macros are not needed anymore. Create a new module for the bdk client that has the required RPCs in it that return either the rust-bitcoin type or non-version specific model types.
1 parent 8f59c84 commit 95997c2

8 files changed

Lines changed: 640 additions & 149 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"]
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/client_async/error.rs

Lines changed: 198 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,20 @@
22

33
use std::{error, fmt, io};
44

5-
use bitcoin::hex;
6-
7-
/// The error type for errors produced in this library.
5+
/// The general error type for the async client.
6+
///
7+
/// This covers connecting, authenticating, and performing a JSON-RPC call. Each RPC method returns
8+
/// its own error type (e.g. [`GetBlockError`]) which wraps this type in its `Rpc` variant.
89
#[derive(Debug)]
910
pub enum Error {
11+
/// A JSON-RPC error occurred (transport error or the node returned an error).
1012
JsonRpc(jsonrpc::error::Error),
11-
HexToArray(hex::HexToArrayError),
12-
HexToBytes(hex::HexToBytesError),
13+
/// Serializing an argument or deserializing the response failed.
1314
Json(serde_json::error::Error),
14-
BitcoinSerialization(bitcoin::consensus::encode::FromHexError),
15+
/// An I/O error occurred (e.g. reading the cookie file).
1516
Io(io::Error),
17+
/// The cookie file was invalid.
1618
InvalidCookieFile,
17-
/// The JSON result had an unexpected structure.
18-
UnexpectedStructure,
19-
/// The daemon returned an error string.
20-
Returned(String),
2119
/// The server version did not match what was expected.
2220
ServerVersion(UnexpectedServerVersionError),
2321
/// Missing user/password.
@@ -28,22 +26,10 @@ impl From<jsonrpc::error::Error> for Error {
2826
fn from(e: jsonrpc::error::Error) -> Error { Error::JsonRpc(e) }
2927
}
3028

31-
impl From<hex::HexToArrayError> for Error {
32-
fn from(e: hex::HexToArrayError) -> Self { Self::HexToArray(e) }
33-
}
34-
35-
impl From<hex::HexToBytesError> for Error {
36-
fn from(e: hex::HexToBytesError) -> Self { Self::HexToBytes(e) }
37-
}
38-
3929
impl From<serde_json::error::Error> for Error {
4030
fn from(e: serde_json::error::Error) -> Error { Error::Json(e) }
4131
}
4232

43-
impl From<bitcoin::consensus::encode::FromHexError> for Error {
44-
fn from(e: bitcoin::consensus::encode::FromHexError) -> Error { Error::BitcoinSerialization(e) }
45-
}
46-
4733
impl From<io::Error> for Error {
4834
fn from(e: io::Error) -> Error { Error::Io(e) }
4935
}
@@ -54,14 +40,9 @@ impl fmt::Display for Error {
5440

5541
match *self {
5642
JsonRpc(ref e) => write!(f, "JSON-RPC error: {}", e),
57-
HexToArray(ref e) => write!(f, "hex to array decode error: {}", e),
58-
HexToBytes(ref e) => write!(f, "hex to bytes decode error: {}", e),
5943
Json(ref e) => write!(f, "JSON error: {}", e),
60-
BitcoinSerialization(ref e) => write!(f, "Bitcoin serialization error: {}", e),
6144
Io(ref e) => write!(f, "I/O error: {}", e),
6245
InvalidCookieFile => write!(f, "invalid cookie file"),
63-
UnexpectedStructure => write!(f, "the JSON result had an unexpected structure"),
64-
Returned(ref s) => write!(f, "the daemon returned an error string: {}", s),
6546
ServerVersion(ref e) => write!(f, "server version: {}", e),
6647
MissingUserPassword => write!(f, "missing user and/or password"),
6748
}
@@ -74,17 +55,203 @@ impl error::Error for Error {
7455

7556
match *self {
7657
JsonRpc(ref e) => Some(e),
77-
HexToArray(ref e) => Some(e),
78-
HexToBytes(ref e) => Some(e),
7958
Json(ref e) => Some(e),
80-
BitcoinSerialization(ref e) => Some(e),
8159
Io(ref e) => Some(e),
8260
ServerVersion(ref e) => Some(e),
83-
InvalidCookieFile | UnexpectedStructure | Returned(_) | MissingUserPassword => None,
61+
InvalidCookieFile | MissingUserPassword => None,
8462
}
8563
}
8664
}
8765

66+
/// Defines the error type returned by a single RPC method on the async `Client`.
67+
///
68+
/// Every method error has an `Rpc` variant, holding the [`Error`] from making the call. Methods
69+
/// that convert the response into a model type additionally have a `Model` variant holding the
70+
/// conversion error. There are three forms:
71+
///
72+
/// - `Name => Type`: `Model` holds the concrete conversion error `Type`.
73+
/// - `Name => boxed`: `Model` holds a boxed error. Used by methods that select a version specific
74+
/// type at runtime and so have no single concrete conversion error type.
75+
/// - `Name`: no `Model` variant, for methods whose response conversion cannot fail.
76+
macro_rules! define_method_error {
77+
// Version-agnostic (boxed) model conversion error.
78+
($(#[$doc:meta])* $name:ident => boxed) => {
79+
$(#[$doc])*
80+
#[derive(Debug)]
81+
pub enum $name {
82+
/// Making the JSON-RPC call failed.
83+
Rpc(Error),
84+
/// Converting the returned JSON into the model type failed.
85+
Model(Box<dyn std::error::Error + Send + Sync + 'static>),
86+
}
87+
88+
impl From<Error> for $name {
89+
fn from(e: Error) -> Self { Self::Rpc(e) }
90+
}
91+
92+
impl From<serde_json::error::Error> for $name {
93+
fn from(e: serde_json::error::Error) -> Self { Self::Rpc(Error::Json(e)) }
94+
}
95+
96+
impl fmt::Display for $name {
97+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98+
match *self {
99+
Self::Rpc(ref e) => write!(f, "JSON-RPC call failed: {}", e),
100+
Self::Model(ref e) => write!(f, "conversion to the model type failed: {}", e),
101+
}
102+
}
103+
}
104+
105+
impl error::Error for $name {
106+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
107+
match *self {
108+
Self::Rpc(ref e) => Some(e),
109+
Self::Model(ref e) => Some(&**e),
110+
}
111+
}
112+
}
113+
};
114+
// Strongly typed model conversion error.
115+
($(#[$doc:meta])* $name:ident => $model:ty) => {
116+
$(#[$doc])*
117+
#[derive(Debug)]
118+
pub enum $name {
119+
/// Making the JSON-RPC call failed.
120+
Rpc(Error),
121+
/// Converting the returned JSON into the model type failed.
122+
Model($model),
123+
}
124+
125+
impl From<Error> for $name {
126+
fn from(e: Error) -> Self { Self::Rpc(e) }
127+
}
128+
129+
impl From<serde_json::error::Error> for $name {
130+
fn from(e: serde_json::error::Error) -> Self { Self::Rpc(Error::Json(e)) }
131+
}
132+
133+
impl fmt::Display for $name {
134+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135+
match *self {
136+
Self::Rpc(ref e) => write!(f, "JSON-RPC call failed: {}", e),
137+
Self::Model(ref e) => write!(f, "conversion to the model type failed: {}", e),
138+
}
139+
}
140+
}
141+
142+
impl error::Error for $name {
143+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
144+
match *self {
145+
Self::Rpc(ref e) => Some(e),
146+
Self::Model(ref e) => Some(e),
147+
}
148+
}
149+
}
150+
};
151+
// RPC failure only (response conversion cannot fail).
152+
($(#[$doc:meta])* $name:ident) => {
153+
$(#[$doc])*
154+
#[derive(Debug)]
155+
pub enum $name {
156+
/// Making the JSON-RPC call failed.
157+
Rpc(Error),
158+
}
159+
160+
impl From<Error> for $name {
161+
fn from(e: Error) -> Self { Self::Rpc(e) }
162+
}
163+
164+
impl From<serde_json::error::Error> for $name {
165+
fn from(e: serde_json::error::Error) -> Self { Self::Rpc(Error::Json(e)) }
166+
}
167+
168+
impl fmt::Display for $name {
169+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170+
match *self {
171+
Self::Rpc(ref e) => write!(f, "JSON-RPC call failed: {}", e),
172+
}
173+
}
174+
}
175+
176+
impl error::Error for $name {
177+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
178+
match *self {
179+
Self::Rpc(ref e) => Some(e),
180+
}
181+
}
182+
}
183+
};
184+
}
185+
186+
define_method_error! {
187+
/// Error returned by [`Client::get_block`](crate::client_async::Client::get_block).
188+
GetBlockError => bitcoin::consensus::encode::FromHexError
189+
}
190+
191+
define_method_error! {
192+
/// Error returned by [`Client::get_block_count`](crate::client_async::Client::get_block_count).
193+
GetBlockCountError
194+
}
195+
196+
define_method_error! {
197+
/// Error returned by [`Client::server_version`](crate::client_async::Client::server_version).
198+
ServerVersionError
199+
}
200+
201+
define_method_error! {
202+
/// Error returned by [`Client::get_block_hash`](crate::client_async::Client::get_block_hash).
203+
GetBlockHashError => bitcoin::hex::HexToArrayError
204+
}
205+
206+
define_method_error! {
207+
/// Error returned by
208+
/// [`Client::get_best_block_hash`](crate::client_async::Client::get_best_block_hash).
209+
GetBestBlockHashError => bitcoin::hex::HexToArrayError
210+
}
211+
212+
define_method_error! {
213+
/// Error returned by [`Client::get_block_header`](crate::client_async::Client::get_block_header).
214+
GetBlockHeaderError => types::v17::GetBlockHeaderError
215+
}
216+
217+
define_method_error! {
218+
/// Error returned by
219+
/// [`Client::get_block_header_verbose`](crate::client_async::Client::get_block_header_verbose).
220+
GetBlockHeaderVerboseError => boxed
221+
}
222+
223+
define_method_error! {
224+
/// Error returned by [`Client::get_block_verbose`](crate::client_async::Client::get_block_verbose).
225+
GetBlockVerboseError => boxed
226+
}
227+
228+
define_method_error! {
229+
/// Error returned by [`Client::get_block_filter`](crate::client_async::Client::get_block_filter).
230+
GetBlockFilterError => types::v19::GetBlockFilterError
231+
}
232+
233+
define_method_error! {
234+
/// Error returned by [`Client::get_raw_mempool`](crate::client_async::Client::get_raw_mempool).
235+
GetRawMempoolError => bitcoin::hex::HexToArrayError
236+
}
237+
238+
define_method_error! {
239+
/// Error returned by
240+
/// [`Client::get_raw_transaction`](crate::client_async::Client::get_raw_transaction).
241+
GetRawTransactionError => bitcoin::consensus::encode::FromHexError
242+
}
243+
244+
define_method_error! {
245+
/// Error returned by
246+
/// [`Client::get_blockchain_info`](crate::client_async::Client::get_blockchain_info).
247+
GetBlockchainInfoError => boxed
248+
}
249+
250+
define_method_error! {
251+
/// Error returned by [`Client::get_tx_out`](crate::client_async::Client::get_tx_out).
252+
GetTxOutError => types::v17::GetTxOutError
253+
}
254+
88255
/// Error returned when RPC client expects a different version than bitcoind reports.
89256
#[derive(Debug, Clone, PartialEq, Eq)]
90257
pub struct UnexpectedServerVersionError {

0 commit comments

Comments
 (0)