Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use custom Reqwest transport #5

Merged
merged 4 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
50 changes: 22 additions & 28 deletions client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,28 @@
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//

use log::Level::{Debug, Trace, Warn};
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader};
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;
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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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<Self> {
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,
})
}
}

Expand Down Expand Up @@ -1385,11 +1382,8 @@ fn log_response(cmd: &str, resp: &Result<jsonrpc_async::Response>) {
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);
}
Expand Down
11 changes: 10 additions & 1 deletion client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -31,6 +30,8 @@ pub enum Error {
UnexpectedStructure,
/// The daemon returned an error string.
ReturnedError(String),
Auth(String),
UrlParse(url::ParseError),
}

impl From<jsonrpc_async::error::Error> for Error {
Expand All @@ -39,6 +40,12 @@ impl From<jsonrpc_async::error::Error> for Error {
}
}

impl From<url::ParseError> for Error {
fn from(e: url::ParseError) -> Error {
Error::UrlParse(e)
}
}

impl From<hex::HexToBytesError> for Error {
fn from(e: hex::HexToBytesError) -> Error {
Error::Hex(e)
Expand Down Expand Up @@ -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),
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Decodable>(hex: &str) -> Result<T> {
let buf = Vec::<u8>::from_hex(hex)?;
Expand Down
50 changes: 50 additions & 0 deletions client/src/transport.rs
Original file line number Diff line number Diff line change
@@ -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<R>(&self, req: impl serde::Serialize) -> Result<R, reqwest::Error>
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<jsonrpc_async::Response, jsonrpc_async::Error> {
Ok(self.request(r).await.map_err(|e| jsonrpc_async::Error::Transport(e.into()))?)
}

async fn send_batch(
&self,
_rs: &[jsonrpc_async::Request<'_>],
) -> Result<Vec<jsonrpc_async::Response>, 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)
}
}
}
7 changes: 3 additions & 4 deletions integration_test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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) {
Expand Down
Loading