Skip to content

Commit

Permalink
Update utility
Browse files Browse the repository at this point in the history
Various fixes and moves in utility module
  • Loading branch information
rajarshimaitra committed Sep 9, 2022
1 parent aa6a687 commit b596c9f
Showing 1 changed file with 214 additions and 45 deletions.
259 changes: 214 additions & 45 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@
//!
//! 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};
Expand Down Expand Up @@ -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",
Expand All @@ -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() {
Expand All @@ -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");
Expand All @@ -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");

Expand All @@ -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 {
Expand All @@ -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,
Expand All @@ -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 = {
Expand All @@ -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()))?,
Expand All @@ -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(),
},
),
_ => {
Expand All @@ -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,
Expand Down

0 comments on commit b596c9f

Please sign in to comment.