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:
parent
77cde96229
commit
f795a43cc7
@ -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();
|
||||||
|
@ -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();
|
||||||
|
@ -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).
|
||||||
|
Loading…
x
Reference in New Issue
Block a user