From dd58a9d548d131b5812ff73be6ea9841df417fc8 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Mon, 19 Dec 2022 13:22:09 -0500 Subject: [PATCH] Refactor: separate blockchain from lib.rs --- .../docs/__pycache__/bdk.cpython-310.pyc | Bin 0 -> 1136 bytes bdk-ffi/src/blockchain.rs | 195 +++++++++++++ bdk-ffi/src/lib.rs | 268 +++++------------- bdk-ffi/src/wallet.rs | 7 +- 4 files changed, 272 insertions(+), 198 deletions(-) create mode 100644 api-docs/python/docs/__pycache__/bdk.cpython-310.pyc create mode 100644 bdk-ffi/src/blockchain.rs diff --git a/api-docs/python/docs/__pycache__/bdk.cpython-310.pyc b/api-docs/python/docs/__pycache__/bdk.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6135d943bed640e001d482edd60b1f6b506732d9 GIT binary patch literal 1136 zcmZ`%!A{#i5Z$#M+oVYgEse@8LcREa-7|_JP?3;&$R%nIma3KGb-*RqW_PWUC`hfs zU+@L|lCS-Xo;tHm0txC$Gv1k<^}L<;@^-sPV4Y1L;s;8|9}J!^3xX3c`4QYX;e^wB zu!JnkkV0&56Jk@x2DgU9Ys65wu?&b#z~mvggoKon(BL#Q>)WBVL%78o_atm^8~)%_ z&MD_2k#hE>*@bo59i=MH)2tiSdB0ib#f3c9JFHrs9ltEsC9whBR}1%OpTT4c+?*S#PpgQ!T4cpl53n5xS>3-sQ{W51YIE@NqyD#lzyf{4XP zHlod<>x8kY$=G$y%L(pVjNO#cWO?E+22W&6AT;r6voj7)V0(eDK$ECHmsr%b4YOtH z{QniS55-Rk;s$8OV4u`~La@XwaIZ68^3E3-?6yljWr, +} + +impl Blockchain { + pub(crate) fn new(blockchain_config: BlockchainConfig) -> Result { + let any_blockchain_config = match blockchain_config { + BlockchainConfig::Electrum { config } => { + AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig { + retry: config.retry, + socks5: config.socks5, + timeout: config.timeout, + url: config.url, + stop_gap: usize::try_from(config.stop_gap).unwrap(), + validate_domain: config.validate_domain, + }) + } + BlockchainConfig::Esplora { config } => { + AnyBlockchainConfig::Esplora(EsploraBlockchainConfig { + base_url: config.base_url, + proxy: config.proxy, + concurrency: config.concurrency, + stop_gap: usize::try_from(config.stop_gap).unwrap(), + timeout: config.timeout, + }) + } + BlockchainConfig::Rpc { config } => AnyBlockchainConfig::Rpc(BdkRpcConfig { + url: config.url, + auth: config.auth.into(), + network: config.network, + wallet_name: config.wallet_name, + sync_params: config.sync_params.map(|p| p.into()), + }), + }; + let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?; + Ok(Self { + blockchain_mutex: Mutex::new(blockchain), + }) + } + + pub(crate) fn get_blockchain(&self) -> MutexGuard { + self.blockchain_mutex.lock().expect("blockchain") + } + + pub(crate) fn broadcast(&self, psbt: &PartiallySignedTransaction) -> Result<(), BdkError> { + let tx = psbt.internal.lock().unwrap().clone().extract_tx(); + self.get_blockchain().broadcast(&tx) + } + + pub(crate) fn get_height(&self) -> Result { + self.get_blockchain().get_height() + } + + pub(crate) fn get_block_hash(&self, height: u32) -> Result { + self.get_blockchain() + .get_block_hash(u64::from(height)) + .map(|hash| hash.to_string()) + } +} + +/// Configuration for an ElectrumBlockchain +pub struct ElectrumConfig { + /// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with ssl:// or tcp:// and include a port + /// e.g. ssl://electrum.blockstream.info:60002 + pub url: String, + /// URL of the socks5 proxy server or a Tor service + pub socks5: Option, + /// Request retry count + pub retry: u8, + /// Request timeout (seconds) + pub timeout: Option, + /// Stop searching addresses for transactions after finding an unused gap of this length + pub stop_gap: u64, + /// Validate the domain when using SSL + pub validate_domain: bool, +} + +/// Configuration for an EsploraBlockchain +pub struct EsploraConfig { + /// Base URL of the esplora service + /// e.g. https://blockstream.info/api/ + pub base_url: String, + /// Optional URL of the proxy to use to make requests to the Esplora server + /// The string should be formatted as: ://:@host:. + /// Note that the format of this value and the supported protocols change slightly between the + /// sync version of esplora (using ureq) and the async version (using reqwest). For more + /// details check with the documentation of the two crates. Both of them are compiled with + /// the socks feature enabled. + /// The proxy is ignored when targeting wasm32. + pub proxy: Option, + /// Number of parallel requests sent to the esplora service (default: 4) + pub concurrency: Option, + /// Stop searching addresses for transactions after finding an unused gap of this length. + pub stop_gap: u64, + /// Socket timeout. + pub timeout: Option, +} + +pub enum Auth { + /// No 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: String, + }, +} + +impl From for BdkAuth { + fn from(auth: Auth) -> Self { + match auth { + Auth::None => BdkAuth::None, + Auth::UserPass { username, password } => BdkAuth::UserPass { username, password }, + Auth::Cookie { file } => BdkAuth::Cookie { + file: PathBuf::from(file), + }, + } + } +} + +/// Sync parameters for Bitcoin Core RPC. +/// +/// In general, BDK tries to sync `scriptPubKey`s cached in `Database` with +/// `scriptPubKey`s imported in the Bitcoin Core Wallet. These parameters are used for determining +/// how the `importdescriptors` RPC calls are to be made. +pub struct RpcSyncParams { + /// The minimum number of scripts to scan for on initial sync. + pub start_script_count: u64, + /// Time in unix seconds in which initial sync will start scanning from (0 to start from genesis). + pub start_time: u64, + /// Forces every sync to use `start_time` as import timestamp. + pub force_start_time: bool, + /// RPC poll rate (in seconds) to get state updates. + pub poll_rate_sec: u64, +} + +impl From for BdkRpcSyncParams { + fn from(params: RpcSyncParams) -> Self { + BdkRpcSyncParams { + start_script_count: params.start_script_count as usize, + start_time: params.start_time, + force_start_time: params.force_start_time, + poll_rate_sec: params.poll_rate_sec, + } + } +} + +/// RpcBlockchain configuration options +pub struct RpcConfig { + /// The bitcoin node url + pub url: String, + /// The bitcoin node authentication mechanism + pub auth: Auth, + /// The network we are using (it will be checked the bitcoin node network matches this) + pub network: Network, + /// The wallet name in the bitcoin node, consider using [crate::wallet::wallet_name_from_descriptor] for this + pub wallet_name: String, + /// Sync parameters + pub sync_params: Option, +} + +/// Type that can contain any of the blockchain configurations defined by the library. +pub enum BlockchainConfig { + /// Electrum client + Electrum { config: ElectrumConfig }, + /// Esplora client + Esplora { config: EsploraConfig }, + /// Bitcoin Core RPC client + Rpc { config: RpcConfig }, +} diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index 48a541c..99d66bf 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -1,21 +1,18 @@ +mod blockchain; mod psbt; mod wallet; +use crate::blockchain::{ + Auth, Blockchain, BlockchainConfig, ElectrumConfig, EsploraConfig, RpcConfig, RpcSyncParams, +}; use crate::psbt::PartiallySignedTransaction; use crate::wallet::{BumpFeeTxBuilder, TxBuilder, Wallet}; + use bdk::bitcoin::blockdata::script::Script as BdkScript; use bdk::bitcoin::secp256k1::Secp256k1; use bdk::bitcoin::util::bip32::{DerivationPath as BdkDerivationPath, Fingerprint}; use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Txid}; -use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; -use bdk::blockchain::rpc::Auth as BdkAuth; -use bdk::blockchain::GetBlockHash; -use bdk::blockchain::GetHeight; -use bdk::blockchain::{ - electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig, - rpc::RpcConfig as BdkRpcConfig, rpc::RpcSyncParams as BdkRpcSyncParams, ConfigurableBlockchain, -}; -use bdk::blockchain::{Blockchain as BdkBlockchain, Progress as BdkProgress}; +use bdk::blockchain::Progress as BdkProgress; use bdk::database::any::{SledDbConfiguration, SqliteDbConfiguration}; use bdk::descriptor::{DescriptorXKey, ExtendedDescriptor, IntoWalletDescriptor}; use bdk::keys::bip39::{Language, Mnemonic as BdkMnemonic, WordCount}; @@ -31,12 +28,11 @@ use bdk::template::{ use bdk::wallet::AddressIndex as BdkAddressIndex; use bdk::wallet::AddressInfo as BdkAddressInfo; use bdk::{Balance as BdkBalance, BlockTime, Error as BdkError, FeeRate, KeychainKind}; -use std::convert::{From, TryFrom}; +use std::convert::From; use std::fmt; use std::ops::Deref; -use std::path::PathBuf; use std::str::FromStr; -use std::sync::{Arc, Mutex, MutexGuard}; +use std::sync::{Arc, Mutex}; uniffi_macros::include_scaffolding!("bdk"); @@ -99,124 +95,6 @@ pub enum DatabaseConfig { Sqlite { config: SqliteDbConfiguration }, } -/// Configuration for an ElectrumBlockchain -pub struct ElectrumConfig { - /// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with ssl:// or tcp:// and include a port - /// e.g. ssl://electrum.blockstream.info:60002 - pub url: String, - /// URL of the socks5 proxy server or a Tor service - pub socks5: Option, - /// Request retry count - pub retry: u8, - /// Request timeout (seconds) - pub timeout: Option, - /// Stop searching addresses for transactions after finding an unused gap of this length - pub stop_gap: u64, - /// Validate the domain when using SSL - pub validate_domain: bool, -} - -/// Configuration for an EsploraBlockchain -pub struct EsploraConfig { - /// Base URL of the esplora service - /// e.g. https://blockstream.info/api/ - pub base_url: String, - /// Optional URL of the proxy to use to make requests to the Esplora server - /// The string should be formatted as: ://:@host:. - /// Note that the format of this value and the supported protocols change slightly between the - /// sync version of esplora (using ureq) and the async version (using reqwest). For more - /// details check with the documentation of the two crates. Both of them are compiled with - /// the socks feature enabled. - /// The proxy is ignored when targeting wasm32. - pub proxy: Option, - /// Number of parallel requests sent to the esplora service (default: 4) - pub concurrency: Option, - /// Stop searching addresses for transactions after finding an unused gap of this length. - pub stop_gap: u64, - /// Socket timeout. - pub timeout: Option, -} - -pub enum Auth { - /// No 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: String, - }, -} - -impl From for BdkAuth { - fn from(auth: Auth) -> Self { - match auth { - Auth::None => BdkAuth::None, - Auth::UserPass { username, password } => BdkAuth::UserPass { username, password }, - Auth::Cookie { file } => BdkAuth::Cookie { - file: PathBuf::from(file), - }, - } - } -} - -/// Sync parameters for Bitcoin Core RPC. -/// -/// In general, BDK tries to sync `scriptPubKey`s cached in `Database` with -/// `scriptPubKey`s imported in the Bitcoin Core Wallet. These parameters are used for determining -/// how the `importdescriptors` RPC calls are to be made. -pub struct RpcSyncParams { - /// The minimum number of scripts to scan for on initial sync. - pub start_script_count: u64, - /// Time in unix seconds in which initial sync will start scanning from (0 to start from genesis). - pub start_time: u64, - /// Forces every sync to use `start_time` as import timestamp. - pub force_start_time: bool, - /// RPC poll rate (in seconds) to get state updates. - pub poll_rate_sec: u64, -} - -impl From for BdkRpcSyncParams { - fn from(params: RpcSyncParams) -> Self { - BdkRpcSyncParams { - start_script_count: params.start_script_count as usize, - start_time: params.start_time, - force_start_time: params.force_start_time, - poll_rate_sec: params.poll_rate_sec, - } - } -} - -/// RpcBlockchain configuration options -pub struct RpcConfig { - /// The bitcoin node url - pub url: String, - /// The bitcoin node authentication mechanism - pub auth: Auth, - /// The network we are using (it will be checked the bitcoin node network matches this) - pub network: Network, - /// The wallet name in the bitcoin node, consider using [crate::wallet::wallet_name_from_descriptor] for this - pub wallet_name: String, - /// Sync parameters - pub sync_params: Option, -} - -/// Type that can contain any of the blockchain configurations defined by the library. -pub enum BlockchainConfig { - /// Electrum client - Electrum { config: ElectrumConfig }, - /// Esplora client - Esplora { config: EsploraConfig }, - /// Bitcoin Core RPC client - Rpc { config: RpcConfig }, -} - /// A wallet transaction #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct TransactionDetails { @@ -250,71 +128,71 @@ impl From<&bdk::TransactionDetails> for TransactionDetails { } } -struct Blockchain { - blockchain_mutex: Mutex, -} - -impl Blockchain { - fn new(blockchain_config: BlockchainConfig) -> Result { - let any_blockchain_config = match blockchain_config { - BlockchainConfig::Electrum { config } => { - AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig { - retry: config.retry, - socks5: config.socks5, - timeout: config.timeout, - url: config.url, - stop_gap: usize::try_from(config.stop_gap).unwrap(), - validate_domain: config.validate_domain, - }) - } - BlockchainConfig::Esplora { config } => { - AnyBlockchainConfig::Esplora(EsploraBlockchainConfig { - base_url: config.base_url, - proxy: config.proxy, - concurrency: config.concurrency, - stop_gap: usize::try_from(config.stop_gap).unwrap(), - timeout: config.timeout, - }) - } - BlockchainConfig::Rpc { config } => AnyBlockchainConfig::Rpc(BdkRpcConfig { - url: config.url, - auth: config.auth.into(), - network: config.network, - wallet_name: config.wallet_name, - sync_params: config.sync_params.map(|p| p.into()), - }), - }; - let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?; - Ok(Self { - blockchain_mutex: Mutex::new(blockchain), - }) - } - - fn get_blockchain(&self) -> MutexGuard { - self.blockchain_mutex.lock().expect("blockchain") - } - - fn broadcast(&self, psbt: &PartiallySignedTransaction) -> Result<(), BdkError> { - let tx = psbt.internal.lock().unwrap().clone().extract_tx(); - self.get_blockchain().broadcast(&tx) - } - - fn estimate_fee(&self, target: u64) -> Result, BdkError> { - let result: Result = - self.get_blockchain().estimate_fee(target as usize); - result.map(Arc::new) - } - - fn get_height(&self) -> Result { - self.get_blockchain().get_height() - } - - fn get_block_hash(&self, height: u32) -> Result { - self.get_blockchain() - .get_block_hash(u64::from(height)) - .map(|hash| hash.to_string()) - } -} +// struct Blockchain { +// blockchain_mutex: Mutex, +// } +// +// impl Blockchain { +// fn new(blockchain_config: BlockchainConfig) -> Result { +// let any_blockchain_config = match blockchain_config { +// BlockchainConfig::Electrum { config } => { +// AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig { +// retry: config.retry, +// socks5: config.socks5, +// timeout: config.timeout, +// url: config.url, +// stop_gap: usize::try_from(config.stop_gap).unwrap(), +// validate_domain: config.validate_domain, +// }) +// } +// BlockchainConfig::Esplora { config } => { +// AnyBlockchainConfig::Esplora(EsploraBlockchainConfig { +// base_url: config.base_url, +// proxy: config.proxy, +// concurrency: config.concurrency, +// stop_gap: usize::try_from(config.stop_gap).unwrap(), +// timeout: config.timeout, +// }) +// } +// BlockchainConfig::Rpc { config } => AnyBlockchainConfig::Rpc(BdkRpcConfig { +// url: config.url, +// auth: config.auth.into(), +// network: config.network, +// wallet_name: config.wallet_name, +// sync_params: config.sync_params.map(|p| p.into()), +// }), +// }; +// let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?; +// Ok(Self { +// blockchain_mutex: Mutex::new(blockchain), +// }) +// } +// +// fn get_blockchain(&self) -> MutexGuard { +// self.blockchain_mutex.lock().expect("blockchain") +// } +// +// fn broadcast(&self, psbt: &PartiallySignedTransaction) -> Result<(), BdkError> { +// let tx = psbt.internal.lock().unwrap().clone().extract_tx(); +// self.get_blockchain().broadcast(&tx) +// } +// +// fn estimate_fee(&self, target: u64) -> Result, BdkError> { +// let result: Result = +// self.get_blockchain().estimate_fee(target as usize); +// result.map(Arc::new) +// } +// +// fn get_height(&self) -> Result { +// self.get_blockchain().get_height() +// } +// +// fn get_block_hash(&self, height: u32) -> Result { +// self.get_blockchain() +// .get_block_hash(u64::from(height)) +// .map(|hash| hash.to_string()) +// } +// } /// A reference to a transaction output. #[derive(Clone, Debug, PartialEq, Eq, Hash)] diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index 6cf06ef..78ea384 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -11,11 +11,12 @@ use std::ops::Deref; use std::str::FromStr; use std::sync::{Arc, Mutex, MutexGuard}; +use crate::blockchain::Blockchain; use crate::psbt::PartiallySignedTransaction; use crate::{ - AddressIndex, AddressInfo, Balance, Blockchain, DatabaseConfig, Descriptor, LocalUtxo, - NetworkLocalUtxo, OutPoint, Progress, ProgressHolder, RbfValue, Script, ScriptAmount, - TransactionDetails, TxBuilderResult, + AddressIndex, AddressInfo, Balance, DatabaseConfig, Descriptor, LocalUtxo, NetworkLocalUtxo, + OutPoint, Progress, ProgressHolder, RbfValue, Script, ScriptAmount, TransactionDetails, + TxBuilderResult, }; #[derive(Debug)]