From 35deb4a75c72f9a0f84ca9ee00a15c120a939d87 Mon Sep 17 00:00:00 2001 From: Dave Hrycyszyn Date: Tue, 25 Jun 2024 14:58:13 +0100 Subject: [PATCH] Extracted the Esplora wallet into a struct --- side-node/src/bitcoin/clients/esplora.rs | 178 +++++++++++++---------- side-node/src/bitcoin/driver.rs | 11 +- 2 files changed, 106 insertions(+), 83 deletions(-) diff --git a/side-node/src/bitcoin/clients/esplora.rs b/side-node/src/bitcoin/clients/esplora.rs index cce3e88..d4ac00f 100644 --- a/side-node/src/bitcoin/clients/esplora.rs +++ b/side-node/src/bitcoin/clients/esplora.rs @@ -1,7 +1,10 @@ use std::{collections::BTreeSet, fs, io::Write, str::FromStr}; use bdk::keys::bip39::Mnemonic; -use bdk_esplora::{esplora_client, EsploraAsyncExt}; +use bdk_esplora::{ + esplora_client::{self, AsyncClient}, + EsploraAsyncExt, +}; use bdk_wallet::{ bitcoin::{Address, Amount, Network, Script}, chain::ConfirmationTimeHeightAnchor, @@ -17,89 +20,102 @@ use crate::utils; const STOP_GAP: usize = 50; const PARALLEL_REQUESTS: usize = 5; -pub(crate) fn build_send_tx( - mut wallet: Wallet, - faucet_address: Address, - amount: Amount, -) -> Result { - let mut tx_builder = wallet.build_tx(); - tx_builder - .add_recipient(faucet_address.script_pubkey(), amount) - .enable_rbf(); - let mut psbt = tx_builder.finish()?; - let finalized = wallet.sign(&mut psbt, SignOptions::default())?; - assert!(finalized); - let tx = psbt.extract_tx()?; - Ok(tx) +pub struct EsploraWallet { + wallet: Wallet, + db: Store, + client: AsyncClient, } -pub(crate) async fn sync( - wallet: &mut Wallet, - db: &mut Store, -) -> Result { - print!("Syncing..."); - let client = esplora_client::Builder::new("https://mutinynet.com/api") - .build_async() - .expect("couldn't build esplora client"); - fn generate_inspect(kind: KeychainKind) -> impl FnMut(u32, &Script) + Send + Sync + 'static { - let mut once = Some(()); - let mut stdout = std::io::stdout(); - move |spk_i, _| { - match once.take() { - Some(_) => print!("\nScanning keychain [{:?}]", kind), - None => print!(" {:<3}", spk_i), - }; - stdout.flush().expect("must flush"); - } +impl EsploraWallet { + pub(crate) fn build_send_tx( + &mut self, + faucet_address: Address, + amount: Amount, + ) -> Result { + let mut tx_builder = self.wallet.build_tx(); + tx_builder + .add_recipient(faucet_address.script_pubkey(), amount) + .enable_rbf(); + let mut psbt = tx_builder.finish()?; + let finalized = self.wallet.sign(&mut psbt, SignOptions::default())?; + assert!(finalized); + let tx = psbt.extract_tx()?; + Ok(tx) } - let request = wallet - .start_full_scan() - .inspect_spks_for_all_keychains({ - let mut once = BTreeSet::::new(); - move |keychain, spk_i, _| { - match once.insert(keychain) { - true => print!("\nScanning keychain [{:?}]", keychain), - false => print!(" {:<3}", spk_i), - } - std::io::stdout().flush().expect("must flush") + + pub(crate) async fn sync(&mut self) -> Result<(), anyhow::Error> { + print!("Syncing..."); + + fn generate_inspect( + kind: KeychainKind, + ) -> impl FnMut(u32, &Script) + Send + Sync + 'static { + let mut once = Some(()); + let mut stdout = std::io::stdout(); + move |spk_i, _| { + match once.take() { + Some(_) => print!("\nScanning keychain [{:?}]", kind), + None => print!(" {:<3}", spk_i), + }; + stdout.flush().expect("must flush"); } - }) - .inspect_spks_for_keychain( - KeychainKind::External, - generate_inspect(KeychainKind::External), - ) - .inspect_spks_for_keychain( - KeychainKind::Internal, - generate_inspect(KeychainKind::Internal), - ); - let mut update = client - .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) - .await?; - let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); - let _ = update.graph_update.update_last_seen_unconfirmed(now); - wallet.apply_update(update)?; - if let Some(changeset) = wallet.take_staged() { - db.write(&changeset)?; + } + let request = self + .wallet + .start_full_scan() + .inspect_spks_for_all_keychains({ + let mut once = BTreeSet::::new(); + move |keychain, spk_i, _| { + match once.insert(keychain) { + true => print!("\nScanning keychain [{:?}]", keychain), + false => print!(" {:<3}", spk_i), + } + std::io::stdout().flush().expect("must flush") + } + }) + .inspect_spks_for_keychain( + KeychainKind::External, + generate_inspect(KeychainKind::External), + ) + .inspect_spks_for_keychain( + KeychainKind::Internal, + generate_inspect(KeychainKind::Internal), + ); + let mut update = self + .client + .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) + .await?; + let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); + let _ = update.graph_update.update_last_seen_unconfirmed(now); + self.wallet.apply_update(update)?; + if let Some(changeset) = self.wallet.take_staged() { + self.db.write(&changeset)?; + } + println!(); + Ok(()) + } + + pub(crate) fn next_unused_address(&mut self) -> Result { + let address = self.wallet.next_unused_address(KeychainKind::External); + if let Some(changeset) = self.wallet.take_staged() { + self.db.write(&changeset)?; + } + println!("Generated Address: {}", address); + Ok(address) + } + + pub(crate) fn balance(&self) -> bdk_wallet::wallet::Balance { + self.wallet.balance() + } + + pub(crate) async fn broadcast( + &self, + tx: &bitcoin::Transaction, + ) -> Result<(), esplora_client::Error> { + self.client.broadcast(tx).await } - println!(); - Ok(client) } -pub(crate) fn next_unused_address( - wallet: &mut Wallet, - db: &mut Store, -) -> Result { - let address = wallet.next_unused_address(KeychainKind::External); - if let Some(changeset) = wallet.take_staged() { - db.write(&changeset)?; - } - println!("Generated Address: {}", address); - Ok(address) -} - -pub(crate) fn create_wallet( - name: &str, -) -> anyhow::Result<(Wallet, Store)> { +pub(crate) fn create_wallet(name: &str) -> anyhow::Result { let keys_dir = utils::home(name); let mnemonic_path = crate::utils::side_paths(keys_dir).1; // TODO: this tuple stinks @@ -135,5 +151,11 @@ pub(crate) fn create_wallet( ) .expect("problem setting up wallet"); - Ok((wallet, db)) + let client = esplora_client::Builder::new("https://mutinynet.com/api") + .build_async() + .expect("couldn't build esplora client"); + + let esplora = EsploraWallet { wallet, db, client }; + + Ok(esplora) } diff --git a/side-node/src/bitcoin/driver.rs b/side-node/src/bitcoin/driver.rs index 46ce4fd..b1c8c93 100644 --- a/side-node/src/bitcoin/driver.rs +++ b/side-node/src/bitcoin/driver.rs @@ -11,14 +11,14 @@ use crate::bitcoin::clients; /// Also, it very handily works with the mutinynet.com esplora server, which is configured /// with 30 second block times. pub(crate) async fn run() -> Result<(), anyhow::Error> { - let (mut wallet, mut db) = clients::esplora::create_wallet("dave")?; + let mut wallet = clients::esplora::create_wallet("dave")?; - let _next_address = clients::esplora::next_unused_address(&mut wallet, &mut db)?; + let _next_address = wallet.next_unused_address()?; let balance = wallet.balance(); println!("Wallet balance before syncing: {} sats", balance.total()); - let client = clients::esplora::sync(&mut wallet, &mut db).await?; + wallet.sync().await?; let balance = wallet.balance(); println!("Wallet balance after syncing: {} sats", balance.total()); @@ -35,8 +35,9 @@ pub(crate) async fn run() -> Result<(), anyhow::Error> { std::process::exit(0); } - let tx = clients::esplora::build_send_tx(wallet, faucet_address, send_amount)?; - client.broadcast(&tx).await?; + let tx = wallet.build_send_tx(faucet_address, send_amount)?; + wallet.broadcast(&tx).await?; + println!("Tx broadcasted! Txid: {}", tx.compute_txid()); Ok(())