From 1144f6895dce379f35d2fbcc066698e8034a67f3 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Wed, 10 Jan 2024 14:43:57 -0600 Subject: [PATCH] example(esplora): update esplora examples to use full_scan and sync requests --- .gitignore | 2 + .../example_bitcoind_rpc_polling/src/main.rs | 4 +- example-crates/example_cli/src/lib.rs | 32 +++--- example-crates/example_esplora/src/main.rs | 99 ++++++++----------- .../wallet_esplora_async/Cargo.toml | 2 + .../wallet_esplora_async/src/main.rs | 63 ++++++------ .../wallet_esplora_blocking/Cargo.toml | 2 + .../wallet_esplora_blocking/src/main.rs | 63 +++++------- 8 files changed, 126 insertions(+), 141 deletions(-) diff --git a/.gitignore b/.gitignore index 95285763a..f3ee3a8e4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ Cargo.lock # Example persisted files. *.db +bdk_wallet_esplora_async_example.dat +bdk_wallet_esplora_blocking_example.dat diff --git a/example-crates/example_bitcoind_rpc_polling/src/main.rs b/example-crates/example_bitcoind_rpc_polling/src/main.rs index 88b83067b..0a016cbe9 100644 --- a/example-crates/example_bitcoind_rpc_polling/src/main.rs +++ b/example-crates/example_bitcoind_rpc_polling/src/main.rs @@ -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!( @@ -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!( diff --git a/example-crates/example_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 4989c08c6..39d96a634 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -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), } } } @@ -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) = @@ -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(), @@ -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) @@ -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; @@ -689,7 +695,7 @@ where let (descriptor, mut keymap) = Descriptor::::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 @@ -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::::open_or_create_new(db_magic, &args.db_path) { diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs index d3ec8bae4..d4d2f32ad 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -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}, @@ -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"), }); @@ -172,36 +172,27 @@ 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::>(); + .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"); @@ -209,11 +200,11 @@ fn main() -> anyhow::Result<()> { // 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 }) @@ -238,7 +229,8 @@ fn main() -> anyhow::Result<()> { } // Spks, outpoints and txids we want updates on will be accumulated here. - let mut spks: Box> = Box::new(core::iter::empty()); + let mut spks: Box> = + Box::new(core::iter::empty()); let mut outpoints: Box> = Box::new(core::iter::empty()); let mut txids: Box> = Box::new(core::iter::empty()); @@ -256,12 +248,7 @@ fn main() -> anyhow::Result<()> { .revealed_spks() .map(|(k, i, spk)| (k, i, spk.to_owned())) .collect::>(); - 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 @@ -269,17 +256,8 @@ fn main() -> anyhow::Result<()> { .unused_spks() .map(|(k, i, spk)| (k, i, spk.to_owned())) .collect::>(); - 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 @@ -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), ) } }; diff --git a/example-crates/wallet_esplora_async/Cargo.toml b/example-crates/wallet_esplora_async/Cargo.toml index c588a87aa..8e71ea993 100644 --- a/example-crates/wallet_esplora_async/Cargo.toml +++ b/example-crates/wallet_esplora_async/Cargo.toml @@ -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" diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index 50a8659e4..e8dc950be 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -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}, @@ -15,17 +16,15 @@ 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::::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); @@ -33,37 +32,33 @@ async fn main() -> Result<(), anyhow::Error> { 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()); @@ -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 diff --git a/example-crates/wallet_esplora_blocking/Cargo.toml b/example-crates/wallet_esplora_blocking/Cargo.toml index 0679bd8f3..5d6ed9b00 100644 --- a/example-crates/wallet_esplora_blocking/Cargo.toml +++ b/example-crates/wallet_esplora_blocking/Cargo.toml @@ -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" diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index 026ce7345..ee2a4d2cd 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -3,8 +3,9 @@ const SEND_AMOUNT: u64 = 1000; const STOP_GAP: usize = 5; const PARALLEL_REQUESTS: usize = 1; -use std::{io::Write, str::FromStr}; +use std::str::FromStr; +use bdk::chain::spk_client::FullScanRequest; use bdk::{ bitcoin::{Address, Network}, wallet::{AddressIndex, Update}, @@ -14,17 +15,15 @@ use bdk_esplora::{esplora_client, EsploraExt}; use bdk_file_store::Store; fn main() -> Result<(), anyhow::Error> { - let db_path = std::env::temp_dir().join("bdk-esplora-example"); + // let db_path = std::env::temp_dir().join("bdk-esplora-example"); + let db_path = "bdk-esplora-blocking-example"; let db = Store::::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); @@ -32,37 +31,29 @@ fn main() -> Result<(), anyhow::Error> { 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_blocking()?; - - 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( - wallet.latest_checkpoint(), - keychain_spks, - STOP_GAP, - PARALLEL_REQUESTS, - )?; + esplora_client::Builder::new("http://signet.bitcoindevkit.net").build_blocking()?; + + let keychain_spks = wallet.all_unbounded_spk_iters(); + + let mut request = FullScanRequest::new(wallet.latest_checkpoint()); + 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)?; + println!("done. "); wallet.apply_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.commit()?; println!();