diff --git a/CHANGELOG.md b/CHANGELOG.md index fbe0c697..d94f0526 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Added `RpcBlockchain` in the `AnyBlockchain` struct to allow using Rpc backend where `AnyBlockchain` is used (eg `bdk-cli`) + ### Wallet - Removed and replaced `set_single_recipient` with more general `drain_to` and replaced `maintain_single_recipient` with `allow_shrinking`. diff --git a/src/blockchain/any.rs b/src/blockchain/any.rs index d88c3b8c..549f5153 100644 --- a/src/blockchain/any.rs +++ b/src/blockchain/any.rs @@ -94,6 +94,8 @@ macro_rules! impl_inner_method { AnyBlockchain::Esplora(inner) => inner.$name( $($args, )* ), #[cfg(feature = "compact_filters")] AnyBlockchain::CompactFilters(inner) => inner.$name( $($args, )* ), + #[cfg(feature = "rpc")] + AnyBlockchain::Rpc(inner) => inner.$name( $($args, )* ), } } } @@ -116,6 +118,10 @@ pub enum AnyBlockchain { #[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))] /// Compact filters client CompactFilters(compact_filters::CompactFiltersBlockchain), + #[cfg(feature = "rpc")] + #[cfg_attr(docsrs, doc(cfg(feature = "rpc")))] + /// RPC client + Rpc(rpc::RpcBlockchain), } #[maybe_async] @@ -157,6 +163,7 @@ impl Blockchain for AnyBlockchain { impl_from!(electrum::ElectrumBlockchain, AnyBlockchain, Electrum, #[cfg(feature = "electrum")]); impl_from!(esplora::EsploraBlockchain, AnyBlockchain, Esplora, #[cfg(feature = "esplora")]); impl_from!(compact_filters::CompactFiltersBlockchain, AnyBlockchain, CompactFilters, #[cfg(feature = "compact_filters")]); +impl_from!(rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]); /// Type that can contain any of the blockchain configurations defined by the library /// @@ -206,6 +213,10 @@ pub enum AnyBlockchainConfig { #[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))] /// Compact filters client CompactFilters(compact_filters::CompactFiltersBlockchainConfig), + #[cfg(feature = "rpc")] + #[cfg_attr(docsrs, doc(cfg(feature = "rpc")))] + /// RPC client configuration + Rpc(rpc::RpcConfig), } impl ConfigurableBlockchain for AnyBlockchain { @@ -225,6 +236,10 @@ impl ConfigurableBlockchain for AnyBlockchain { AnyBlockchainConfig::CompactFilters(inner) => AnyBlockchain::CompactFilters( compact_filters::CompactFiltersBlockchain::from_config(inner)?, ), + #[cfg(feature = "rpc")] + AnyBlockchainConfig::Rpc(inner) => { + AnyBlockchain::Rpc(rpc::RpcBlockchain::from_config(inner)?) + } }) } } @@ -232,3 +247,4 @@ impl ConfigurableBlockchain for AnyBlockchain { impl_from!(electrum::ElectrumBlockchainConfig, AnyBlockchainConfig, Electrum, #[cfg(feature = "electrum")]); impl_from!(esplora::EsploraBlockchainConfig, AnyBlockchainConfig, Esplora, #[cfg(feature = "esplora")]); impl_from!(compact_filters::CompactFiltersBlockchainConfig, AnyBlockchainConfig, CompactFilters, #[cfg(feature = "compact_filters")]); +impl_from!(rpc::RpcConfig, AnyBlockchainConfig, Rpc, #[cfg(feature = "rpc")]); diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 6a382b9a..0383497c 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -30,9 +30,19 @@ use crate::FeeRate; #[cfg(any(feature = "electrum", feature = "esplora"))] pub(crate) mod utils; -#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))] +#[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "compact_filters", + feature = "rpc" +))] pub mod any; -#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))] +#[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "compact_filters", + feature = "rpc" +))] pub use any::{AnyBlockchain, AnyBlockchainConfig}; #[cfg(feature = "electrum")] diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs index 30f8cf34..fd06dcf1 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -18,10 +18,12 @@ //! ## Example //! //! ```no_run -//! # use bdk::blockchain::{RpcConfig, RpcBlockchain, ConfigurableBlockchain}; +//! # use bdk::blockchain::{RpcConfig, RpcBlockchain, ConfigurableBlockchain, rpc::Auth}; //! let config = RpcConfig { //! url: "127.0.0.1:18332".to_string(), -//! auth: bitcoincore_rpc::Auth::CookieFile("/home/user/.bitcoin/.cookie".into()), +//! auth: Auth::Cookie { +//! file: "/home/user/.bitcoin/.cookie".into(), +//! }, //! network: bdk::bitcoin::Network::Testnet, //! wallet_name: "wallet_name".to_string(), //! skip_blocks: None, @@ -41,10 +43,12 @@ use bitcoincore_rpc::json::{ ImportMultiRequestScriptPubkey, ImportMultiRescanSince, }; use bitcoincore_rpc::jsonrpc::serde_json::Value; -use bitcoincore_rpc::{Auth, Client, RpcApi}; +use bitcoincore_rpc::Auth as RpcAuth; +use bitcoincore_rpc::{Client, RpcApi}; use log::debug; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; use std::str::FromStr; /// The main struct for RPC backend implementing the [crate::blockchain::Blockchain] trait @@ -64,7 +68,7 @@ pub struct RpcBlockchain { } /// RpcBlockchain configuration options -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct RpcConfig { /// The bitcoin node url pub url: String, @@ -78,6 +82,39 @@ pub struct RpcConfig { pub skip_blocks: Option, } +/// This struct is equivalent to [bitcoincore_rpc::Auth] but it implements [serde::Serialize] +/// To be removed once upstream equivalent is implementing Serialize (json serialization format +/// should be the same) https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/181 +#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(untagged)] +pub enum Auth { + /// None authentication + None, + /// Authentication with username and password, usually [Auth::Cookie] should be preferred + UserPass { + /// Username + username: String, + /// Password + password: String, + }, + /// Authentication with a cookie file + Cookie { + /// Cookie file + file: PathBuf, + }, +} + +impl From for RpcAuth { + fn from(auth: Auth) -> Self { + match auth { + Auth::None => RpcAuth::None, + Auth::UserPass { username, password } => RpcAuth::UserPass(username, password), + Auth::Cookie { file } => RpcAuth::CookieFile(file), + } + } +} + impl RpcBlockchain { fn get_node_synced_height(&self) -> Result { let info = self.client.get_address_info(&self._storage_address)?; @@ -320,7 +357,7 @@ impl ConfigurableBlockchain for RpcBlockchain { let wallet_url = format!("{}/wallet/{}", config.url, &wallet_name); debug!("connecting to {} auth:{:?}", wallet_url, config.auth); - let client = Client::new(wallet_url, config.auth.clone())?; + let client = Client::new(wallet_url, config.auth.clone().into())?; let loaded_wallets = client.list_wallets()?; if loaded_wallets.contains(&wallet_name) { debug!("wallet already loaded {:?}", wallet_name); @@ -427,7 +464,7 @@ crate::bdk_blockchain_tests! { fn test_instance(test_client: &TestClient) -> RpcBlockchain { let config = RpcConfig { url: test_client.bitcoind.rpc_url(), - auth: Auth::CookieFile(test_client.bitcoind.params.cookie_file.clone()), + auth: Auth::Cookie { file: test_client.bitcoind.params.cookie_file.clone() }, network: Network::Regtest, wallet_name: format!("client-wallet-test-{:?}", std::time::SystemTime::now() ), skip_blocks: None,