diff --git a/examples/example_wallet_electrum/src/main.rs b/examples/example_wallet_electrum/src/main.rs index 91ef5636..2479b111 100644 --- a/examples/example_wallet_electrum/src/main.rs +++ b/examples/example_wallet_electrum/src/main.rs @@ -1,3 +1,4 @@ +use bdk_wallet::bitcoin::Txid; use bdk_wallet::file_store::Store; use bdk_wallet::Wallet; use std::io::Write; @@ -39,12 +40,12 @@ fn main() -> Result<(), anyhow::Error> { let address = wallet.next_unused_address(KeychainKind::External); wallet.persist(&mut db)?; - println!("Generated Address: {}", address); + println!("Generated Address: {address}"); let balance = wallet.balance(); println!("Wallet balance before syncing: {}", balance.total()); - print!("Syncing..."); + println!("=== Performing Full Sync ==="); let client = BdkElectrumClient::new(electrum_client::Client::new(ELECTRUM_URL)?); // Populate the electrum client's transaction cache so it doesn't redownload transaction we @@ -56,9 +57,9 @@ fn main() -> Result<(), anyhow::Error> { let mut once = HashSet::::new(); move |k, spk_i, _| { if once.insert(k) { - print!("\nScanning keychain [{:?}]", k); + print!("\nScanning keychain [{k:?}]"); } - print!(" {:<3}", spk_i); + print!(" {spk_i:<3}"); stdout.flush().expect("must flush"); } }); @@ -71,13 +72,15 @@ fn main() -> Result<(), anyhow::Error> { wallet.persist(&mut db)?; let balance = wallet.balance(); - println!("Wallet balance after syncing: {}", balance.total()); + println!("Wallet balance after full sync: {}", balance.total()); + println!( + "Wallet has {} transactions and {} utxos after full sync", + wallet.transactions().count(), + wallet.list_unspent().count() + ); if balance.total() < SEND_AMOUNT { - println!( - "Please send at least {} to the receiving address", - SEND_AMOUNT - ); + println!("Please send at least {SEND_AMOUNT} to the receiving address"); std::process::exit(0); } @@ -92,5 +95,73 @@ fn main() -> Result<(), anyhow::Error> { client.transaction_broadcast(&tx)?; println!("Tx broadcasted! Txid: {}", tx.compute_txid()); + let unconfirmed_txids: HashSet = wallet + .transactions() + .filter(|tx| tx.chain_position.is_unconfirmed()) + .map(|tx| tx.tx_node.txid) + .collect(); + + client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); + + println!("\n=== Performing Partial Sync ===\n"); + print!("SCANNING: "); + let mut last_printed = 0; + let sync_request = wallet + .start_sync_with_revealed_spks() + .inspect(move |_, sync_progress| { + let progress_percent = + (100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32; + let progress_percent = progress_percent.round() as u32; + if progress_percent.is_multiple_of(5) && progress_percent > last_printed { + print!("{progress_percent}% "); + std::io::stdout().flush().expect("must flush"); + last_printed = progress_percent; + } + }); + client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); + let sync_update = client.sync(sync_request, BATCH_SIZE, false)?; + println!(); + + let mut evicted_txs = Vec::new(); + for txid in unconfirmed_txids { + let tx_node = wallet + .tx_graph() + .full_txs() + .find(|full_tx| full_tx.txid == txid); + let wallet_tx = wallet.get_tx(txid); + + let is_evicted = match wallet_tx { + Some(wallet_tx) => { + !wallet_tx.chain_position.is_unconfirmed() + && !wallet_tx.chain_position.is_confirmed() + } + None => true, + }; + + if is_evicted { + if let Some(full_tx) = tx_node { + evicted_txs.push((full_tx.txid, full_tx.last_seen.unwrap_or(0))); + } else { + evicted_txs.push((txid, 0)); + } + } + } + + if !evicted_txs.is_empty() { + wallet.apply_evicted_txs(evicted_txs.clone()); + println!("Applied {} evicted transactions", evicted_txs.len()); + } + + wallet.apply_update(sync_update)?; + wallet.persist(&mut db)?; + + let balance_after_sync = wallet.balance(); + println!("Wallet balance after sync: {}", balance_after_sync.total()); + println!( + "Wallet has {} transactions and {} utxos after partial sync", + wallet.transactions().count(), + wallet.list_unspent().count() + ); + Ok(()) } diff --git a/examples/example_wallet_esplora_async/src/main.rs b/examples/example_wallet_esplora_async/src/main.rs index b6ab5d6d..4191ea0d 100644 --- a/examples/example_wallet_esplora_async/src/main.rs +++ b/examples/example_wallet_esplora_async/src/main.rs @@ -1,12 +1,14 @@ -use std::{collections::BTreeSet, io::Write}; - use anyhow::Ok; use bdk_esplora::{esplora_client, EsploraAsyncExt}; use bdk_wallet::{ - bitcoin::{Amount, Network}, + bitcoin::{Amount, Network, Txid}, rusqlite::Connection, KeychainKind, SignOptions, Wallet, }; +use std::{ + collections::{BTreeSet, HashSet}, + io::Write, +}; const SEND_AMOUNT: Amount = Amount::from_sat(5000); const STOP_GAP: usize = 5; @@ -42,7 +44,7 @@ async fn main() -> Result<(), anyhow::Error> { let balance = wallet.balance(); println!("Wallet balance before syncing: {}", balance.total()); - print!("Syncing..."); + println!("=== Performing Full Sync ==="); let client = esplora_client::Builder::new(ESPLORA_URL).build_async()?; let request = wallet.start_full_scan().inspect({ @@ -50,9 +52,9 @@ async fn main() -> Result<(), anyhow::Error> { let mut once = BTreeSet::::new(); move |keychain, spk_i, _| { if once.insert(keychain) { - print!("\nScanning keychain [{:?}]", keychain); + print!("\nScanning keychain [{keychain:?}]"); } - print!(" {:<3}", spk_i); + print!(" {spk_i:<3}"); stdout.flush().expect("must flush") } }); @@ -66,13 +68,15 @@ async fn main() -> Result<(), anyhow::Error> { println!(); let balance = wallet.balance(); - println!("Wallet balance after syncing: {}", balance.total()); + println!("Wallet balance after full sync: {}", balance.total()); + println!( + "Wallet has {} transactions and {} utxos after full sync", + wallet.transactions().count(), + wallet.list_unspent().count() + ); if balance.total() < SEND_AMOUNT { - println!( - "Please send at least {} to the receiving address", - SEND_AMOUNT - ); + println!("Please send at least {SEND_AMOUNT} to the receiving address"); std::process::exit(0); } @@ -87,5 +91,71 @@ async fn main() -> Result<(), anyhow::Error> { client.broadcast(&tx).await?; println!("Tx broadcasted! Txid: {}", tx.compute_txid()); + let unconfirmed_txids: HashSet = wallet + .transactions() + .filter(|tx| tx.chain_position.is_unconfirmed()) + .map(|tx| tx.tx_node.txid) + .collect(); + + println!("\n=== Performing Partial Sync ===\n"); + print!("SCANNING: "); + let mut printed = 0; + let sync_request = wallet + .start_sync_with_revealed_spks() + .inspect(move |_, sync_progress| { + let progress_percent = + (100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32; + let progress_percent = progress_percent.round() as u32; + if progress_percent.is_multiple_of(5) && progress_percent > printed { + print!("{progress_percent}% "); + std::io::stdout().flush().expect("must flush"); + printed = progress_percent; + } + }); + let sync_update = client.sync(sync_request, PARALLEL_REQUESTS).await?; + println!(); + + let mut evicted_txs = Vec::new(); + for txid in unconfirmed_txids { + let tx_node = wallet + .tx_graph() + .full_txs() + .find(|full_tx| full_tx.txid == txid); + let wallet_tx = wallet.get_tx(txid); + + let is_evicted = match wallet_tx { + Some(wallet_tx) => { + !wallet_tx.chain_position.is_unconfirmed() + && !wallet_tx.chain_position.is_confirmed() + } + None => true, + }; + + if is_evicted { + if let Some(full_tx) = tx_node { + evicted_txs.push((full_tx.txid, full_tx.last_seen.unwrap_or(0))); + } else { + evicted_txs.push((txid, 0)); + } + } + } + + if !evicted_txs.is_empty() { + let evicted_count = evicted_txs.len(); + wallet.apply_evicted_txs(evicted_txs); + println!("Applied {evicted_count} evicted transactions"); + } + + wallet.apply_update(sync_update)?; + wallet.persist(&mut conn)?; + + let balance_after_sync = wallet.balance(); + println!("Wallet balance after sync: {}", balance_after_sync.total()); + println!( + "Wallet has {} transactions and {} utxos after partial sync", + wallet.transactions().count(), + wallet.list_unspent().count() + ); + Ok(()) } diff --git a/examples/example_wallet_esplora_blocking/src/main.rs b/examples/example_wallet_esplora_blocking/src/main.rs index 735f2a78..b964de87 100644 --- a/examples/example_wallet_esplora_blocking/src/main.rs +++ b/examples/example_wallet_esplora_blocking/src/main.rs @@ -1,11 +1,13 @@ -use std::{collections::BTreeSet, io::Write}; - use bdk_esplora::{esplora_client, EsploraExt}; use bdk_wallet::{ - bitcoin::{Amount, Network}, + bitcoin::{Amount, Network, Txid}, file_store::Store, KeychainKind, SignOptions, Wallet, }; +use std::{ + collections::{BTreeSet, HashSet}, + io::Write, +}; const DB_MAGIC: &str = "bdk_wallet_esplora_example"; const DB_PATH: &str = "bdk-example-esplora-blocking.db"; @@ -52,9 +54,9 @@ fn main() -> Result<(), anyhow::Error> { let mut once = BTreeSet::::new(); move |keychain, spk_i, _| { if once.insert(keychain) { - print!("\nScanning keychain [{:?}] ", keychain); + print!("\nScanning keychain [{keychain:?}] "); } - print!(" {:<3}", spk_i); + print!(" {spk_i:<3}"); stdout.flush().expect("must flush") } }); @@ -69,10 +71,7 @@ fn main() -> Result<(), anyhow::Error> { println!("Wallet balance after syncing: {}", balance.total()); if balance.total() < SEND_AMOUNT { - println!( - "Please send at least {} to the receiving address", - SEND_AMOUNT - ); + println!("Please send at least {SEND_AMOUNT} to the receiving address"); std::process::exit(0); } @@ -87,5 +86,71 @@ fn main() -> Result<(), anyhow::Error> { client.broadcast(&tx)?; println!("Tx broadcasted! Txid: {}", tx.compute_txid()); + let unconfirmed_txids: HashSet = wallet + .transactions() + .filter(|tx| tx.chain_position.is_unconfirmed()) + .map(|tx| tx.tx_node.txid) + .collect(); + + println!("\n=== Performing Partial Sync ===\n"); + print!("SCANNING: "); + let mut printed = 0; + let sync_request = wallet + .start_sync_with_revealed_spks() + .inspect(move |_, sync_progress| { + let progress_percent = + (100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32; + let progress_percent = progress_percent.round() as u32; + if progress_percent.is_multiple_of(5) && progress_percent > printed { + print!("{progress_percent}% "); + std::io::stdout().flush().expect("must flush"); + printed = progress_percent; + } + }); + let sync_update = client.sync(sync_request, PARALLEL_REQUESTS)?; + println!(); + + let mut evicted_txs = Vec::new(); + for txid in unconfirmed_txids { + let tx_node = wallet + .tx_graph() + .full_txs() + .find(|full_tx| full_tx.txid == txid); + let wallet_tx = wallet.get_tx(txid); + + let is_evicted = match wallet_tx { + Some(wallet_tx) => { + !wallet_tx.chain_position.is_unconfirmed() + && !wallet_tx.chain_position.is_confirmed() + } + None => true, + }; + + if is_evicted { + if let Some(full_tx) = tx_node { + evicted_txs.push((full_tx.txid, full_tx.last_seen.unwrap_or(0))); + } else { + evicted_txs.push((txid, 0)); + } + } + } + + if !evicted_txs.is_empty() { + let evicted_count = evicted_txs.len(); + wallet.apply_evicted_txs(evicted_txs); + println!("Applied {evicted_count} evicted transactions"); + } + + wallet.apply_update(sync_update)?; + wallet.persist(&mut db)?; + + let balance_after_sync = wallet.balance(); + println!("Wallet balance after sync: {}", balance_after_sync.total()); + println!( + "Wallet has {} transactions and {} utxos after partial sync", + wallet.transactions().count(), + wallet.list_unspent().count() + ); + Ok(()) } diff --git a/examples/example_wallet_rpc/src/main.rs b/examples/example_wallet_rpc/src/main.rs index 5526e666..1aef139e 100644 --- a/examples/example_wallet_rpc/src/main.rs +++ b/examples/example_wallet_rpc/src/main.rs @@ -166,10 +166,7 @@ fn main() -> anyhow::Result<()> { wallet.apply_block_connected_to(&block_emission.block, height, connected_to)?; wallet.persist(&mut db)?; let elapsed = start_apply_block.elapsed().as_secs_f32(); - println!( - "Applied block {} at height {} in {}s", - hash, height, elapsed - ); + println!("Applied block {hash} at height {height} in {elapsed}s",); } Emission::Mempool(event) => { let start_apply_mempool = Instant::now();