diff --git a/client/Cargo.toml b/client/Cargo.toml
index 914e6a9f..7860b23e 100644
--- a/client/Cargo.toml
+++ b/client/Cargo.toml
@@ -23,11 +23,13 @@ bitcoincore-rpc-json = { version = "0.18.0", path = "../json" }
log = "0.4.5"
jsonrpc-async = "2.0.2"
+reqwest = { version = "0.12.5", default-features = false, features = ["json", "rustls-tls"] }
async-trait = "0.1.42"
+url = "2.5.1"
# Used for deserialization of JSON.
serde = "1"
-serde_json = "1"
+serde_json = { version = "1" }
[dev-dependencies]
tempfile = "3.3.0"
diff --git a/client/src/client.rs b/client/src/client.rs
index 8e456edf..c5ed266e 100644
--- a/client/src/client.rs
+++ b/client/src/client.rs
@@ -8,6 +8,7 @@
// If not, see .
//
+use log::Level::{Debug, Trace, Warn};
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader};
@@ -15,12 +16,13 @@ use std::iter::FromIterator;
use std::path::PathBuf;
use std::{fmt, result};
+use crate::transport::ReqwestTransport;
use crate::{bitcoin, deserialize_hex};
use async_trait::async_trait;
use bitcoin::hex::DisplayHex;
-use jsonrpc_async;
+use jsonrpc_async::Client as JsonRpcClient;
use serde::{self, Serialize};
-use serde_json::{self};
+use url::Url;
use crate::bitcoin::address::{NetworkChecked, NetworkUnchecked};
use crate::bitcoin::hashes::hex::FromHex;
@@ -28,7 +30,6 @@ use crate::bitcoin::secp256k1::ecdsa::Signature;
use crate::bitcoin::{
Address, Amount, Block, OutPoint, PrivateKey, PublicKey, Script, Transaction,
};
-use log::Level::{Debug, Trace, Warn};
use crate::error::*;
use crate::json;
@@ -1308,7 +1309,7 @@ pub trait RpcApi: Sized {
/// Client implements a JSON-RPC client for the Bitcoin Core daemon or compatible APIs.
pub struct Client {
- client: jsonrpc_async::client::Client,
+ client: JsonRpcClient,
}
impl fmt::Debug for Client {
@@ -1319,28 +1320,24 @@ impl fmt::Debug for Client {
impl Client {
/// Creates a client to a bitcoind JSON-RPC server.
- ///
- /// Can only return [Err] when using cookie authentication.
pub async fn new(url: &str, auth: Auth) -> Result {
- let (user, pass) = auth.get_user_pass()?;
- jsonrpc_async::client::Client::simple_http(url, user, pass)
- .await
- .map(|client| Client {
- client,
- })
- .map_err(|e| super::error::Error::JsonRpc(e.into()))
- }
-
- /// Create a new Client using the given [jsonrpc_async::Client].
- pub fn from_jsonrpc(client: jsonrpc_async::client::Client) -> Client {
- Client {
- client,
+ let mut parsed_url = Url::parse(url)?;
+
+ if let (Some(user), pass) = auth.get_user_pass()? {
+ parsed_url
+ .set_username(&user)
+ .map_err(|_| Error::Auth("Failed to set username".to_string()))?;
+ parsed_url
+ .set_password(pass.as_deref())
+ .map_err(|_| Error::Auth("Failed to set password".to_string()))?;
}
- }
- /// Get the underlying JSONRPC client.
- pub fn get_jsonrpc_client(&self) -> &jsonrpc_async::client::Client {
- &self.client
+ let transport = ReqwestTransport::new(parsed_url);
+ let client = JsonRpcClient::with_transport(transport);
+
+ Ok(Self {
+ client,
+ })
}
}
@@ -1385,11 +1382,8 @@ fn log_response(cmd: &str, resp: &Result) {
debug!(target: "bitcoincore_rpc", "JSON-RPC error for {}: {:?}", cmd, e);
}
} else if log_enabled!(Trace) {
- // we can't use to_raw_value here due to compat with Rust 1.29
- let def = serde_json::value::RawValue::from_string(
- serde_json::Value::Null.to_string(),
- )
- .unwrap();
+ let def =
+ serde_json::value::to_raw_value(&serde_json::value::Value::Null).unwrap();
let result = resp.result.as_ref().unwrap_or(&def);
trace!(target: "bitcoincore_rpc", "JSON-RPC response for {}: {}", cmd, result);
}
diff --git a/client/src/error.rs b/client/src/error.rs
index 3d8b7a58..c3b4e811 100644
--- a/client/src/error.rs
+++ b/client/src/error.rs
@@ -13,7 +13,6 @@ use std::{error, fmt, io};
use crate::bitcoin;
use crate::bitcoin::hashes::hex;
use crate::bitcoin::secp256k1;
-use jsonrpc_async;
use serde_json;
/// The error type for errors produced in this library.
@@ -31,6 +30,8 @@ pub enum Error {
UnexpectedStructure,
/// The daemon returned an error string.
ReturnedError(String),
+ Auth(String),
+ UrlParse(url::ParseError),
}
impl From for Error {
@@ -39,6 +40,12 @@ impl From for Error {
}
}
+impl From for Error {
+ fn from(e: url::ParseError) -> Error {
+ Error::UrlParse(e)
+ }
+}
+
impl From for Error {
fn from(e: hex::HexToBytesError) -> Error {
Error::Hex(e)
@@ -88,6 +95,8 @@ impl fmt::Display for Error {
Error::InvalidCookieFile => write!(f, "invalid cookie file"),
Error::UnexpectedStructure => write!(f, "the JSON result had an unexpected structure"),
Error::ReturnedError(ref s) => write!(f, "the daemon returned an error string: {}", s),
+ Error::Auth(ref s) => write!(f, "Auth error: {}", s),
+ Error::UrlParse(ref s) => write!(f, "Url error: {}", s),
}
}
}
diff --git a/client/src/lib.rs b/client/src/lib.rs
index a33ddc0f..b830a1bf 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -22,21 +22,21 @@ extern crate log;
#[macro_use] // `macro_use` is needed for v1.24.0 compilation.
extern crate serde;
-pub extern crate jsonrpc_async;
-
pub extern crate bitcoincore_rpc_json;
pub use crate::json::bitcoin;
-use bitcoincore_rpc_json::bitcoin::hex::FromHex;
pub use bitcoincore_rpc_json as json;
+use bitcoincore_rpc_json::bitcoin::hex::FromHex;
use json::bitcoin::consensus::{Decodable, ReadExt};
mod client;
mod error;
mod queryable;
+mod transport;
pub use crate::client::*;
pub use crate::error::Error;
pub use crate::queryable::*;
+pub use jsonrpc_async::Error as RpcError;
fn deserialize_hex(hex: &str) -> Result {
let buf = Vec::::from_hex(hex)?;
diff --git a/client/src/transport.rs b/client/src/transport.rs
new file mode 100644
index 00000000..cc876133
--- /dev/null
+++ b/client/src/transport.rs
@@ -0,0 +1,50 @@
+use async_trait::async_trait;
+use jsonrpc_async::Transport;
+use url::Url;
+
+pub struct ReqwestTransport {
+ client: reqwest::Client,
+ url: Url,
+}
+
+impl ReqwestTransport {
+ pub fn new(url: Url) -> Self {
+ Self {
+ client: reqwest::Client::new(),
+ url,
+ }
+ }
+
+ async fn request(&self, req: impl serde::Serialize) -> Result
+ where
+ R: for<'a> serde::de::Deserialize<'a>,
+ {
+ let response = self.client.post(self.url.clone()).json(&req).send().await?;
+ response.json().await
+ }
+}
+
+#[async_trait]
+impl Transport for ReqwestTransport {
+ async fn send_request(
+ &self,
+ r: jsonrpc_async::Request<'_>,
+ ) -> Result {
+ Ok(self.request(r).await.map_err(|e| jsonrpc_async::Error::Transport(e.into()))?)
+ }
+
+ async fn send_batch(
+ &self,
+ _rs: &[jsonrpc_async::Request<'_>],
+ ) -> Result, jsonrpc_async::Error> {
+ unimplemented!()
+ }
+
+ fn fmt_target(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let (Some(host), Some(port)) = (self.url.host(), self.url.port()) {
+ write!(f, "http://{}:{}{}", host, port, self.url.path())
+ } else {
+ write!(f, "http://{:?}", self.url)
+ }
+ }
+}
diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs
index 9c083791..d29930e6 100644
--- a/integration_test/src/main.rs
+++ b/integration_test/src/main.rs
@@ -19,8 +19,7 @@ use std::str::FromStr;
use bitcoin::absolute::LockTime;
use bitcoin::address::{NetworkChecked, NetworkUnchecked};
use bitcoincore_rpc::json;
-use bitcoincore_rpc::jsonrpc_async::error::Error as JsonRpcError;
-use bitcoincore_rpc::{Auth, Client, Error, RpcApi};
+use bitcoincore_rpc::{Auth, Client, Error, RpcApi, RpcError as JsonRpcError};
use crate::json::BlockStatsFields as BsFields;
use bitcoin::consensus::encode::{deserialize, serialize_hex};
@@ -839,12 +838,12 @@ async fn test_test_mempool_accept(cl: &Client) {
.await
.unwrap();
let res = cl.test_mempool_accept(&[&tx]).await.unwrap();
- assert!(!res[0].allowed);
+ assert!(!res[0].allowed.unwrap());
assert!(res[0].reject_reason.is_some());
let signed =
cl.sign_raw_transaction_with_wallet(&tx, None, None).await.unwrap().transaction().unwrap();
let res = cl.test_mempool_accept(&[&signed]).await.unwrap();
- assert!(res[0].allowed, "not allowed: {:?}", res[0].reject_reason);
+ assert!(res[0].allowed.unwrap(), "not allowed: {:?}", res[0].reject_reason);
}
async fn test_wallet_create_funded_psbt(cl: &Client) {