Skip to content

Commit

Permalink
Update backend node handling
Browse files Browse the repository at this point in the history
Previously the backend node didn't had an RPC api access. This opens
up some basic the bitcoin core rpc via a new `bdk-cli node` command.

Some other minor changes due to dependency update.
  • Loading branch information
rajarshimaitra committed May 13, 2022
1 parent f89d9d9 commit 94f8bda
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 60 deletions.
155 changes: 97 additions & 58 deletions src/bdk_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@

use bitcoin::secp256k1::Secp256k1;
use bitcoin::Network;
use std::fs;
use std::path::PathBuf;

use clap::AppSettings;
use log::{debug, error, info, warn};
use std::fs;
use std::path::PathBuf;

#[cfg(feature = "repl")]
use rustyline::error::ReadlineError;
Expand Down Expand Up @@ -51,7 +50,7 @@ use bdk::database::{AnyDatabase, AnyDatabaseConfig, BatchDatabase, ConfigurableD
use bdk::wallet::wallet_name_from_descriptor;
use bdk::Wallet;
use bdk::{bitcoin, Error};
use bdk_cli::WalletSubCommand;
use bdk_cli::{Backend, WalletSubCommand};
use bdk_cli::{CliOpts, CliSubCommand, KeySubCommand, OfflineWalletSubCommand, WalletOpts};

#[cfg(any(
Expand Down Expand Up @@ -86,6 +85,9 @@ enum ReplSubCommand {
OfflineWalletSubCommand(OfflineWalletSubCommand),
#[structopt(flatten)]
KeySubCommand(KeySubCommand),
#[cfg(feature = "regtest-node")]
#[structopt(flatten)]
NodeSubCommand(bdk_cli::NodeSubCommand),
/// Exit REPL loop
Exit,
}
Expand Down Expand Up @@ -150,6 +152,33 @@ 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`.
// TODO: Fix the directory creation flow.
#[cfg(feature = "regtest-node")]
fn prepare_node_datadir() -> 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");

if !dir.exists() {
info!("Creating home directory {}", dir.as_path().display());
fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
}

dir.push("node-data");

if !dir.exists() {
info!("Creating node directory {}", dir.as_path().display());
fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
}

Ok(dir)
}

fn open_database(wallet_opts: &WalletOpts) -> Result<AnyDatabase, Error> {
let wallet_name = wallet_opts.wallet.as_ref().expect("wallet name");
let database_path = prepare_wallet_db_dir(wallet_name)?;
Expand All @@ -174,16 +203,6 @@ fn open_database(wallet_opts: &WalletOpts) -> Result<AnyDatabase, Error> {
Ok(database)
}

#[allow(dead_code)]
// Different Backend types activated with `regtest-*` mode.
// If `regtest-*` feature not activated, then default is `None`.
enum Backend {
None,
Bitcoin { rpc_url: String, rpc_auth: String },
Electrum { electrum_url: String },
Esplora { esplora_url: String },
}

#[cfg(any(
feature = "electrum",
feature = "esplora",
Expand All @@ -198,7 +217,7 @@ fn new_blockchain(
#[cfg(feature = "electrum")]
let config = {
let url = match _backend {
Backend::Electrum { electrum_url } => electrum_url.to_owned(),
Backend::Electrum { electrsd } => electrsd.electrum_url,
_ => wallet_opts.electrum_opts.server.clone(),
};

Expand All @@ -212,13 +231,20 @@ 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 {
Backend::Esplora { esplorad } => esplorad.esplora_url.expect("Esplora url expected"),
_ => wallet_opts.electrum_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 @@ -248,10 +274,10 @@ fn new_blockchain(
#[cfg(feature = "rpc")]
let config: AnyBlockchainConfig = {
let (url, auth) = match _backend {
Backend::Bitcoin { rpc_url, rpc_auth } => (
rpc_url,
Backend::Bitcoin { bitcoind } => (
bitcoind.params.rpc_socket.to_string().clone(),
Auth::Cookie {
file: rpc_auth.into(),
file: bitcoind.params.cookie_file.clone(),
},
),
_ => {
Expand All @@ -265,16 +291,13 @@ 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 Down Expand Up @@ -319,10 +342,24 @@ fn main() {

#[cfg(feature = "regtest-node")]
let bitcoind = {
if network != Network::Regtest {
error!("Do not override default network value for `regtest-node` features");
}
let bitcoind_conf = electrsd::bitcoind::Conf::default();
// Configure node directory according to cli options
// nodes always have a persistent directory
let bitcoind_conf = {
match &cli_opts.data_dir {
None => {
let datadir = prepare_node_datadir().unwrap();
let mut conf = electrsd::bitcoind::Conf::default();
conf.staticdir = Some(datadir);
conf
}
Some(path) => {
let mut conf = electrsd::bitcoind::Conf::default();
conf.staticdir = Some(path.into());
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).unwrap()
Expand All @@ -331,14 +368,7 @@ fn main() {
#[cfg(feature = "regtest-bitcoin")]
let backend = {
Backend::Bitcoin {
rpc_url: bitcoind.params.rpc_socket.to_string(),
rpc_auth: bitcoind
.params
.cookie_file
.clone()
.into_os_string()
.into_string()
.unwrap(),
bitcoind: &bitcoind,
}
};

Expand All @@ -349,7 +379,7 @@ fn main() {
electrsd::downloaded_exe_path().expect("We should always have downloaded path");
let electrsd = electrsd::ElectrsD::with_conf(elect_exe, &bitcoind, &elect_conf).unwrap();
let backend = Backend::Electrum {
electrum_url: electrsd.electrum_url.clone(),
electrsd: &electrsd,
};
(electrsd, backend)
};
Expand All @@ -362,18 +392,15 @@ fn main() {
electrsd::downloaded_exe_path().expect("Electrsd downloaded binaries not found");
let electrsd = electrsd::ElectrsD::with_conf(elect_exe, &bitcoind, &elect_conf).unwrap();
let backend = Backend::Esplora {
esplora_url: electrsd
.esplora_url
.clone()
.expect("Esplora port not open in electrum"),
esplorad: &electrsd,
};
(electrsd, backend)
};

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

match handle_command(cli_opts, network, backend) {
match handle_command(cli_opts, network, &backend) {
Ok(result) => println!("{}", result),
Err(e) => {
match e {
Expand Down Expand Up @@ -404,8 +431,16 @@ fn maybe_descriptor_wallet_name(
Ok(wallet_opts)
}

fn handle_command(cli_opts: CliOpts, network: Network, _backend: Backend) -> Result<String, Error> {
fn handle_command(
cli_opts: CliOpts,
network: Network,
_backend: &Backend,
) -> Result<String, Error> {
let result = match cli_opts.subcommand {
#[cfg(feature = "regtest-node")]
CliSubCommand::Node { subcommand: cmd } => {
serde_json::to_string_pretty(&_backend.exec_cmd(cmd)?)
}
#[cfg(any(
feature = "electrum",
feature = "esplora",
Expand All @@ -422,7 +457,7 @@ fn handle_command(cli_opts: CliOpts, network: Network, _backend: Backend) -> Res
let wallet = new_wallet(network, &wallet_opts, database)?;
let result =
bdk_cli::handle_online_wallet_subcommand(&wallet, &blockchain, online_subcommand)?;
serde_json::to_string_pretty(&result)?
serde_json::to_string_pretty(&result)
}
CliSubCommand::Wallet {
wallet_opts,
Expand All @@ -436,13 +471,13 @@ fn handle_command(cli_opts: CliOpts, network: Network, _backend: Backend) -> Res
&wallet_opts,
offline_subcommand,
)?;
serde_json::to_string_pretty(&result)?
serde_json::to_string_pretty(&result)
}
CliSubCommand::Key {
subcommand: key_subcommand,
} => {
let result = bdk_cli::handle_key_subcommand(network, key_subcommand)?;
serde_json::to_string_pretty(&result)?
serde_json::to_string_pretty(&result)
}
#[cfg(feature = "compiler")]
CliSubCommand::Compile {
Expand Down Expand Up @@ -496,6 +531,10 @@ fn handle_command(cli_opts: CliOpts, network: Network, _backend: Backend) -> Res
debug!("repl_subcommand = {:?}", repl_subcommand);

let result = match repl_subcommand {
#[cfg( feature = "regtest-node")]
ReplSubCommand::NodeSubCommand(sub_command) => {
_backend.exec_cmd(sub_command)
}
#[cfg(any(
feature = "electrum",
feature = "esplora",
Expand Down Expand Up @@ -534,7 +573,7 @@ fn handle_command(cli_opts: CliOpts, network: Network, _backend: Backend) -> Res
}
}

"Exiting REPL".to_string()
Ok("Exiting REPL".to_string())
}
#[cfg(all(feature = "reserves", feature = "electrum"))]
CliSubCommand::ExternalReserves {
Expand All @@ -555,7 +594,7 @@ fn handle_command(cli_opts: CliOpts, network: Network, _backend: Backend) -> Res
serde_json::to_string_pretty(&result)?
}
};
Ok(result)
result.map_err(|e| e.into())
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit 94f8bda

Please sign in to comment.