Skip to content

Commit

Permalink
Draft RPC backend implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
rajarshimaitra committed Aug 3, 2021
1 parent 378b33a commit b2c48fc
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 24 deletions.
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ readme = "README.md"
license = "MIT"

[dependencies]
bdk = { version = "^0.7", default-features = false, features = ["all-keys"]}
#bdk = { version = "^0.7", default-features = false, features = ["all-keys"]}
bdk = { git = "https://github.com/bitcoindevkit/bdk", barnch = "master", default-features = false, features = ["all-keys"]}
bdk-macros = "^0.4"
structopt = "^0.3"
serde_json = { version = "^1.0" }
Expand All @@ -31,9 +32,11 @@ default = ["repl", "electrum"]
repl = ["bdk/key-value-db", "clap", "dirs-next", "env_logger", "regex", "rustyline"]
electrum = ["bdk/electrum"]
esplora = ["bdk/esplora"]
rpc = ["bdk/rpc"]
compact_filters = ["bdk/compact_filters"]
compiler = ["bdk/compiler"]
async-interface = ["bdk/async-interface"]
compact_filters = ["bdk/compact_filters"]


[[bin]]
name = "bdk-cli"
Expand Down
52 changes: 47 additions & 5 deletions src/bdk_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
use std::fs;
use std::path::PathBuf;

#[cfg(feature = "rpc")]
use bitcoin::secp256k1::Secp256k1;
use bitcoin::Network;

use clap::AppSettings;
use log::{debug, error, info, warn};
use rustyline::error::ReadlineError;
Expand All @@ -36,9 +39,15 @@ use structopt::StructOpt;
use bdk::blockchain::compact_filters::{BitcoinPeerConfig, CompactFiltersBlockchainConfig};
#[cfg(feature = "esplora")]
use bdk::blockchain::esplora::EsploraBlockchainConfig;
use bdk::blockchain::{
AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain, ElectrumBlockchainConfig,
};

#[cfg(feature = "electrum")]
use bdk::blockchain::ElectrumBlockchainConfig;

use bdk::blockchain::{AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain};

#[cfg(feature = "rpc")]
use bdk::blockchain::rpc::{wallet_name_from_descriptor, Auth, RpcConfig};

use bdk::database::BatchDatabase;
use bdk::sled;
use bdk::sled::Tree;
Expand Down Expand Up @@ -118,13 +127,14 @@ where
#[cfg(not(feature = "esplora"))]
let config_esplora = None;

#[cfg(feature = "electrum")]
let config_electrum = AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
url: wallet_opts.electrum_opts.electrum.clone(),
socks5: wallet_opts.proxy_opts.proxy.clone(),
retry: wallet_opts.proxy_opts.retries,
timeout: wallet_opts.electrum_opts.timeout,
stop_gap: 20,
});

#[cfg(feature = "compact_filters")]
let config_compact_filters: Option<AnyBlockchainConfig> = {
let mut peers = vec![];
Expand All @@ -151,8 +161,40 @@ where
#[cfg(not(feature = "compact_filters"))]
let config_compact_filters = None;

#[cfg(feature = "rpc")]
let config_rpc: Option<AnyBlockchainConfig> = {
let auth = Auth::UserPass {
username: wallet_opts.rpc_opts.auth.0.clone(),
password: wallet_opts.rpc_opts.auth.1.clone(),
};

// 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_config = RpcConfig {
url: wallet_opts.rpc_opts.address.clone(),
auth,
network,
wallet_name: wallet_name,
skip_blocks: wallet_opts.rpc_opts.skip_blocks,
};

Some(AnyBlockchainConfig::Rpc(rpc_config))
};

#[cfg(not(feature = "rpc"))]
let config_rpc = None;

// Fall back to Electrum config if Esplora or Compact Filter config isn't provided
let config = config_esplora
// Both rpc and electrum can be connected by default without any config options.
// if rpc is enabled, then it trumps over electrum.
let config = config_rpc
.or(config_esplora)
.or(config_compact_filters)
.unwrap_or(config_electrum);

Expand Down
97 changes: 80 additions & 17 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ pub enum WalletSubCommand {
///
/// The wallet options required for all [`CliSubCommand::Wallet`] or [`CliSubCommand::Repl`]
/// sub-commands. These options capture wallet descriptor and blockchain client information. The
/// blockchain client details are only used for [`OnlineWalletSubCommand`]s.
/// blockchain client details are only used for [`OnlineWalletSubCommand`]s.
///
/// # Example
///
Expand Down Expand Up @@ -314,7 +314,7 @@ pub enum WalletSubCommand {
/// electrum: "ssl://electrum.blockstream.info:60002".to_string(),
/// },
/// #[cfg(feature = "esplora")]
/// esplora_opts: EsploraOpts {
/// esplora_opts: EsploraOpts {
/// esplora: None,
/// esplora_concurrency: 4,
/// },
Expand All @@ -334,6 +334,7 @@ pub enum WalletSubCommand {
///
/// assert_eq!(expected_wallet_opts, wallet_opts);
/// ```
#[derive(Debug, StructOpt, Clone, PartialEq)]
pub struct WalletOpts {
/// Selects the wallet to use
Expand Down Expand Up @@ -362,6 +363,11 @@ pub struct WalletOpts {
#[cfg(feature = "compact_filters")]
#[structopt(flatten)]
pub compactfilter_opts: CompactFilterOpts,

#[cfg(feature = "rpc")]
#[structopt(flatten)]
pub rpc_opts: RpcOpts,

#[cfg(any(feature = "compact_filters", feature = "electrum"))]
#[structopt(flatten)]
pub proxy_opts: ProxyOpts,
Expand All @@ -378,7 +384,7 @@ pub struct ProxyOpts {
pub proxy: Option<String>,

/// Sets the SOCKS5 proxy credential
#[structopt(name="PROXY_USER:PASSWD", long="proxy_auth", short="a", parse(try_from_str = parse_proxy_auth))]
#[structopt(name="PROXY_USER:PASSWD", long="proxy-auth", short="a", parse(try_from_str = parse_auth))]
pub proxy_auth: Option<(String, String)>,

/// Sets the SOCKS5 proxy retries for the Electrum client
Expand Down Expand Up @@ -420,6 +426,33 @@ pub struct CompactFilterOpts {
pub skip_blocks: usize,
}

#[cfg(feature = "rpc")]
#[derive(Debug, StructOpt, Clone, PartialEq)]
pub struct RpcOpts {
/// Sets the full node address for rpc connection
#[structopt(
name = "ADDRESS:PORT",
short = "n",
long = "rpc-node",
default_value = "127.0.0.1:18443"
)]
pub address: String,

/// Sets the rpc authentication username:password
#[structopt(
name = "USER:PASSWD",
short = "A",
long = "rpc-auth",
parse(try_from_str = parse_auth),
default_value = "admin:password",
)]
pub auth: (String, String),

/// Optionally skip initial `skip_blocks` blocks
#[structopt(name = "SKIP_BLOCKS", short = "x", long = "skip-blocks")]
pub skip_blocks: Option<u32>,
}

/// Electrum options
///
/// Electrum blockchain client information used by [`OnlineWalletSubCommand`]s.
Expand Down Expand Up @@ -659,20 +692,20 @@ fn parse_recipient(s: &str) -> Result<(Script, u64), String> {
return Err("Invalid format".to_string());
}

let addr = Address::from_str(&parts[0]);
let addr = Address::from_str(parts[0]);
if let Err(e) = addr {
return Err(format!("{:?}", e));
}
let val = u64::from_str(&parts[1]);
let val = u64::from_str(parts[1]);
if let Err(e) = val {
return Err(format!("{:?}", e));
}

Ok((addr.unwrap().script_pubkey(), val.unwrap()))
}

#[cfg(any(feature = "electrum", feature = "compact_filters"))]
fn parse_proxy_auth(s: &str) -> Result<(String, String), String> {
#[cfg(any(feature = "electrum", feature = "compact_filters", feature = "rpc"))]
fn parse_auth(s: &str) -> Result<(String, String), String> {
let parts: Vec<_> = s.split(':').collect();
if parts.len() != 2 {
return Err("Invalid format".to_string());
Expand Down Expand Up @@ -700,7 +733,7 @@ where
D: BatchDatabase,
{
match offline_subcommand {
GetNewAddress => Ok(json!({"address": wallet.get_address(AddressIndex::New)?})),
GetNewAddress => Ok(json!({"address": *wallet.get_address(AddressIndex::New)?})),
ListUnspent => Ok(serde_json::to_value(&wallet.list_unspent()?)?),
ListTransactions => Ok(serde_json::to_value(&wallet.list_transactions(false)?)?),
GetBalance => Ok(json!({"satoshi": wallet.get_balance()?})),
Expand All @@ -718,9 +751,7 @@ where
let mut tx_builder = wallet.build_tx();

if send_all {
tx_builder
.drain_wallet()
.set_single_recipient(recipients[0].0.clone());
tx_builder.drain_wallet().drain_to(recipients[0].0.clone());
} else {
tx_builder.set_recipients(recipients);
}
Expand Down Expand Up @@ -750,7 +781,7 @@ where
internal_policy.map(|p| (p, KeychainKind::Internal)),
];

for (policy, keychain) in policies.into_iter().filter_map(|x| x) {
for (policy, keychain) in policies.into_iter().flatten() {
let policy = serde_json::from_str::<BTreeMap<String, Vec<usize>>>(&policy)
.map_err(|s| Error::Generic(s.to_string()))?;
tx_builder.policy_path(policy, keychain);
Expand Down Expand Up @@ -779,7 +810,7 @@ where
tx_builder.fee_rate(FeeRate::from_sat_per_vb(fee_rate));

if send_all {
tx_builder.maintain_single_recipient()?;
//tx_builder.allow_shrinking()?;
}

if offline_signer {
Expand Down Expand Up @@ -1067,6 +1098,8 @@ mod test {
use crate::OnlineWalletSubCommand::{Broadcast, Sync};
#[cfg(any(feature = "compact_filters", feature = "electrum"))]
use crate::ProxyOpts;
#[cfg(feature = "rpc")]
use crate::RpcOpts;
use crate::{handle_key_subcommand, CliSubCommand, KeySubCommand, WalletSubCommand};

use bdk::bitcoin::{Address, Network, OutPoint};
Expand Down Expand Up @@ -1112,7 +1145,13 @@ mod test {
proxy: None,
proxy_auth: None,
retries: 5,
}
},
#[cfg(feature = "rpc")]
rpc_opts: RpcOpts {
address: "127.0.0.1:18443".to_string(),
auth: ("admin".to_string(), "password".to_string()),
skip_blocks: None,
},
},
subcommand: WalletSubCommand::OfflineWalletSubCommand(GetNewAddress),
},
Expand Down Expand Up @@ -1162,6 +1201,12 @@ mod test {
proxy: Some("127.0.0.1:9150".to_string()),
proxy_auth: None,
retries: 3,
},
#[cfg(feature = "rpc")]
rpc_opts: RpcOpts {
address: "127.0.0.1:18443".to_string(),
auth: ("admin".to_string(), "password".to_string()),
skip_blocks: None,
}
},
subcommand: WalletSubCommand::OfflineWalletSubCommand(GetNewAddress),
Expand Down Expand Up @@ -1311,7 +1356,13 @@ mod test {
proxy: None,
proxy_auth: None,
retries: 5,
}
},
#[cfg(feature = "rpc")]
rpc_opts: RpcOpts {
address: "127.0.0.1:18443".to_string(),
auth: ("admin".to_string(), "password".to_string()),
skip_blocks: None,
},
},
subcommand: WalletSubCommand::OnlineWalletSubCommand(Sync {
max_addresses: Some(50)
Expand Down Expand Up @@ -1377,7 +1428,13 @@ mod test {
proxy: None,
proxy_auth: None,
retries: 5,
}
},
#[cfg(feature = "rpc")]
rpc_opts: RpcOpts {
address: "127.0.0.1:8332".to_string(),
auth: ("admin".to_string(), "password".to_string()),
skip_blocks: None,
},
},
subcommand: WalletSubCommand::OfflineWalletSubCommand(CreateTx {
recipients: vec![(script1, 123456), (script2, 78910)],
Expand Down Expand Up @@ -1434,7 +1491,13 @@ mod test {
proxy: None,
proxy_auth: None,
retries: 5,
}
},
#[cfg(feature = "rpc")]
rpc_opts: RpcOpts {
address: "127.0.0.1:8332".to_string(),
auth: ("admin".to_string(), "password".to_string()),
skip_blocks: None,
},
},
subcommand: WalletSubCommand::OnlineWalletSubCommand(Broadcast {
psbt: Some("cHNidP8BAEICAAAAASWhGE1AhvtO+2GjJHopssFmgfbq+WweHd8zN/DeaqmDAAAAAAD/////AQAAAAAAAAAABmoEAAECAwAAAAAAAAA=".to_string()),
Expand Down

0 comments on commit b2c48fc

Please sign in to comment.