Skip to content

Commit

Permalink
Update utils
Browse files Browse the repository at this point in the history
utils.rs updated with following:
 - utility function moved from handlers added here.
 - data directory creation workflow updated.
 - a new `new_backend()` function takes care of creation of backends.
 - created backend then used for `new_blockchain()` function.
  • Loading branch information
rajarshimaitra committed Jun 26, 2022
1 parent c11ae7f commit 1741f29
Showing 1 changed file with 221 additions and 31 deletions.
252 changes: 221 additions & 31 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
use std::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;

#[cfg(any(
feature = "electrum",
feature = "esplora",
Expand Down Expand Up @@ -79,6 +85,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,13 +105,47 @@ 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> {
/// prepare bdk-cli home directory
pub(crate) fn prepare_home_dir() -> 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()))?,
Expand All @@ -116,6 +157,13 @@ pub(crate) fn prepare_home_wallet_dir(wallet_name: &str) -> Result<PathBuf, Erro
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) -> Result<PathBuf, Error> {
let mut dir = prepare_home_dir()?;

dir.push(wallet_name);

if !dir.exists() {
Expand All @@ -127,8 +175,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) -> Result<PathBuf, Error> {
let mut db_dir = prepare_wallet_dir(wallet_name)?;

#[cfg(feature = "key-value-db")]
db_dir.push("wallet.sled");
Expand All @@ -147,8 +195,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) -> Result<PathBuf, Error> {
let mut bc_dir = prepare_wallet_dir(wallet_name)?;

bc_dir.push("compact_filters");

Expand All @@ -163,6 +211,40 @@ 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 `~/.bdk-bitcoin/node-data/regtest/wallets`.
#[cfg(feature = "regtest-node")]
pub(crate) fn prepare_bitcoind_datadir() -> Result<PathBuf, Error> {
let mut dir = prepare_home_dir()?;

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 `~/.bdk-bitcoin/node-data/regtest/wallets`.
#[cfg(feature = "regtest-electrum")]
pub(crate) fn prepare_electrum_datadir() -> Result<PathBuf, Error> {
let mut dir = prepare_home_dir()?;

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> {
let wallet_name = wallet_opts.wallet.as_ref().expect("wallet name");
Expand All @@ -188,6 +270,107 @@ pub(crate) fn open_database(wallet_opts: &WalletOpts) -> Result<AnyDatabase, Err
Ok(database)
}

pub(crate) fn new_backend(datadir: Option<PathBuf>) -> Result<Backend, Error> {
#[cfg(feature = "regtest-node")]
let bitcoind = {
// Configure node directory according to cli options
// nodes always have a persistent directory
let bitcoind_conf = {
match &datadir {
None => {
let datadir = prepare_bitcoind_datadir()?;
let mut conf = electrsd::bitcoind::Conf::default();
conf.staticdir = Some(datadir);
conf
}
Some(path) => {
let mut path = path.to_owned();
path.push("bitcoind");
let mut conf = electrsd::bitcoind::Conf::default();
conf.staticdir = Some(path);
conf
}
}
};
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 = {
Backend::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 elect_conf = {
match &datadir {
None => {
let datadir = prepare_electrum_datadir()?;
let mut conf = electrsd::Conf::default();
conf.staticdir = Some(datadir);
conf
}
Some(path) => {
let mut path = path.to_owned();
path.push("electrsd");
let mut conf = electrsd::Conf::default();
conf.staticdir = Some(path);
conf
}
}
};
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()))?;
Backend::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();
Backend::Esplora {
bitcoind: Box::new(bitcoind),
esplorad: Box::new(electrsd),
}
};

#[cfg(not(feature = "regtest-node"))]
let backend = Backend::None;

Ok(backend)
}

#[cfg(any(
feature = "electrum",
feature = "esplora",
Expand All @@ -199,17 +382,18 @@ pub(crate) fn open_database(wallet_opts: &WalletOpts) -> Result<AnyDatabase, Err
pub(crate) fn new_blockchain(
_network: Network,
wallet_opts: &WalletOpts,
_backend: &Backend,
backend: &Backend,
) -> Result<AnyBlockchain, Error> {
#[cfg(feature = "electrum")]
let config = {
let url = match _backend {
Backend::Electrum { electrum_url } => electrum_url.to_owned(),
_ => wallet_opts.electrum_opts.server.clone(),
let url = match backend {
#[cfg(feature = "regtest-electrum")]
Backend::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 @@ -218,13 +402,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"))]
Backend::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 Down Expand Up @@ -253,11 +445,12 @@ pub(crate) fn new_blockchain(

#[cfg(feature = "rpc")]
let config: AnyBlockchainConfig = {
let (url, auth) = match _backend {
Backend::Bitcoin { rpc_url, rpc_auth } => (
rpc_url,
let (url, auth) = match backend {
#[cfg(feature = "regtest-node")]
Backend::Bitcoin { bitcoind } => (
bitcoind.params.rpc_socket.to_string(),
Auth::Cookie {
file: rpc_auth.into(),
file: bitcoind.params.cookie_file.clone(),
},
),
_ => {
Expand All @@ -271,16 +464,13 @@ 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 wallet_name = wallet_opts
.wallet
.to_owned()
.expect("Wallet name should be available this level");

let rpc_url = "http://".to_string() + &url;

Expand All @@ -295,7 +485,7 @@ pub(crate) fn new_blockchain(
AnyBlockchainConfig::Rpc(rpc_config)
};

Ok(AnyBlockchain::from_config(&config)?)
AnyBlockchain::from_config(&config)
}

/// Create a new wallet from given wallet configuration options
Expand Down

0 comments on commit 1741f29

Please sign in to comment.