Skip to content

Commit

Permalink
example(esplora): update esplora examples to use full_scan and sync r…
Browse files Browse the repository at this point in the history
…equests
  • Loading branch information
notmandatory committed Mar 29, 2024
1 parent 98508f8 commit 1144f68
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 141 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ Cargo.lock

# Example persisted files.
*.db
bdk_wallet_esplora_async_example.dat
bdk_wallet_esplora_blocking_example.dat
4 changes: 2 additions & 2 deletions example-crates/example_bitcoind_rpc_polling/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ fn main() -> anyhow::Result<()> {
&*chain,
synced_to.block_id(),
graph.index.outpoints().iter().cloned(),
|(k, _), _| k == &Keychain::Internal,
|(k, _), _| k == &Keychain::Internal { account: 0 },
)
};
println!(
Expand Down Expand Up @@ -344,7 +344,7 @@ fn main() -> anyhow::Result<()> {
&*chain,
synced_to.block_id(),
graph.index.outpoints().iter().cloned(),
|(k, _), _| k == &Keychain::Internal,
|(k, _), _| k == &Keychain::Internal { account: 0 },
)
};
println!(
Expand Down
32 changes: 19 additions & 13 deletions example-crates/example_cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,15 @@ pub enum TxOutCmd {
Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, serde::Deserialize, serde::Serialize,
)]
pub enum Keychain {
External,
Internal,
External { account: u32 },
Internal { account: u32 },
}

impl core::fmt::Display for Keychain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Keychain::External => write!(f, "external"),
Keychain::Internal => write!(f, "internal"),
Keychain::External { account } => write!(f, "external[{}]", account),
Keychain::Internal { account } => write!(f, "internal[{}]", account),
}
}
}
Expand Down Expand Up @@ -247,10 +247,15 @@ where
script_pubkey: address.script_pubkey(),
}];

let internal_keychain = if graph.index.keychains().get(&Keychain::Internal).is_some() {
Keychain::Internal
let internal_keychain = if graph
.index
.keychains()
.get(&Keychain::Internal { account: 0 })
.is_some()
{
Keychain::Internal { account: 0 }
} else {
Keychain::External
Keychain::External { account: 0 }
};

let ((change_index, change_script), change_changeset) =
Expand Down Expand Up @@ -463,7 +468,8 @@ where
_ => unreachable!("only these two variants exist in match arm"),
};

let ((spk_i, spk), index_changeset) = spk_chooser(index, &Keychain::External);
let ((spk_i, spk), index_changeset) =
spk_chooser(index, &Keychain::External { account: 0 });
let db = &mut *db.lock().unwrap();
db.stage_and_commit(C::from((
local_chain::ChangeSet::default(),
Expand All @@ -482,8 +488,8 @@ where
}
AddressCmd::List { change } => {
let target_keychain = match change {
true => Keychain::Internal,
false => Keychain::External,
true => Keychain::Internal { account: 0 },
false => Keychain::External { account: 0 },
};
for (spk_i, spk) in index.revealed_keychain_spks(&target_keychain) {
let address = Address::from_script(spk, network)
Expand Down Expand Up @@ -516,7 +522,7 @@ where
chain,
chain.get_chain_tip()?,
graph.index.outpoints().iter().cloned(),
|(k, _), _| k == &Keychain::Internal,
|(k, _), _| k == &Keychain::Internal { account: 0 },
)?;

let confirmed_total = balance.confirmed + balance.immature;
Expand Down Expand Up @@ -689,7 +695,7 @@ where

let (descriptor, mut keymap) =
Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &args.descriptor)?;
index.add_keychain(Keychain::External, descriptor);
index.add_keychain(Keychain::External { account: 0 }, descriptor);

if let Some((internal_descriptor, internal_keymap)) = args
.change_descriptor
Expand All @@ -698,7 +704,7 @@ where
.transpose()?
{
keymap.extend(internal_keymap);
index.add_keychain(Keychain::Internal, internal_descriptor);
index.add_keychain(Keychain::Internal { account: 0 }, internal_descriptor);
}

let mut db_backend = match Store::<C>::open_or_create_new(db_magic, &args.db_path) {
Expand Down
99 changes: 43 additions & 56 deletions example-crates/example_esplora/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::{
collections::BTreeMap,
io::{self, Write},
sync::Mutex,
};

use bdk_chain::spk_client::{FullScanRequest, SyncRequest};
use bdk_chain::{
bitcoin::{constants::genesis_block, Address, Network, OutPoint, ScriptBuf, Txid},
indexed_tx_graph::{self, IndexedTxGraph},
Expand Down Expand Up @@ -83,7 +83,7 @@ impl EsploraArgs {
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",
Network::Signet => "http://signet.bitcoindevkit.net",
_ => panic!("unsupported network"),
});

Expand Down Expand Up @@ -172,48 +172,39 @@ fn main() -> anyhow::Result<()> {
.lock()
.expect("mutex must not be poisoned")
.index
.all_unbounded_spk_iters()
.into_iter()
// This `map` is purely for logging.
.map(|(keychain, iter)| {
let mut first = true;
let spk_iter = iter.inspect(move |(i, _)| {
if first {
eprint!("\nscanning {}: ", keychain);
first = false;
}
eprint!("{} ", i);
// Flush early to ensure we print at every iteration.
let _ = io::stderr().flush();
});
(keychain, spk_iter)
})
.collect::<BTreeMap<_, _>>();
.all_unbounded_spk_iters();

// The client scans keychain spks for transaction histories, stopping after `stop_gap`
// is reached. It returns a `TxGraph` update (`graph_update`) and a structure that
// represents the last active spk derivation indices of keychains
// (`keychain_indices_update`).
let update = client
.full_scan(
local_tip,
keychain_spks,
*stop_gap,
scan_options.parallel_requests,
)
let mut request = FullScanRequest::new(local_tip);
request.add_spks_by_keychain(keychain_spks);
request.inspect_spks(move |k, i, spk| {
println!(
"{:?}[{}]: {}",
k,
i,
Address::from_script(spk, args.network).unwrap()
);
});
println!("Scanning... ");
let result = client
.full_scan(request, *stop_gap, scan_options.parallel_requests)
.context("scanning for transactions")?;
println!("done. ");

let mut graph = graph.lock().expect("mutex must not be poisoned");
let mut chain = chain.lock().expect("mutex must not be poisoned");
// Because we did a stop gap based scan we are likely to have some updates to our
// deriviation indices. Usually before a scan you are on a fresh wallet with no
// addresses derived so we need to derive up to last active addresses the scan found
// before adding the transactions.
(chain.apply_update(update.local_chain)?, {
(chain.apply_update(result.chain_update)?, {
let (_, index_changeset) = graph
.index
.reveal_to_target_multi(&update.last_active_indices);
let mut indexed_tx_graph_changeset = graph.apply_update(update.tx_graph);
.reveal_to_target_multi(&result.last_active_indices);
let mut indexed_tx_graph_changeset = graph.apply_update(result.graph_update);
indexed_tx_graph_changeset.append(index_changeset.into());
indexed_tx_graph_changeset
})
Expand All @@ -238,7 +229,8 @@ fn main() -> anyhow::Result<()> {
}

// Spks, outpoints and txids we want updates on will be accumulated here.
let mut spks: Box<dyn Iterator<Item = ScriptBuf>> = Box::new(core::iter::empty());
let mut spks: Box<dyn Iterator<Item = (u32, ScriptBuf)>> =
Box::new(core::iter::empty());
let mut outpoints: Box<dyn Iterator<Item = OutPoint>> = Box::new(core::iter::empty());
let mut txids: Box<dyn Iterator<Item = Txid>> = Box::new(core::iter::empty());

Expand All @@ -256,30 +248,16 @@ fn main() -> anyhow::Result<()> {
.revealed_spks()
.map(|(k, i, spk)| (k, i, spk.to_owned()))
.collect::<Vec<_>>();
spks = Box::new(spks.chain(all_spks.into_iter().map(|(k, i, spk)| {
eprintln!("scanning {}:{}", k, i);
// Flush early to ensure we print at every iteration.
let _ = io::stderr().flush();
spk
})));
spks = Box::new(spks.chain(all_spks.into_iter().map(|(_k, i, spk)| (i, spk))));
}
if unused_spks {
let unused_spks = graph
.index
.unused_spks()
.map(|(k, i, spk)| (k, i, spk.to_owned()))
.collect::<Vec<_>>();
spks = Box::new(spks.chain(unused_spks.into_iter().map(|(k, i, spk)| {
eprintln!(
"Checking if address {} {}:{} has been used",
Address::from_script(&spk, args.network).unwrap(),
k,
i,
);
// Flush early to ensure we print at every iteration.
let _ = io::stderr().flush();
spk
})));
spks =
Box::new(spks.chain(unused_spks.into_iter().map(|(_k, i, spk)| (i, spk))));
}
if utxos {
// We want to search for whether the UTXO is spent, and spent by which
Expand Down Expand Up @@ -323,17 +301,26 @@ fn main() -> anyhow::Result<()> {
}
}

let update = client.sync(
local_tip,
spks,
txids,
outpoints,
scan_options.parallel_requests,
)?;
let mut request = SyncRequest::new(local_tip);
request.add_spks(spks);
request.inspect_spks(move |i, spk| {
println!(
"[{}]: {}",
i,
Address::from_script(spk, args.network).unwrap()
);
});
request.add_txids(txids);
request.add_outpoints(outpoints);
println!("Syncing... ");
let result = client
.sync(request, scan_options.parallel_requests)
.context("syncing transactions")?;
println!("done. ");

(
chain.lock().unwrap().apply_update(update.local_chain)?,
graph.lock().unwrap().apply_update(update.tx_graph),
chain.lock().unwrap().apply_update(result.chain_update)?,
graph.lock().unwrap().apply_update(result.graph_update),
)
}
};
Expand Down
2 changes: 2 additions & 0 deletions example-crates/wallet_esplora_async/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ bdk_esplora = { path = "../../crates/esplora", features = ["async-https"] }
bdk_file_store = { path = "../../crates/file_store" }
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
anyhow = "1"
env_logger = { version = "0.10", default-features = false, features = ["humantime"] }
log = "0.4.20"
63 changes: 29 additions & 34 deletions example-crates/wallet_esplora_async/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{io::Write, str::FromStr};
use std::str::FromStr;

use bdk::chain::spk_client::FullScanRequest;
use bdk::{
bitcoin::{Address, Network},
wallet::{AddressIndex, Update},
Expand All @@ -15,55 +16,49 @@ const PARALLEL_REQUESTS: usize = 5;

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let db_path = std::env::temp_dir().join("bdk-esplora-async-example");
//let db_path = std::env::temp_dir().join("bdk-esplora-async-example");
let db_path = "bdk-esplora-async-example";
let db = Store::<bdk::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
let network = Network::Signet;

let mut wallet = Wallet::new_or_load(
external_descriptor,
Some(internal_descriptor),
db,
Network::Testnet,
)?;
let mut wallet =
Wallet::new_or_load(external_descriptor, Some(internal_descriptor), db, network)?;

let address = wallet.try_get_address(AddressIndex::New)?;
println!("Generated Address: {}", address);

let balance = wallet.get_balance();
println!("Wallet balance before syncing: {} sats", balance.total());

print!("Syncing...");
let client =
esplora_client::Builder::new("https://blockstream.info/testnet/api").build_async()?;
let client = esplora_client::Builder::new("http://signet.bitcoindevkit.net").build_async()?;

let prev_tip = wallet.latest_checkpoint();
let keychain_spks = wallet
.all_unbounded_spk_iters()
.into_iter()
.map(|(k, k_spks)| {
let mut once = Some(());
let mut stdout = std::io::stdout();
let k_spks = k_spks
.inspect(move |(spk_i, _)| match once.take() {
Some(_) => print!("\nScanning keychain [{:?}]", k),
None => print!(" {:<3}", spk_i),
})
.inspect(move |_| stdout.flush().expect("must flush"));
(k, k_spks)
})
.collect();
let update = client
.full_scan(prev_tip, keychain_spks, STOP_GAP, PARALLEL_REQUESTS)
let keychain_spks = wallet.all_unbounded_spk_iters();

let mut request = FullScanRequest::new(prev_tip);
request.add_spks_by_keychain(keychain_spks);
request.inspect_spks(move |k, i, spk| {
println!(
"{:?}[{}]: {}",
k,
i,
Address::from_script(spk, network).unwrap()
);
});
println!("Scanning... ");
let result = client
.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)
.await?;
println!("done. ");
let update = Update {
last_active_indices: update.last_active_indices,
graph: update.tx_graph,
chain: Some(update.local_chain),
last_active_indices: result.last_active_indices,
graph: result.graph_update,
chain: Some(result.chain_update),
};
wallet.apply_update(update)?;
wallet.commit()?;
println!();

let balance = wallet.get_balance();
println!("Wallet balance after syncing: {} sats", balance.total());
Expand All @@ -76,8 +71,8 @@ async fn main() -> Result<(), anyhow::Error> {
std::process::exit(0);
}

let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")?
.require_network(Network::Testnet)?;
let faucet_address =
Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")?.require_network(network)?;

let mut tx_builder = wallet.build_tx();
tx_builder
Expand Down
2 changes: 2 additions & 0 deletions example-crates/wallet_esplora_blocking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ bdk = { path = "../../crates/bdk" }
bdk_esplora = { path = "../../crates/esplora", features = ["blocking"] }
bdk_file_store = { path = "../../crates/file_store" }
anyhow = "1"
env_logger = { version = "0.10", default-features = false, features = ["humantime"] }
log = "0.4.20"
Loading

0 comments on commit 1144f68

Please sign in to comment.