]> Untitled Git - bdk-cli/commitdiff
Update utility
authorrajarshimaitra <rajarshi149@gmail.com>
Fri, 9 Sep 2022 08:00:15 +0000 (13:30 +0530)
committerrajarshimaitra <rajarshi149@gmail.com>
Fri, 9 Sep 2022 15:30:24 +0000 (21:00 +0530)
Various fixes and moves in utility module

src/utils.rs

index aecaad548d9764d7c1c58c7a04dcc4922c95a44c..8333ce5e9521e7ff487341b0cd210ed1e964ea03 100644 (file)
 //!
 //! This module includes all the utility tools used by the App.
 
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use std::str::FromStr;
 
+#[cfg(all(feature = "reserves", feature = "electrum"))]
+use bdk::electrum_client::{Client, ElectrumApi};
+
+#[cfg(all(feature = "reserves", feature = "electrum"))]
+use bdk::bitcoin::TxOut;
+
 use crate::commands::WalletOpts;
-#[cfg(any(
-    feature = "electrum",
-    feature = "esplora",
-    feature = "compact_filters",
-    feature = "rpc"
-))]
 use crate::nodes::Nodes;
 use bdk::bitcoin::secp256k1::Secp256k1;
 use bdk::bitcoin::{Address, Network, OutPoint, Script};
@@ -79,6 +79,7 @@ pub(crate) fn parse_recipient(s: &str) -> Result<(Script, u64), String> {
 
     Ok((addr.script_pubkey(), val))
 }
+
 #[cfg(any(
     feature = "electrum",
     feature = "compact_filters",
@@ -98,24 +99,73 @@ pub(crate) fn parse_proxy_auth(s: &str) -> Result<(String, String), String> {
     Ok((user, passwd))
 }
 
+#[cfg(all(feature = "reserves", feature = "electrum"))]
+pub fn get_outpoints_for_address(
+    address: Address,
+    client: &Client,
+    max_confirmation_height: Option<usize>,
+) -> Result<Vec<(OutPoint, TxOut)>, Error> {
+    let unspents = client
+        .script_list_unspent(&address.script_pubkey())
+        .map_err(Error::Electrum)?;
+
+    unspents
+        .iter()
+        .filter(|utxo| {
+            utxo.height > 0 && utxo.height <= max_confirmation_height.unwrap_or(usize::MAX)
+        })
+        .map(|utxo| {
+            let tx = match client.transaction_get(&utxo.tx_hash) {
+                Ok(tx) => tx,
+                Err(e) => {
+                    return Err(e).map_err(Error::Electrum);
+                }
+            };
+
+            Ok((
+                OutPoint {
+                    txid: utxo.tx_hash,
+                    vout: utxo.tx_pos as u32,
+                },
+                tx.output[utxo.tx_pos].clone(),
+            ))
+        })
+        .collect()
+}
+
 /// Parse a outpoint (Txid:Vout) argument from cli input
 pub(crate) fn parse_outpoint(s: &str) -> Result<OutPoint, String> {
     OutPoint::from_str(s).map_err(|e| e.to_string())
 }
 
-/// prepare bdk_cli home and wallet directory
-pub(crate) fn prepare_home_wallet_dir(wallet_name: &str) -> Result<PathBuf, Error> {
-    let mut dir = PathBuf::new();
-    dir.push(
-        &dirs_next::home_dir().ok_or_else(|| Error::Generic("home dir not found".to_string()))?,
-    );
-    dir.push(".bdk-bitcoin");
+/// prepare bdk-cli home directory
+///
+/// This function is called to check if [`crate::CliOpts`] datadir is set.
+/// If not the default home directory is created at `~/.bdk-bitcoin
+pub(crate) fn prepare_home_dir(home_path: Option<PathBuf>) -> Result<PathBuf, Error> {
+    let dir = home_path.unwrap_or_else(|| {
+        let mut dir = PathBuf::new();
+        dir.push(
+            &dirs_next::home_dir()
+                .ok_or_else(|| Error::Generic("home dir not found".to_string()))
+                .unwrap(),
+        );
+        dir.push(".bdk-bitcoin");
+        dir
+    });
 
     if !dir.exists() {
         log::info!("Creating home directory {}", dir.as_path().display());
         std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
     }
 
+    Ok(dir)
+}
+
+/// prepare bdk_cli wallet directory
+fn prepare_wallet_dir(wallet_name: &str, home_path: &Path) -> Result<PathBuf, Error> {
+    let mut dir = home_path.to_owned();
+
     dir.push(wallet_name);
 
     if !dir.exists() {
@@ -127,8 +177,8 @@ pub(crate) fn prepare_home_wallet_dir(wallet_name: &str) -> Result<PathBuf, Erro
 }
 
 /// Prepare wallet database directory
-pub(crate) fn prepare_wallet_db_dir(wallet_name: &str) -> Result<PathBuf, Error> {
-    let mut db_dir = prepare_home_wallet_dir(wallet_name)?;
+fn prepare_wallet_db_dir(wallet_name: &str, home_path: &Path) -> Result<PathBuf, Error> {
+    let mut db_dir = prepare_wallet_dir(wallet_name, home_path)?;
 
     #[cfg(feature = "key-value-db")]
     db_dir.push("wallet.sled");
@@ -147,8 +197,8 @@ pub(crate) fn prepare_wallet_db_dir(wallet_name: &str) -> Result<PathBuf, Error>
 
 /// Prepare blockchain data directory (for compact filters)
 #[cfg(feature = "compact_filters")]
-pub(crate) fn prepare_bc_dir(wallet_name: &str) -> Result<PathBuf, Error> {
-    let mut bc_dir = prepare_home_wallet_dir(wallet_name)?;
+fn prepare_bc_dir(wallet_name: &str, home_path: &Path) -> Result<PathBuf, Error> {
+    let mut bc_dir = prepare_wallet_dir(wallet_name, home_path)?;
 
     bc_dir.push("compact_filters");
 
@@ -163,10 +213,47 @@ pub(crate) fn prepare_bc_dir(wallet_name: &str) -> Result<PathBuf, Error> {
     Ok(bc_dir)
 }
 
+// We create only a global single node directory. Because multiple
+// wallets can access the same node datadir, and they will have separate
+// wallet names in `<home_path>/bitcoind/regtest/wallets`.
+#[cfg(feature = "regtest-node")]
+pub(crate) fn prepare_bitcoind_datadir(home_path: &Path) -> Result<PathBuf, Error> {
+    let mut dir = home_path.to_owned();
+
+    dir.push("bitcoind");
+
+    if !dir.exists() {
+        log::info!("Creating node directory {}", dir.as_path().display());
+        std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
+    }
+
+    Ok(dir)
+}
+
+// We create only a global single node directory. Because multiple
+// wallets can access the same node datadir, and they will have separate
+// wallet names in `<home_path>/electrsd/regtest/wallets`.
+#[cfg(feature = "regtest-electrum")]
+pub(crate) fn prepare_electrum_datadir(home_path: &Path) -> Result<PathBuf, Error> {
+    let mut dir = home_path.to_owned();
+
+    dir.push("electrsd");
+
+    if !dir.exists() {
+        log::info!("Creating node directory {}", dir.as_path().display());
+        std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
+    }
+
+    Ok(dir)
+}
+
 /// Open the wallet database
-pub(crate) fn open_database(wallet_opts: &WalletOpts) -> Result<AnyDatabase, Error> {
+pub(crate) fn open_database(
+    wallet_opts: &WalletOpts,
+    home_path: &Path,
+) -> Result<AnyDatabase, Error> {
     let wallet_name = wallet_opts.wallet.as_ref().expect("wallet name");
-    let database_path = prepare_wallet_db_dir(wallet_name)?;
+    let database_path = prepare_wallet_db_dir(wallet_name, home_path)?;
 
     #[cfg(feature = "key-value-db")]
     let config = AnyDatabaseConfig::Sled(SledDbConfiguration {
@@ -191,28 +278,104 @@ pub(crate) fn open_database(wallet_opts: &WalletOpts) -> Result<AnyDatabase, Err
     Ok(database)
 }
 
+#[allow(dead_code)]
+pub(crate) fn new_backend(_datadir: &Path) -> Result<Nodes, Error> {
+    #[cfg(feature = "regtest-node")]
+    let bitcoind = {
+        // Configure node directory according to cli options
+        // nodes always have a persistent directory
+        let datadir = prepare_bitcoind_datadir(_datadir)?;
+        let mut bitcoind_conf = electrsd::bitcoind::Conf::default();
+        bitcoind_conf.staticdir = Some(datadir);
+        let bitcoind_exe = electrsd::bitcoind::downloaded_exe_path()
+            .expect("We should always have downloaded path");
+        electrsd::bitcoind::BitcoinD::with_conf(bitcoind_exe, &bitcoind_conf)
+            .map_err(|e| Error::Generic(e.to_string()))?
+    };
+
+    #[cfg(feature = "regtest-bitcoin")]
+    let backend = {
+        Nodes::Bitcoin {
+            bitcoind: Box::new(bitcoind),
+        }
+    };
+
+    #[cfg(feature = "regtest-electrum")]
+    let backend = {
+        // Configure node directory according to cli options
+        // nodes always have a persistent directory
+        let datadir = prepare_electrum_datadir(_datadir)?;
+        let mut elect_conf = electrsd::Conf::default();
+        elect_conf.staticdir = Some(datadir);
+        let elect_exe =
+            electrsd::downloaded_exe_path().expect("We should always have downloaded path");
+        let electrsd = electrsd::ElectrsD::with_conf(elect_exe, &bitcoind, &elect_conf)
+            .map_err(|e| Error::Generic(e.to_string()))?;
+        Nodes::Electrum {
+            bitcoind: Box::new(bitcoind),
+            electrsd: Box::new(electrsd),
+        }
+    };
+
+    #[cfg(any(feature = "regtest-esplora-ureq", feature = "regtest-esplora-reqwest"))]
+    let backend = {
+        // Configure node directory according to cli options
+        // nodes always have a persistent directory
+        let mut elect_conf = {
+            match _datadir {
+                None => {
+                    let datadir = utils::prepare_electrum_datadir().unwrap();
+                    let mut conf = electrsd::Conf::default();
+                    conf.staticdir = Some(_datadir);
+                    conf
+                }
+                Some(path) => {
+                    let mut conf = electrsd::Conf::default();
+                    conf.staticdir = Some(path.into());
+                    conf
+                }
+            }
+        };
+        elect_conf.http_enabled = true;
+        let elect_exe =
+            electrsd::downloaded_exe_path().expect("Electrsd downloaded binaries not found");
+        let electrsd = electrsd::ElectrsD::with_conf(elect_exe, &bitcoind, &elect_conf).unwrap();
+        Nodes::Esplora {
+            bitcoind: Box::new(bitcoind),
+            esplorad: Box::new(electrsd),
+        }
+    };
+
+    #[cfg(not(feature = "regtest-node"))]
+    let backend = Nodes::None;
+
+    Ok(backend)
+}
+
 #[cfg(any(
     feature = "electrum",
     feature = "esplora",
     feature = "compact_filters",
     feature = "rpc"
 ))]
-/// Create a new blockchain for a given [Backend] if available
+/// Create a new blockchain for a given [Nodes] if available
 /// Or else create one from the wallet configuration options
 pub(crate) fn new_blockchain(
     _network: Network,
     wallet_opts: &WalletOpts,
     _backend: &Nodes,
+    _home_dir: &Path,
 ) -> Result<AnyBlockchain, Error> {
     #[cfg(feature = "electrum")]
     let config = {
         let url = match _backend {
-            Nodes::Electrum { electrum_url } => electrum_url.to_owned(),
-            _ => wallet_opts.electrum_opts.server.clone(),
+            #[cfg(feature = "regtest-electrum")]
+            Nodes::Electrum { electrsd, .. } => &electrsd.electrum_url,
+            _ => &wallet_opts.electrum_opts.server,
         };
 
         AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
-            url,
+            url: url.to_owned(),
             socks5: wallet_opts.proxy_opts.proxy.clone(),
             retry: wallet_opts.proxy_opts.retries,
             timeout: wallet_opts.electrum_opts.timeout,
@@ -221,13 +384,21 @@ pub(crate) fn new_blockchain(
     };
 
     #[cfg(feature = "esplora")]
-    let config = AnyBlockchainConfig::Esplora(EsploraBlockchainConfig {
-        base_url: wallet_opts.esplora_opts.server.clone(),
-        timeout: Some(wallet_opts.esplora_opts.timeout),
-        concurrency: Some(wallet_opts.esplora_opts.conc),
-        stop_gap: wallet_opts.esplora_opts.stop_gap,
-        proxy: wallet_opts.proxy_opts.proxy.clone(),
-    });
+    let config = {
+        let url = match _backend {
+            #[cfg(any(feature = "regtest-esplora-ureq", feature = "regtest-esplora-reqwest"))]
+            Nodes::Esplora { esplorad } => esplorad.esplora_url.expect("Esplora url expected"),
+            _ => wallet_opts.esplora_opts.server.clone(),
+        };
+
+        AnyBlockchainConfig::Esplora(EsploraBlockchainConfig {
+            base_url: url,
+            timeout: Some(wallet_opts.esplora_opts.timeout),
+            concurrency: Some(wallet_opts.esplora_opts.conc),
+            stop_gap: wallet_opts.esplora_opts.stop_gap,
+            proxy: wallet_opts.proxy_opts.proxy.clone(),
+        })
+    };
 
     #[cfg(feature = "compact_filters")]
     let config = {
@@ -246,7 +417,7 @@ pub(crate) fn new_blockchain(
         AnyBlockchainConfig::CompactFilters(CompactFiltersBlockchainConfig {
             peers,
             network: _network,
-            storage_dir: prepare_bc_dir(wallet_name)?
+            storage_dir: prepare_bc_dir(wallet_name, _home_dir)?
                 .into_os_string()
                 .into_string()
                 .map_err(|_| Error::Generic("Internal OS_String conversion error".to_string()))?,
@@ -257,10 +428,11 @@ pub(crate) fn new_blockchain(
     #[cfg(feature = "rpc")]
     let config: AnyBlockchainConfig = {
         let (url, auth) = match _backend {
-            Nodes::Bitcoin { rpc_url, rpc_auth } => (
-                rpc_url,
+            #[cfg(feature = "regtest-node")]
+            Nodes::Bitcoin { bitcoind } => (
+                bitcoind.params.rpc_socket.to_string(),
                 Auth::Cookie {
-                    file: rpc_auth.into(),
+                    file: bitcoind.params.cookie_file.clone(),
                 },
             ),
             _ => {
@@ -274,18 +446,15 @@ pub(crate) fn new_blockchain(
                         password: wallet_opts.rpc_opts.basic_auth.1.clone(),
                     }
                 };
-                (&wallet_opts.rpc_opts.address, auth)
+                (wallet_opts.rpc_opts.address.clone(), auth)
             }
         };
-        // Use deterministic wallet name derived from descriptor
-        let wallet_name = wallet_name_from_descriptor(
-            &wallet_opts.descriptor[..],
-            wallet_opts.change_descriptor.as_deref(),
-            _network,
-            &Secp256k1::new(),
-        )?;
-
-        let rpc_url = "http://".to_string() + url;
+        let wallet_name = wallet_opts
+            .wallet
+            .to_owned()
+            .expect("Wallet name should be available this level");
+
+        let rpc_url = "http://".to_string() + &url;
 
         let rpc_config = RpcConfig {
             url: rpc_url,