feat(example_cli): allow chain specific args in examples

So you can pass in the esplora/electrum/bitcoind_rpc server details in
the example.

Co-authored-by: LLFourn <lloyd.fourn@gmail.com>
This commit is contained in:
志宇 2023-10-04 16:45:57 +08:00
parent 77cde96229
commit f795a43cc7
No known key found for this signature in database
GPG Key ID: F6345C9837C2BDE8
3 changed files with 280 additions and 274 deletions

View File

@ -34,7 +34,7 @@ pub type Database<'m, C> = Persist<Store<'m, C>, C>;
#[derive(Parser)] #[derive(Parser)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
#[clap(propagate_version = true)] #[clap(propagate_version = true)]
pub struct Args<S: clap::Subcommand> { pub struct Args<CS: clap::Subcommand, S: clap::Args> {
#[clap(env = "DESCRIPTOR")] #[clap(env = "DESCRIPTOR")]
pub descriptor: String, pub descriptor: String,
#[clap(env = "CHANGE_DESCRIPTOR")] #[clap(env = "CHANGE_DESCRIPTOR")]
@ -50,14 +50,14 @@ pub struct Args<S: clap::Subcommand> {
pub cp_limit: usize, pub cp_limit: usize,
#[clap(subcommand)] #[clap(subcommand)]
pub command: Commands<S>, pub command: Commands<CS, S>,
} }
#[allow(clippy::almost_swapped)] #[allow(clippy::almost_swapped)]
#[derive(Subcommand, Debug, Clone)] #[derive(Subcommand, Debug, Clone)]
pub enum Commands<S: clap::Subcommand> { pub enum Commands<CS: clap::Subcommand, S: clap::Args> {
#[clap(flatten)] #[clap(flatten)]
ChainSpecific(S), ChainSpecific(CS),
/// Address generation and inspection. /// Address generation and inspection.
Address { Address {
#[clap(subcommand)] #[clap(subcommand)]
@ -77,6 +77,8 @@ pub enum Commands<S: clap::Subcommand> {
address: Address<address::NetworkUnchecked>, address: Address<address::NetworkUnchecked>,
#[clap(short, default_value = "bnb")] #[clap(short, default_value = "bnb")]
coin_select: CoinSelectionAlgo, coin_select: CoinSelectionAlgo,
#[clap(flatten)]
chain_specfic: S,
}, },
} }
@ -183,225 +185,6 @@ impl core::fmt::Display for Keychain {
} }
} }
pub fn run_address_cmd<A, C>(
graph: &mut KeychainTxGraph<A>,
db: &Mutex<Database<C>>,
network: Network,
cmd: AddressCmd,
) -> anyhow::Result<()>
where
C: Default + Append + DeserializeOwned + Serialize + From<KeychainChangeSet<A>>,
{
let index = &mut graph.index;
match cmd {
AddressCmd::Next | AddressCmd::New => {
let spk_chooser = match cmd {
AddressCmd::Next => KeychainTxOutIndex::next_unused_spk,
AddressCmd::New => KeychainTxOutIndex::reveal_next_spk,
_ => unreachable!("only these two variants exist in match arm"),
};
let ((spk_i, spk), index_changeset) = spk_chooser(index, &Keychain::External);
let db = &mut *db.lock().unwrap();
db.stage(C::from((
local_chain::ChangeSet::default(),
indexed_tx_graph::ChangeSet::from(index_changeset),
)));
db.commit()?;
let addr = Address::from_script(spk, network).context("failed to derive address")?;
println!("[address @ {}] {}", spk_i, addr);
Ok(())
}
AddressCmd::Index => {
for (keychain, derivation_index) in index.last_revealed_indices() {
println!("{:?}: {}", keychain, derivation_index);
}
Ok(())
}
AddressCmd::List { change } => {
let target_keychain = match change {
true => Keychain::Internal,
false => Keychain::External,
};
for (spk_i, spk) in index.revealed_spks_of_keychain(&target_keychain) {
let address = Address::from_script(spk, network)
.expect("should always be able to derive address");
println!(
"{:?} {} used:{}",
spk_i,
address,
index.is_used(&(target_keychain, spk_i))
);
}
Ok(())
}
}
}
pub fn run_balance_cmd<A: Anchor, O: ChainOracle>(
graph: &KeychainTxGraph<A>,
chain: &O,
) -> Result<(), O::Error> {
fn print_balances<'a>(title_str: &'a str, items: impl IntoIterator<Item = (&'a str, u64)>) {
println!("{}:", title_str);
for (name, amount) in items.into_iter() {
println!(" {:<10} {:>12} sats", name, amount)
}
}
let balance = graph.graph().try_balance(
chain,
chain.get_chain_tip()?.unwrap_or_default(),
graph.index.outpoints().iter().cloned(),
|(k, _), _| k == &Keychain::Internal,
)?;
let confirmed_total = balance.confirmed + balance.immature;
let unconfirmed_total = balance.untrusted_pending + balance.trusted_pending;
print_balances(
"confirmed",
[
("total", confirmed_total),
("spendable", balance.confirmed),
("immature", balance.immature),
],
);
print_balances(
"unconfirmed",
[
("total", unconfirmed_total),
("trusted", balance.trusted_pending),
("untrusted", balance.untrusted_pending),
],
);
Ok(())
}
pub fn run_txo_cmd<A: Anchor, O: ChainOracle>(
graph: &KeychainTxGraph<A>,
chain: &O,
network: Network,
cmd: TxOutCmd,
) -> anyhow::Result<()>
where
O::Error: std::error::Error + Send + Sync + 'static,
{
let chain_tip = chain.get_chain_tip()?.unwrap_or_default();
let outpoints = graph.index.outpoints().iter().cloned();
match cmd {
TxOutCmd::List {
spent,
unspent,
confirmed,
unconfirmed,
} => {
let txouts = graph
.graph()
.try_filter_chain_txouts(chain, chain_tip, outpoints)
.filter(|r| match r {
Ok((_, full_txo)) => match (spent, unspent) {
(true, false) => full_txo.spent_by.is_some(),
(false, true) => full_txo.spent_by.is_none(),
_ => true,
},
// always keep errored items
Err(_) => true,
})
.filter(|r| match r {
Ok((_, full_txo)) => match (confirmed, unconfirmed) {
(true, false) => full_txo.chain_position.is_confirmed(),
(false, true) => !full_txo.chain_position.is_confirmed(),
_ => true,
},
// always keep errored items
Err(_) => true,
})
.collect::<Result<Vec<_>, _>>()?;
for (spk_i, full_txo) in txouts {
let addr = Address::from_script(&full_txo.txout.script_pubkey, network)?;
println!(
"{:?} {} {} {} spent:{:?}",
spk_i, full_txo.txout.value, full_txo.outpoint, addr, full_txo.spent_by
)
}
Ok(())
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn run_send_cmd<A: Anchor, O: ChainOracle, C>(
graph: &Mutex<KeychainTxGraph<A>>,
db: &Mutex<Database<'_, C>>,
chain: &O,
keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
cs_algorithm: CoinSelectionAlgo,
address: Address,
value: u64,
broadcast: impl FnOnce(&Transaction) -> anyhow::Result<()>,
) -> anyhow::Result<()>
where
O::Error: std::error::Error + Send + Sync + 'static,
C: Default + Append + DeserializeOwned + Serialize + From<KeychainChangeSet<A>>,
{
let (transaction, change_index) = {
let graph = &mut *graph.lock().unwrap();
// take mutable ref to construct tx -- it is only open for a short time while building it.
let (tx, change_info) = create_tx(graph, chain, keymap, cs_algorithm, address, value)?;
if let Some((index_changeset, (change_keychain, index))) = change_info {
// We must first persist to disk the fact that we've got a new address from the
// change keychain so future scans will find the tx we're about to broadcast.
// If we're unable to persist this, then we don't want to broadcast.
{
let db = &mut *db.lock().unwrap();
db.stage(C::from((
local_chain::ChangeSet::default(),
indexed_tx_graph::ChangeSet::from(index_changeset),
)));
db.commit()?;
}
// We don't want other callers/threads to use this address while we're using it
// but we also don't want to scan the tx we just created because it's not
// technically in the blockchain yet.
graph.index.mark_used(&change_keychain, index);
(tx, Some((change_keychain, index)))
} else {
(tx, None)
}
};
match (broadcast)(&transaction) {
Ok(_) => {
println!("Broadcasted Tx : {}", transaction.txid());
let keychain_changeset = graph.lock().unwrap().insert_tx(&transaction, None, None);
// We know the tx is at least unconfirmed now. Note if persisting here fails,
// it's not a big deal since we can always find it again form
// blockchain.
db.lock().unwrap().stage(C::from((
local_chain::ChangeSet::default(),
keychain_changeset,
)));
Ok(())
}
Err(e) => {
if let Some((keychain, index)) = change_index {
// We failed to broadcast, so allow our change address to be used in the future
graph.lock().unwrap().index.unmark_used(&keychain, index);
}
Err(e)
}
}
}
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn create_tx<A: Anchor, O: ChainOracle>( pub fn create_tx<A: Anchor, O: ChainOracle>(
graph: &mut KeychainTxGraph<A>, graph: &mut KeychainTxGraph<A>,
@ -647,14 +430,14 @@ pub fn planned_utxos<A: Anchor, O: ChainOracle, K: Clone + bdk_tmp_plan::CanDeri
.collect() .collect()
} }
pub fn handle_commands<S: clap::Subcommand, A: Anchor, O: ChainOracle, C>( pub fn handle_commands<CS: clap::Subcommand, S: clap::Args, A: Anchor, O: ChainOracle, C>(
graph: &Mutex<KeychainTxGraph<A>>, graph: &Mutex<KeychainTxGraph<A>>,
db: &Mutex<Database<C>>, db: &Mutex<Database<C>>,
chain: &Mutex<O>, chain: &Mutex<O>,
keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>, keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
network: Network, network: Network,
broadcast: impl FnOnce(&Transaction) -> anyhow::Result<()>, broadcast: impl FnOnce(S, &Transaction) -> anyhow::Result<()>,
cmd: Commands<S>, cmd: Commands<CS, S>,
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
O::Error: std::error::Error + Send + Sync + 'static, O::Error: std::error::Error + Send + Sync + 'static,
@ -664,45 +447,213 @@ where
Commands::ChainSpecific(_) => unreachable!("example code should handle this!"), Commands::ChainSpecific(_) => unreachable!("example code should handle this!"),
Commands::Address { addr_cmd } => { Commands::Address { addr_cmd } => {
let graph = &mut *graph.lock().unwrap(); let graph = &mut *graph.lock().unwrap();
run_address_cmd(graph, db, network, addr_cmd) let index = &mut graph.index;
match addr_cmd {
AddressCmd::Next | AddressCmd::New => {
let spk_chooser = match addr_cmd {
AddressCmd::Next => KeychainTxOutIndex::next_unused_spk,
AddressCmd::New => KeychainTxOutIndex::reveal_next_spk,
_ => unreachable!("only these two variants exist in match arm"),
};
let ((spk_i, spk), index_changeset) = spk_chooser(index, &Keychain::External);
let db = &mut *db.lock().unwrap();
db.stage(C::from((
local_chain::ChangeSet::default(),
indexed_tx_graph::ChangeSet::from(index_changeset),
)));
db.commit()?;
let addr =
Address::from_script(spk, network).context("failed to derive address")?;
println!("[address @ {}] {}", spk_i, addr);
Ok(())
}
AddressCmd::Index => {
for (keychain, derivation_index) in index.last_revealed_indices() {
println!("{:?}: {}", keychain, derivation_index);
}
Ok(())
}
AddressCmd::List { change } => {
let target_keychain = match change {
true => Keychain::Internal,
false => Keychain::External,
};
for (spk_i, spk) in index.revealed_spks_of_keychain(&target_keychain) {
let address = Address::from_script(spk, network)
.expect("should always be able to derive address");
println!(
"{:?} {} used:{}",
spk_i,
address,
index.is_used(&(target_keychain, spk_i))
);
}
Ok(())
}
}
} }
Commands::Balance => { Commands::Balance => {
let graph = &*graph.lock().unwrap(); let graph = &*graph.lock().unwrap();
let chain = &*chain.lock().unwrap(); let chain = &*chain.lock().unwrap();
run_balance_cmd(graph, chain).map_err(anyhow::Error::from) fn print_balances<'a>(
title_str: &'a str,
items: impl IntoIterator<Item = (&'a str, u64)>,
) {
println!("{}:", title_str);
for (name, amount) in items.into_iter() {
println!(" {:<10} {:>12} sats", name, amount)
}
}
let balance = graph.graph().try_balance(
chain,
chain.get_chain_tip()?.unwrap_or_default(),
graph.index.outpoints().iter().cloned(),
|(k, _), _| k == &Keychain::Internal,
)?;
let confirmed_total = balance.confirmed + balance.immature;
let unconfirmed_total = balance.untrusted_pending + balance.trusted_pending;
print_balances(
"confirmed",
[
("total", confirmed_total),
("spendable", balance.confirmed),
("immature", balance.immature),
],
);
print_balances(
"unconfirmed",
[
("total", unconfirmed_total),
("trusted", balance.trusted_pending),
("untrusted", balance.untrusted_pending),
],
);
Ok(())
} }
Commands::TxOut { txout_cmd } => { Commands::TxOut { txout_cmd } => {
let graph = &*graph.lock().unwrap(); let graph = &*graph.lock().unwrap();
let chain = &*chain.lock().unwrap(); let chain = &*chain.lock().unwrap();
run_txo_cmd(graph, chain, network, txout_cmd) let chain_tip = chain.get_chain_tip()?.unwrap_or_default();
let outpoints = graph.index.outpoints().iter().cloned();
match txout_cmd {
TxOutCmd::List {
spent,
unspent,
confirmed,
unconfirmed,
} => {
let txouts = graph
.graph()
.try_filter_chain_txouts(chain, chain_tip, outpoints)
.filter(|r| match r {
Ok((_, full_txo)) => match (spent, unspent) {
(true, false) => full_txo.spent_by.is_some(),
(false, true) => full_txo.spent_by.is_none(),
_ => true,
},
// always keep errored items
Err(_) => true,
})
.filter(|r| match r {
Ok((_, full_txo)) => match (confirmed, unconfirmed) {
(true, false) => full_txo.chain_position.is_confirmed(),
(false, true) => !full_txo.chain_position.is_confirmed(),
_ => true,
},
// always keep errored items
Err(_) => true,
})
.collect::<Result<Vec<_>, _>>()?;
for (spk_i, full_txo) in txouts {
let addr = Address::from_script(&full_txo.txout.script_pubkey, network)?;
println!(
"{:?} {} {} {} spent:{:?}",
spk_i, full_txo.txout.value, full_txo.outpoint, addr, full_txo.spent_by
)
}
Ok(())
}
}
} }
Commands::Send { Commands::Send {
value, value,
address, address,
coin_select, coin_select,
chain_specfic,
} => { } => {
let chain = &*chain.lock().unwrap(); let chain = &*chain.lock().unwrap();
let address = address.require_network(network)?; let address = address.require_network(network)?;
run_send_cmd( let (transaction, change_index) = {
graph, let graph = &mut *graph.lock().unwrap();
db, // take mutable ref to construct tx -- it is only open for a short time while building it.
chain, let (tx, change_info) =
keymap, create_tx(graph, chain, keymap, coin_select, address, value)?;
coin_select,
address, if let Some((index_changeset, (change_keychain, index))) = change_info {
value, // We must first persist to disk the fact that we've got a new address from the
broadcast, // change keychain so future scans will find the tx we're about to broadcast.
) // If we're unable to persist this, then we don't want to broadcast.
{
let db = &mut *db.lock().unwrap();
db.stage(C::from((
local_chain::ChangeSet::default(),
indexed_tx_graph::ChangeSet::from(index_changeset),
)));
db.commit()?;
}
// We don't want other callers/threads to use this address while we're using it
// but we also don't want to scan the tx we just created because it's not
// technically in the blockchain yet.
graph.index.mark_used(&change_keychain, index);
(tx, Some((change_keychain, index)))
} else {
(tx, None)
}
};
match (broadcast)(chain_specfic, &transaction) {
Ok(_) => {
println!("Broadcasted Tx : {}", transaction.txid());
let keychain_changeset =
graph.lock().unwrap().insert_tx(&transaction, None, None);
// We know the tx is at least unconfirmed now. Note if persisting here fails,
// it's not a big deal since we can always find it again form
// blockchain.
db.lock().unwrap().stage(C::from((
local_chain::ChangeSet::default(),
keychain_changeset,
)));
Ok(())
}
Err(e) => {
if let Some((keychain, index)) = change_index {
// We failed to broadcast, so allow our change address to be used in the future
graph.lock().unwrap().index.unmark_used(&keychain, index);
}
Err(e)
}
}
} }
} }
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn init<'m, S: clap::Subcommand, C>( pub fn init<'m, CS: clap::Subcommand, S: clap::Args, C>(
db_magic: &'m [u8], db_magic: &'m [u8],
db_default_path: &str, db_default_path: &str,
) -> anyhow::Result<( ) -> anyhow::Result<(
Args<S>, Args<CS, S>,
KeyMap, KeyMap,
KeychainTxOutIndex<Keychain>, KeychainTxOutIndex<Keychain>,
Mutex<Database<'m, C>>, Mutex<Database<'m, C>>,
@ -714,7 +665,7 @@ where
if std::env::var("BDK_DB_PATH").is_err() { if std::env::var("BDK_DB_PATH").is_err() {
std::env::set_var("BDK_DB_PATH", db_default_path); std::env::set_var("BDK_DB_PATH", db_default_path);
} }
let args = Args::<S>::parse(); let args = Args::<CS, S>::parse();
let secp = Secp256k1::default(); let secp = Secp256k1::default();
let mut index = KeychainTxOutIndex::<Keychain>::default(); let mut index = KeychainTxOutIndex::<Keychain>::default();

View File

@ -12,7 +12,7 @@ use bdk_chain::{
Append, ConfirmationHeightAnchor, Append, ConfirmationHeightAnchor,
}; };
use bdk_electrum::{ use bdk_electrum::{
electrum_client::{self, ElectrumApi}, electrum_client::{self, Client, ElectrumApi},
ElectrumExt, ElectrumUpdate, ElectrumExt, ElectrumUpdate,
}; };
use example_cli::{ use example_cli::{
@ -33,6 +33,8 @@ enum ElectrumCommands {
stop_gap: usize, stop_gap: usize,
#[clap(flatten)] #[clap(flatten)]
scan_options: ScanOptions, scan_options: ScanOptions,
#[clap(flatten)]
electrum_args: ElectrumArgs,
}, },
/// Scans particular addresses using the electrum API. /// Scans particular addresses using the electrum API.
Sync { Sync {
@ -50,9 +52,44 @@ enum ElectrumCommands {
unconfirmed: bool, unconfirmed: bool,
#[clap(flatten)] #[clap(flatten)]
scan_options: ScanOptions, scan_options: ScanOptions,
#[clap(flatten)]
electrum_args: ElectrumArgs,
}, },
} }
impl ElectrumCommands {
fn electrum_args(&self) -> ElectrumArgs {
match self {
ElectrumCommands::Scan { electrum_args, .. } => electrum_args.clone(),
ElectrumCommands::Sync { electrum_args, .. } => electrum_args.clone(),
}
}
}
#[derive(clap::Args, Debug, Clone)]
pub struct ElectrumArgs {
/// The electrum url to use to connect to. If not provided it will use a default electrum server
/// for your chosen network.
electrum_url: Option<String>,
}
impl ElectrumArgs {
pub fn client(&self, network: Network) -> anyhow::Result<Client> {
let electrum_url = self.electrum_url.as_deref().unwrap_or(match network {
Network::Bitcoin => "ssl://electrum.blockstream.info:50002",
Network::Testnet => "ssl://electrum.blockstream.info:60002",
Network::Regtest => "tcp://localhost:60401",
Network::Signet => "tcp://signet-electrumx.wakiyamap.dev:50001",
_ => panic!("Unknown network"),
});
let config = electrum_client::Config::builder()
.validate_domain(matches!(network, Network::Bitcoin))
.build();
Ok(electrum_client::Client::from_config(electrum_url, config)?)
}
}
#[derive(Parser, Debug, Clone, PartialEq)] #[derive(Parser, Debug, Clone, PartialEq)]
pub struct ScanOptions { pub struct ScanOptions {
/// Set batch size for each script_history call to electrum client. /// Set batch size for each script_history call to electrum client.
@ -67,7 +104,7 @@ type ChangeSet = (
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let (args, keymap, index, db, (disk_local_chain, disk_tx_graph)) = let (args, keymap, index, db, (disk_local_chain, disk_tx_graph)) =
example_cli::init::<ElectrumCommands, ChangeSet>(DB_MAGIC, DB_PATH)?; example_cli::init::<ElectrumCommands, ElectrumArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
let graph = Mutex::new({ let graph = Mutex::new({
let mut graph = IndexedTxGraph::new(index); let mut graph = IndexedTxGraph::new(index);
@ -77,19 +114,6 @@ fn main() -> anyhow::Result<()> {
let chain = Mutex::new(LocalChain::from_changeset(disk_local_chain)); let chain = Mutex::new(LocalChain::from_changeset(disk_local_chain));
let electrum_url = match args.network {
Network::Bitcoin => "ssl://electrum.blockstream.info:50002",
Network::Testnet => "ssl://electrum.blockstream.info:60002",
Network::Regtest => "tcp://localhost:60401",
Network::Signet => "tcp://signet-electrumx.wakiyamap.dev:50001",
_ => panic!("Unknown network"),
};
let config = electrum_client::Config::builder()
.validate_domain(matches!(args.network, Network::Bitcoin))
.build();
let client = electrum_client::Client::from_config(electrum_url, config)?;
let electrum_cmd = match &args.command { let electrum_cmd = match &args.command {
example_cli::Commands::ChainSpecific(electrum_cmd) => electrum_cmd, example_cli::Commands::ChainSpecific(electrum_cmd) => electrum_cmd,
general_cmd => { general_cmd => {
@ -99,11 +123,10 @@ fn main() -> anyhow::Result<()> {
&chain, &chain,
&keymap, &keymap,
args.network, args.network,
|tx| { |electrum_args, tx| {
client let client = electrum_args.client(args.network)?;
.transaction_broadcast(tx) client.transaction_broadcast(tx)?;
.map(|_| ()) Ok(())
.map_err(anyhow::Error::from)
}, },
general_cmd.clone(), general_cmd.clone(),
); );
@ -113,10 +136,13 @@ fn main() -> anyhow::Result<()> {
} }
}; };
let client = electrum_cmd.electrum_args().client(args.network)?;
let response = match electrum_cmd.clone() { let response = match electrum_cmd.clone() {
ElectrumCommands::Scan { ElectrumCommands::Scan {
stop_gap, stop_gap,
scan_options, scan_options,
..
} => { } => {
let (keychain_spks, tip) = { let (keychain_spks, tip) = {
let graph = &*graph.lock().unwrap(); let graph = &*graph.lock().unwrap();
@ -162,6 +188,7 @@ fn main() -> anyhow::Result<()> {
mut utxos, mut utxos,
mut unconfirmed, mut unconfirmed,
scan_options, scan_options,
..
} => { } => {
// Get a short lock on the tracker to get the spks we're interested in // Get a short lock on the tracker to get the spks we're interested in
let graph = graph.lock().unwrap(); let graph = graph.lock().unwrap();

View File

@ -37,6 +37,8 @@ enum EsploraCommands {
stop_gap: usize, stop_gap: usize,
#[clap(flatten)] #[clap(flatten)]
scan_options: ScanOptions, scan_options: ScanOptions,
#[clap(flatten)]
esplora_args: EsploraArgs,
}, },
/// Scan for particular addresses and unconfirmed transactions using the esplora API. /// Scan for particular addresses and unconfirmed transactions using the esplora API.
Sync { Sync {
@ -54,8 +56,40 @@ enum EsploraCommands {
unconfirmed: bool, unconfirmed: bool,
#[clap(flatten)] #[clap(flatten)]
scan_options: ScanOptions, scan_options: ScanOptions,
#[clap(flatten)]
esplora_args: EsploraArgs,
}, },
} }
impl EsploraCommands {
fn esplora_args(&self) -> EsploraArgs {
match self {
EsploraCommands::Scan { esplora_args, .. } => esplora_args.clone(),
EsploraCommands::Sync { esplora_args, .. } => esplora_args.clone(),
}
}
}
#[derive(clap::Args, Debug, Clone)]
pub struct EsploraArgs {
/// The esplora url endpoint to connect to e.g. `<https://blockstream.info/api>`
/// If not provided it'll be set to a default for the network provided
esplora_url: Option<String>,
}
impl EsploraArgs {
pub fn client(&self, network: Network) -> anyhow::Result<esplora_client::BlockingClient> {
let esplora_url = self.esplora_url.as_deref().unwrap_or(match network {
Network::Bitcoin => "https://blockstream.info/api",
Network::Testnet => "https://blockstream.info/testnet/api",
Network::Regtest => "http://localhost:3002",
Network::Signet => "https://mempool.space/signet/api",
_ => panic!("unsupported network"),
});
let client = esplora_client::Builder::new(esplora_url).build_blocking()?;
Ok(client)
}
}
#[derive(Parser, Debug, Clone, PartialEq)] #[derive(Parser, Debug, Clone, PartialEq)]
pub struct ScanOptions { pub struct ScanOptions {
@ -66,7 +100,7 @@ pub struct ScanOptions {
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let (args, keymap, index, db, init_changeset) = let (args, keymap, index, db, init_changeset) =
example_cli::init::<EsploraCommands, ChangeSet>(DB_MAGIC, DB_PATH)?; example_cli::init::<EsploraCommands, EsploraArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
let (init_chain_changeset, init_indexed_tx_graph_changeset) = init_changeset; let (init_chain_changeset, init_indexed_tx_graph_changeset) = init_changeset;
@ -84,16 +118,6 @@ fn main() -> anyhow::Result<()> {
chain chain
}); });
let esplora_url = match args.network {
Network::Bitcoin => "https://blockstream.info/api",
Network::Testnet => "https://blockstream.info/testnet/api",
Network::Regtest => "http://localhost:3002",
Network::Signet => "https://mempool.space/signet/api",
_ => panic!("unsupported network"),
};
let client = esplora_client::Builder::new(esplora_url).build_blocking()?;
let esplora_cmd = match &args.command { let esplora_cmd = match &args.command {
// These are commands that are handled by this example (sync, scan). // These are commands that are handled by this example (sync, scan).
example_cli::Commands::ChainSpecific(esplora_cmd) => esplora_cmd, example_cli::Commands::ChainSpecific(esplora_cmd) => esplora_cmd,
@ -105,7 +129,8 @@ fn main() -> anyhow::Result<()> {
&chain, &chain,
&keymap, &keymap,
args.network, args.network,
|tx| { |esplora_args, tx| {
let client = esplora_args.client(args.network)?;
client client
.broadcast(tx) .broadcast(tx)
.map(|_| ()) .map(|_| ())
@ -119,6 +144,7 @@ fn main() -> anyhow::Result<()> {
} }
}; };
let client = esplora_cmd.esplora_args().client(args.network)?;
// Prepare the `IndexedTxGraph` update based on whether we are scanning or syncing. // Prepare the `IndexedTxGraph` update based on whether we are scanning or syncing.
// Scanning: We are iterating through spks of all keychains and scanning for transactions for // Scanning: We are iterating through spks of all keychains and scanning for transactions for
// each spk. We start with the lowest derivation index spk and stop scanning after `stop_gap` // each spk. We start with the lowest derivation index spk and stop scanning after `stop_gap`
@ -131,6 +157,7 @@ fn main() -> anyhow::Result<()> {
EsploraCommands::Scan { EsploraCommands::Scan {
stop_gap, stop_gap,
scan_options, scan_options,
..
} => { } => {
let keychain_spks = graph let keychain_spks = graph
.lock() .lock()
@ -184,6 +211,7 @@ fn main() -> anyhow::Result<()> {
mut utxos, mut utxos,
mut unconfirmed, mut unconfirmed,
scan_options, scan_options,
..
} => { } => {
if !(*all_spks || unused_spks || utxos || unconfirmed) { if !(*all_spks || unused_spks || utxos || unconfirmed) {
// If nothing is specifically selected, we select everything (except all spks). // If nothing is specifically selected, we select everything (except all spks).