diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b35dd13..9ca6b8d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `get_internal_address` to allow you to get internal addresses just as you get external addresses. - added `ensure_addresses_cached` to `Wallet` to let offline wallets load and cache addresses in their database +### Sync API change + +To decouple the `Wallet` from the `Blockchain` we've made major changes: + +- Removed `Blockchain` from Wallet. +- Removed `Wallet::broadcast` (just use `Blockchain::broadcast`) +- Deprecated `Wallet::new_offline` (all wallets are offline now) +- Changed `Wallet::sync` to take a `Blockchain`. +- Stop making a request for the block height when calling `Wallet:new`. +- Added `SyncOptions` to capture extra (future) arguments to `Wallet::sync`. +- Removed `max_addresses` sync parameter which determined how many addresses to cache before syncing since this can just be done with `ensure_addresses_cached`. + ## [v0.16.1] - [v0.16.0] - Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0 @@ -421,4 +433,4 @@ final transaction is created by calling `finish` on the builder. [v0.14.0]: https://github.com/bitcoindevkit/bdk/compare/v0.13.0...v0.14.0 [v0.15.0]: https://github.com/bitcoindevkit/bdk/compare/v0.14.0...v0.15.0 [v0.16.0]: https://github.com/bitcoindevkit/bdk/compare/v0.15.0...v0.16.0 -[v0.16.1]: https://github.com/bitcoindevkit/bdk/compare/v0.16.0...v0.16.1 \ No newline at end of file +[v0.16.1]: https://github.com/bitcoindevkit/bdk/compare/v0.16.0...v0.16.1 diff --git a/README.md b/README.md index d9491400..8f2c7e74 100644 --- a/README.md +++ b/README.md @@ -41,21 +41,21 @@ The `bdk` library aims to be the core building block for Bitcoin wallets of any ```rust,no_run use bdk::Wallet; use bdk::database::MemoryDatabase; -use bdk::blockchain::{noop_progress, ElectrumBlockchain}; +use bdk::blockchain::ElectrumBlockchain; +use bdk::SyncOptions; use bdk::electrum_client::Client; fn main() -> Result<(), bdk::Error> { - let client = Client::new("ssl://electrum.blockstream.info:60002")?; + let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); let wallet = Wallet::new( "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), bitcoin::Network::Testnet, MemoryDatabase::default(), - ElectrumBlockchain::from(client) )?; - wallet.sync(noop_progress(), None)?; + wallet.sync(&blockchain, SyncOptions::default())?; println!("Descriptor balance: {} SAT", wallet.get_balance()?); @@ -70,7 +70,7 @@ use bdk::{Wallet, database::MemoryDatabase}; use bdk::wallet::AddressIndex::New; fn main() -> Result<(), bdk::Error> { - let wallet = Wallet::new_offline( + let wallet = Wallet::new( "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), bitcoin::Network::Testnet, @@ -88,9 +88,9 @@ fn main() -> Result<(), bdk::Error> { ### Create a transaction ```rust,no_run -use bdk::{FeeRate, Wallet}; +use bdk::{FeeRate, Wallet, SyncOptions}; use bdk::database::MemoryDatabase; -use bdk::blockchain::{noop_progress, ElectrumBlockchain}; +use bdk::blockchain::ElectrumBlockchain; use bdk::electrum_client::Client; use bdk::wallet::AddressIndex::New; @@ -98,16 +98,15 @@ use bdk::wallet::AddressIndex::New; use bitcoin::consensus::serialize; fn main() -> Result<(), bdk::Error> { - let client = Client::new("ssl://electrum.blockstream.info:60002")?; + let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); let wallet = Wallet::new( "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), bitcoin::Network::Testnet, MemoryDatabase::default(), - ElectrumBlockchain::from(client) )?; - wallet.sync(noop_progress(), None)?; + wallet.sync(&blockchain, SyncOptions::default())?; let send_to = wallet.get_address(New)?; let (psbt, details) = { @@ -135,7 +134,7 @@ use bdk::{Wallet, SignOptions, database::MemoryDatabase}; use bitcoin::consensus::deserialize; fn main() -> Result<(), bdk::Error> { - let wallet = Wallet::new_offline( + let wallet = Wallet::new( "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), bitcoin::Network::Testnet, diff --git a/examples/address_validator.rs b/examples/address_validator.rs index 560a6df5..85a23560 100644 --- a/examples/address_validator.rs +++ b/examples/address_validator.rs @@ -48,8 +48,7 @@ impl AddressValidator for DummyValidator { fn main() -> Result<(), bdk::Error> { let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))"; - let mut wallet = - Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?; + let mut wallet = Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new())?; wallet.add_address_validator(Arc::new(DummyValidator)); diff --git a/examples/compact_filters_balance.rs b/examples/compact_filters_balance.rs index 78b69d6c..ce875b4d 100644 --- a/examples/compact_filters_balance.rs +++ b/examples/compact_filters_balance.rs @@ -10,7 +10,6 @@ // licenses. use bdk::blockchain::compact_filters::*; -use bdk::blockchain::noop_progress; use bdk::database::MemoryDatabase; use bdk::*; use bitcoin::*; @@ -35,9 +34,8 @@ fn main() -> Result<(), CompactFiltersError> { let descriptor = "wpkh(tpubD6NzVbkrYhZ4X2yy78HWrr1M9NT8dKeWfzNiQqDdMqqa9UmmGztGGz6TaLFGsLfdft5iu32gxq1T4eMNxExNNWzVCpf9Y6JZi5TnqoC9wJq/*)"; let database = MemoryDatabase::default(); - let wallet = - Arc::new(Wallet::new(descriptor, None, Network::Testnet, database, blockchain).unwrap()); - wallet.sync(noop_progress(), None).unwrap(); + let wallet = Arc::new(Wallet::new(descriptor, None, Network::Testnet, database).unwrap()); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); info!("balance: {}", wallet.get_balance()?); Ok(()) } diff --git a/examples/compiler.rs b/examples/compiler.rs index 0af034cb..c3a3cb4f 100644 --- a/examples/compiler.rs +++ b/examples/compiler.rs @@ -89,7 +89,7 @@ fn main() -> Result<(), Box> { .transpose() .unwrap() .unwrap_or(Network::Testnet); - let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?; + let wallet = Wallet::new(&format!("{}", descriptor), None, network, database)?; info!("... First address: {}", wallet.get_address(New)?); diff --git a/src/blockchain/any.rs b/src/blockchain/any.rs index 8470959d..91f448b3 100644 --- a/src/blockchain/any.rs +++ b/src/blockchain/any.rs @@ -16,61 +16,17 @@ //! //! ## Example //! -//! In this example both `wallet_electrum` and `wallet_esplora` have the same type of -//! `Wallet`. This means that they could both, for instance, be -//! assigned to a struct member. -//! -//! ```no_run -//! # use bitcoin::Network; -//! # use bdk::blockchain::*; -//! # use bdk::database::MemoryDatabase; -//! # use bdk::Wallet; -//! # #[cfg(feature = "electrum")] -//! # { -//! let electrum_blockchain = ElectrumBlockchain::from(electrum_client::Client::new("...")?); -//! let wallet_electrum: Wallet = Wallet::new( -//! "...", -//! None, -//! Network::Testnet, -//! MemoryDatabase::default(), -//! electrum_blockchain.into(), -//! )?; -//! # } -//! -//! # #[cfg(all(feature = "esplora", feature = "ureq"))] -//! # { -//! let esplora_blockchain = EsploraBlockchain::new("...", 20); -//! let wallet_esplora: Wallet = Wallet::new( -//! "...", -//! None, -//! Network::Testnet, -//! MemoryDatabase::default(), -//! esplora_blockchain.into(), -//! )?; -//! # } -//! -//! # Ok::<(), bdk::Error>(()) -//! ``` -//! -//! When paired with the use of [`ConfigurableBlockchain`], it allows creating wallets with any +//! When paired with the use of [`ConfigurableBlockchain`], it allows creating any //! blockchain type supported using a single line of code: //! //! ```no_run //! # use bitcoin::Network; //! # use bdk::blockchain::*; -//! # use bdk::database::MemoryDatabase; -//! # use bdk::Wallet; //! # #[cfg(all(feature = "esplora", feature = "ureq"))] //! # { //! let config = serde_json::from_str("...")?; //! let blockchain = AnyBlockchain::from_config(&config)?; -//! let wallet = Wallet::new( -//! "...", -//! None, -//! Network::Testnet, -//! MemoryDatabase::default(), -//! blockchain, -//! )?; +//! let height = blockchain.get_height(); //! # } //! # Ok::<(), bdk::Error>(()) //! ``` @@ -133,33 +89,55 @@ impl Blockchain for AnyBlockchain { maybe_await!(impl_inner_method!(self, get_capabilities)) } - fn setup( - &self, - database: &mut D, - progress_update: P, - ) -> Result<(), Error> { - maybe_await!(impl_inner_method!(self, setup, database, progress_update)) - } - fn sync( - &self, - database: &mut D, - progress_update: P, - ) -> Result<(), Error> { - maybe_await!(impl_inner_method!(self, sync, database, progress_update)) - } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - maybe_await!(impl_inner_method!(self, get_tx, txid)) - } fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { maybe_await!(impl_inner_method!(self, broadcast, tx)) } + fn estimate_fee(&self, target: usize) -> Result { + maybe_await!(impl_inner_method!(self, estimate_fee, target)) + } +} + +#[maybe_async] +impl GetHeight for AnyBlockchain { fn get_height(&self) -> Result { maybe_await!(impl_inner_method!(self, get_height)) } - fn estimate_fee(&self, target: usize) -> Result { - maybe_await!(impl_inner_method!(self, estimate_fee, target)) +} + +#[maybe_async] +impl GetTx for AnyBlockchain { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + maybe_await!(impl_inner_method!(self, get_tx, txid)) + } +} + +#[maybe_async] +impl WalletSync for AnyBlockchain { + fn wallet_sync( + &self, + database: &mut D, + progress_update: Box, + ) -> Result<(), Error> { + maybe_await!(impl_inner_method!( + self, + wallet_sync, + database, + progress_update + )) + } + + fn wallet_setup( + &self, + database: &mut D, + progress_update: Box, + ) -> Result<(), Error> { + maybe_await!(impl_inner_method!( + self, + wallet_setup, + database, + progress_update + )) } } diff --git a/src/blockchain/compact_filters/mod.rs b/src/blockchain/compact_filters/mod.rs index 56e9efc3..e7e235b5 100644 --- a/src/blockchain/compact_filters/mod.rs +++ b/src/blockchain/compact_filters/mod.rs @@ -67,7 +67,7 @@ mod peer; mod store; mod sync; -use super::{Blockchain, Capability, ConfigurableBlockchain, Progress}; +use crate::blockchain::*; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::error::Error; use crate::types::{KeychainKind, LocalUtxo, TransactionDetails}; @@ -225,11 +225,38 @@ impl Blockchain for CompactFiltersBlockchain { vec![Capability::FullHistory].into_iter().collect() } + fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { + self.peers[0].broadcast_tx(tx.clone())?; + + Ok(()) + } + + fn estimate_fee(&self, _target: usize) -> Result { + // TODO + Ok(FeeRate::default()) + } +} + +impl GetHeight for CompactFiltersBlockchain { + fn get_height(&self) -> Result { + Ok(self.headers.get_height()? as u32) + } +} + +impl GetTx for CompactFiltersBlockchain { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + Ok(self.peers[0] + .get_mempool() + .get_tx(&Inventory::Transaction(*txid))) + } +} + +impl WalletSync for CompactFiltersBlockchain { #[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop. - fn setup( + fn wallet_setup( &self, database: &mut D, - progress_update: P, + progress_update: Box, ) -> Result<(), Error> { let first_peer = &self.peers[0]; @@ -430,27 +457,6 @@ impl Blockchain for CompactFiltersBlockchain { Ok(()) } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(self.peers[0] - .get_mempool() - .get_tx(&Inventory::Transaction(*txid))) - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - self.peers[0].broadcast_tx(tx.clone())?; - - Ok(()) - } - - fn get_height(&self) -> Result { - Ok(self.headers.get_height()? as u32) - } - - fn estimate_fee(&self, _target: usize) -> Result { - // TODO - Ok(FeeRate::default()) - } } /// Data to connect to a Bitcoin P2P peer diff --git a/src/blockchain/electrum.rs b/src/blockchain/electrum.rs index 1ab0db1c..0b8691bc 100644 --- a/src/blockchain/electrum.rs +++ b/src/blockchain/electrum.rs @@ -68,10 +68,39 @@ impl Blockchain for ElectrumBlockchain { .collect() } - fn setup( + fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { + Ok(self.client.transaction_broadcast(tx).map(|_| ())?) + } + + fn estimate_fee(&self, target: usize) -> Result { + Ok(FeeRate::from_btc_per_kvb( + self.client.estimate_fee(target)? as f32 + )) + } +} + +impl GetHeight for ElectrumBlockchain { + fn get_height(&self) -> Result { + // TODO: unsubscribe when added to the client, or is there a better call to use here? + + Ok(self + .client + .block_headers_subscribe() + .map(|data| data.height as u32)?) + } +} + +impl GetTx for ElectrumBlockchain { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + Ok(self.client.transaction_get(txid).map(Option::Some)?) + } +} + +impl WalletSync for ElectrumBlockchain { + fn wallet_setup( &self, database: &mut D, - _progress_update: P, + _progress_update: Box, ) -> Result<(), Error> { let mut request = script_sync::start(database, self.stop_gap)?; let mut block_times = HashMap::::new(); @@ -207,29 +236,6 @@ impl Blockchain for ElectrumBlockchain { database.commit_batch(batch_update)?; Ok(()) } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(self.client.transaction_get(txid).map(Option::Some)?) - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - Ok(self.client.transaction_broadcast(tx).map(|_| ())?) - } - - fn get_height(&self) -> Result { - // TODO: unsubscribe when added to the client, or is there a better call to use here? - - Ok(self - .client - .block_headers_subscribe() - .map(|data| data.height as u32)?) - } - - fn estimate_fee(&self, target: usize) -> Result { - Ok(FeeRate::from_btc_per_kvb( - self.client.estimate_fee(target)? as f32 - )) - } } struct TxCache<'a, 'b, D> { diff --git a/src/blockchain/esplora/reqwest.rs b/src/blockchain/esplora/reqwest.rs index 494c6d30..2141b8e6 100644 --- a/src/blockchain/esplora/reqwest.rs +++ b/src/blockchain/esplora/reqwest.rs @@ -91,10 +91,36 @@ impl Blockchain for EsploraBlockchain { .collect() } - fn setup( + fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { + Ok(await_or_block!(self.url_client._broadcast(tx))?) + } + + fn estimate_fee(&self, target: usize) -> Result { + let estimates = await_or_block!(self.url_client._get_fee_estimates())?; + super::into_fee_rate(target, estimates) + } +} + +#[maybe_async] +impl GetHeight for EsploraBlockchain { + fn get_height(&self) -> Result { + Ok(await_or_block!(self.url_client._get_height())?) + } +} + +#[maybe_async] +impl GetTx for EsploraBlockchain { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + Ok(await_or_block!(self.url_client._get_tx(txid))?) + } +} + +#[maybe_async] +impl WalletSync for EsploraBlockchain { + fn wallet_setup( &self, database: &mut D, - _progress_update: P, + _progress_update: Box, ) -> Result<(), Error> { use crate::blockchain::script_sync::Request; let mut request = script_sync::start(database, self.stop_gap)?; @@ -180,23 +206,6 @@ impl Blockchain for EsploraBlockchain { Ok(()) } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(await_or_block!(self.url_client._get_tx(txid))?) - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - Ok(await_or_block!(self.url_client._broadcast(tx))?) - } - - fn get_height(&self) -> Result { - Ok(await_or_block!(self.url_client._get_height())?) - } - - fn estimate_fee(&self, target: usize) -> Result { - let estimates = await_or_block!(self.url_client._get_fee_estimates())?; - super::into_fee_rate(target, estimates) - } } impl UrlClient { diff --git a/src/blockchain/esplora/ureq.rs b/src/blockchain/esplora/ureq.rs index 856f6958..9bfa378f 100644 --- a/src/blockchain/esplora/ureq.rs +++ b/src/blockchain/esplora/ureq.rs @@ -87,10 +87,34 @@ impl Blockchain for EsploraBlockchain { .collect() } - fn setup( + fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { + let _txid = self.url_client._broadcast(tx)?; + Ok(()) + } + + fn estimate_fee(&self, target: usize) -> Result { + let estimates = self.url_client._get_fee_estimates()?; + super::into_fee_rate(target, estimates) + } +} + +impl GetHeight for EsploraBlockchain { + fn get_height(&self) -> Result { + Ok(self.url_client._get_height()?) + } +} + +impl GetTx for EsploraBlockchain { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + Ok(self.url_client._get_tx(txid)?) + } +} + +impl WalletSync for EsploraBlockchain { + fn wallet_setup( &self, database: &mut D, - _progress_update: P, + _progress_update: Box, ) -> Result<(), Error> { use crate::blockchain::script_sync::Request; let mut request = script_sync::start(database, self.stop_gap)?; @@ -179,24 +203,6 @@ impl Blockchain for EsploraBlockchain { Ok(()) } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(self.url_client._get_tx(txid)?) - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - let _txid = self.url_client._broadcast(tx)?; - Ok(()) - } - - fn get_height(&self) -> Result { - Ok(self.url_client._get_height()?) - } - - fn estimate_fee(&self, target: usize) -> Result { - let estimates = self.url_client._get_fee_estimates()?; - super::into_fee_rate(target, estimates) - } } impl UrlClient { diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index bbf0303d..714fdf6a 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -86,28 +86,50 @@ pub enum Capability { /// Trait that defines the actions that must be supported by a blockchain backend #[maybe_async] -pub trait Blockchain { +pub trait Blockchain: WalletSync + GetHeight + GetTx { /// Return the set of [`Capability`] supported by this backend fn get_capabilities(&self) -> HashSet; + /// Broadcast a transaction + fn broadcast(&self, tx: &Transaction) -> Result<(), Error>; + /// Estimate the fee rate required to confirm a transaction in a given `target` of blocks + fn estimate_fee(&self, target: usize) -> Result; +} +/// Trait for getting the current height of the blockchain. +#[maybe_async] +pub trait GetHeight { + /// Return the current height + fn get_height(&self) -> Result; +} + +#[maybe_async] +/// Trait for getting a transaction by txid +pub trait GetTx { + /// Fetch a transaction given its txid + fn get_tx(&self, txid: &Txid) -> Result, Error>; +} + +/// Trait for blockchains that can sync by updating the database directly. +#[maybe_async] +pub trait WalletSync { /// Setup the backend and populate the internal database for the first time /// - /// This method is the equivalent of [`Blockchain::sync`], but it's guaranteed to only be + /// This method is the equivalent of [`Self::wallet_sync`], but it's guaranteed to only be /// called once, at the first [`Wallet::sync`](crate::wallet::Wallet::sync). /// /// The rationale behind the distinction between `sync` and `setup` is that some custom backends /// might need to perform specific actions only the first time they are synced. /// /// For types that do not have that distinction, only this method can be implemented, since - /// [`Blockchain::sync`] defaults to calling this internally if not overridden. - fn setup( + /// [`WalletSync::wallet_sync`] defaults to calling this internally if not overridden. + /// Populate the internal database with transactions and UTXOs + fn wallet_setup( &self, database: &mut D, - progress_update: P, + progress_update: Box, ) -> Result<(), Error>; - /// Populate the internal database with transactions and UTXOs - /// - /// If not overridden, it defaults to calling [`Blockchain::setup`] internally. + + /// If not overridden, it defaults to calling [`Self::wallet_setup`] internally. /// /// This method should implement the logic required to iterate over the list of the wallet's /// script_pubkeys using [`Database::iter_script_pubkeys`] and look for relevant transactions @@ -124,23 +146,13 @@ pub trait Blockchain { /// [`BatchOperations::set_tx`]: crate::database::BatchOperations::set_tx /// [`BatchOperations::set_utxo`]: crate::database::BatchOperations::set_utxo /// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo - fn sync( + fn wallet_sync( &self, database: &mut D, - progress_update: P, + progress_update: Box, ) -> Result<(), Error> { - maybe_await!(self.setup(database, progress_update)) + maybe_await!(self.wallet_setup(database, progress_update)) } - - /// Fetch a transaction from the blockchain given its txid - fn get_tx(&self, txid: &Txid) -> Result, Error>; - /// Broadcast a transaction - fn broadcast(&self, tx: &Transaction) -> Result<(), Error>; - - /// Return the current height - fn get_height(&self) -> Result; - /// Estimate the fee rate required to confirm a transaction in a given `target` of blocks - fn estimate_fee(&self, target: usize) -> Result; } /// Trait for [`Blockchain`] types that can be created given a configuration @@ -155,9 +167,9 @@ pub trait ConfigurableBlockchain: Blockchain + Sized { /// Data sent with a progress update over a [`channel`] pub type ProgressData = (f32, Option); -/// Trait for types that can receive and process progress updates during [`Blockchain::sync`] and -/// [`Blockchain::setup`] -pub trait Progress: Send { +/// Trait for types that can receive and process progress updates during [`WalletSync::wallet_sync`] and +/// [`WalletSync::wallet_setup`] +pub trait Progress: Send + 'static + core::fmt::Debug { /// Send a new progress update /// /// The `progress` value should be in the range 0.0 - 100.0, and the `message` value is an @@ -182,7 +194,7 @@ impl Progress for Sender { } /// Type that implements [`Progress`] and drops every update received -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Default, Debug)] pub struct NoopProgress; /// Create a new instance of [`NoopProgress`] @@ -197,7 +209,7 @@ impl Progress for NoopProgress { } /// Type that implements [`Progress`] and logs at level `INFO` every update received -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Default, Debug)] pub struct LogProgress; /// Create a new instance of [`LogProgress`] @@ -223,33 +235,44 @@ impl Blockchain for Arc { maybe_await!(self.deref().get_capabilities()) } - fn setup( - &self, - database: &mut D, - progress_update: P, - ) -> Result<(), Error> { - maybe_await!(self.deref().setup(database, progress_update)) - } - - fn sync( - &self, - database: &mut D, - progress_update: P, - ) -> Result<(), Error> { - maybe_await!(self.deref().sync(database, progress_update)) - } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - maybe_await!(self.deref().get_tx(txid)) - } fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { maybe_await!(self.deref().broadcast(tx)) } - fn get_height(&self) -> Result { - maybe_await!(self.deref().get_height()) - } fn estimate_fee(&self, target: usize) -> Result { maybe_await!(self.deref().estimate_fee(target)) } } + +#[maybe_async] +impl GetTx for Arc { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + maybe_await!(self.deref().get_tx(txid)) + } +} + +#[maybe_async] +impl GetHeight for Arc { + fn get_height(&self) -> Result { + maybe_await!(self.deref().get_height()) + } +} + +#[maybe_async] +impl WalletSync for Arc { + fn wallet_setup( + &self, + database: &mut D, + progress_update: Box, + ) -> Result<(), Error> { + maybe_await!(self.deref().wallet_setup(database, progress_update)) + } + + fn wallet_sync( + &self, + database: &mut D, + progress_update: Box, + ) -> Result<(), Error> { + maybe_await!(self.deref().wallet_sync(database, progress_update)) + } +} diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs index 1b56cbaa..e4a79846 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -33,7 +33,7 @@ use crate::bitcoin::consensus::deserialize; use crate::bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid}; -use crate::blockchain::{Blockchain, Capability, ConfigurableBlockchain, Progress}; +use crate::blockchain::*; use crate::database::{BatchDatabase, DatabaseUtils}; use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails}; use bitcoincore_rpc::json::{ @@ -139,10 +139,39 @@ impl Blockchain for RpcBlockchain { self.capabilities.clone() } - fn setup( + fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { + Ok(self.client.send_raw_transaction(tx).map(|_| ())?) + } + + fn estimate_fee(&self, target: usize) -> Result { + let sat_per_kb = self + .client + .estimate_smart_fee(target as u16, None)? + .fee_rate + .ok_or(Error::FeeRateUnavailable)? + .as_sat() as f64; + + Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32)) + } +} + +impl GetTx for RpcBlockchain { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + Ok(Some(self.client.get_raw_transaction(txid, None)?)) + } +} + +impl GetHeight for RpcBlockchain { + fn get_height(&self) -> Result { + Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?) + } +} + +impl WalletSync for RpcBlockchain { + fn wallet_setup( &self, database: &mut D, - progress_update: P, + progress_update: Box, ) -> Result<(), Error> { let mut scripts_pubkeys = database.iter_script_pubkeys(Some(KeychainKind::External))?; scripts_pubkeys.extend(database.iter_script_pubkeys(Some(KeychainKind::Internal))?); @@ -187,13 +216,13 @@ impl Blockchain for RpcBlockchain { } } - self.sync(database, progress_update) + self.wallet_sync(database, progress_update) } - fn sync( + fn wallet_sync( &self, db: &mut D, - _progress_update: P, + _progress_update: Box, ) -> Result<(), Error> { let mut indexes = HashMap::new(); for keykind in &[KeychainKind::External, KeychainKind::Internal] { @@ -289,22 +318,24 @@ impl Blockchain for RpcBlockchain { } } - let current_utxos: HashSet<_> = current_utxo + // Filter out trasactions that are for script pubkeys that aren't in this wallet. + let current_utxos = current_utxo .into_iter() - .map(|u| { - Ok(LocalUtxo { - outpoint: OutPoint::new(u.txid, u.vout), - keychain: db - .get_path_from_script_pubkey(&u.script_pub_key)? - .ok_or(Error::TransactionNotFound)? - .0, - txout: TxOut { - value: u.amount.as_sat(), - script_pubkey: u.script_pub_key, - }, - }) - }) - .collect::>()?; + .filter_map( + |u| match db.get_path_from_script_pubkey(&u.script_pub_key) { + Err(e) => Some(Err(e)), + Ok(None) => None, + Ok(Some(path)) => Some(Ok(LocalUtxo { + outpoint: OutPoint::new(u.txid, u.vout), + keychain: path.0, + txout: TxOut { + value: u.amount.as_sat(), + script_pubkey: u.script_pub_key, + }, + })), + }, + ) + .collect::, Error>>()?; let spent: HashSet<_> = known_utxos.difference(¤t_utxos).collect(); for s in spent { @@ -324,29 +355,6 @@ impl Blockchain for RpcBlockchain { Ok(()) } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(Some(self.client.get_raw_transaction(txid, None)?)) - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - Ok(self.client.send_raw_transaction(tx).map(|_| ())?) - } - - fn get_height(&self) -> Result { - Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?) - } - - fn estimate_fee(&self, target: usize) -> Result { - let sat_per_kb = self - .client - .estimate_smart_fee(target as u16, None)? - .fee_rate - .ok_or(Error::FeeRateUnavailable)? - .as_sat() as f64; - - Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32)) - } } impl ConfigurableBlockchain for RpcBlockchain { diff --git a/src/database/any.rs b/src/database/any.rs index 8b626e4b..c4c74dc7 100644 --- a/src/database/any.rs +++ b/src/database/any.rs @@ -23,12 +23,12 @@ //! # use bdk::database::{AnyDatabase, MemoryDatabase}; //! # use bdk::{Wallet}; //! let memory = MemoryDatabase::default(); -//! let wallet_memory = Wallet::new_offline("...", None, Network::Testnet, memory)?; +//! let wallet_memory = Wallet::new("...", None, Network::Testnet, memory)?; //! //! # #[cfg(feature = "key-value-db")] //! # { //! let sled = sled::open("my-database")?.open_tree("default_tree")?; -//! let wallet_sled = Wallet::new_offline("...", None, Network::Testnet, sled)?; +//! let wallet_sled = Wallet::new("...", None, Network::Testnet, sled)?; //! # } //! # Ok::<(), bdk::Error>(()) //! ``` @@ -42,7 +42,7 @@ //! # use bdk::{Wallet}; //! let config = serde_json::from_str("...")?; //! let database = AnyDatabase::from_config(&config)?; -//! let wallet = Wallet::new_offline("...", None, Network::Testnet, database)?; +//! let wallet = Wallet::new("...", None, Network::Testnet, database)?; //! # Ok::<(), bdk::Error>(()) //! ``` diff --git a/src/database/memory.rs b/src/database/memory.rs index afde6fee..0d5b7ef3 100644 --- a/src/database/memory.rs +++ b/src/database/memory.rs @@ -554,7 +554,7 @@ macro_rules! doctest_wallet { Some(100), ); - $crate::Wallet::new_offline( + $crate::Wallet::new( &descriptors.0, descriptors.1.as_ref(), Network::Regtest, diff --git a/src/descriptor/template.rs b/src/descriptor/template.rs index 9b5d025e..fb4e7b04 100644 --- a/src/descriptor/template.rs +++ b/src/descriptor/template.rs @@ -79,7 +79,7 @@ impl IntoWalletDescriptor for T { /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// P2Pkh(key), /// None, /// Network::Testnet, @@ -113,7 +113,7 @@ impl> DescriptorTemplate for P2Pkh { /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// P2Wpkh_P2Sh(key), /// None, /// Network::Testnet, @@ -148,7 +148,7 @@ impl> DescriptorTemplate for P2Wpkh_P2Sh { /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// P2Wpkh(key), /// None, /// Network::Testnet, @@ -186,7 +186,7 @@ impl> DescriptorTemplate for P2Wpkh { /// use bdk::template::Bip44; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip44(key.clone(), KeychainKind::External), /// Some(Bip44(key, KeychainKind::Internal)), /// Network::Testnet, @@ -226,7 +226,7 @@ impl> DescriptorTemplate for Bip44 { /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip44Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, @@ -262,7 +262,7 @@ impl> DescriptorTemplate for Bip44Public { /// use bdk::template::Bip49; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip49(key.clone(), KeychainKind::External), /// Some(Bip49(key, KeychainKind::Internal)), /// Network::Testnet, @@ -302,7 +302,7 @@ impl> DescriptorTemplate for Bip49 { /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip49Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, @@ -338,7 +338,7 @@ impl> DescriptorTemplate for Bip49Public { /// use bdk::template::Bip84; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip84(key.clone(), KeychainKind::External), /// Some(Bip84(key, KeychainKind::Internal)), /// Network::Testnet, @@ -378,7 +378,7 @@ impl> DescriptorTemplate for Bip84 { /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip84Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, diff --git a/src/lib.rs b/src/lib.rs index 0423dc4f..4b8a2228 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,22 +53,22 @@ ### Example ```no_run -use bdk::Wallet; +use bdk::{Wallet, SyncOptions}; use bdk::database::MemoryDatabase; -use bdk::blockchain::{noop_progress, ElectrumBlockchain}; +use bdk::blockchain::ElectrumBlockchain; use bdk::electrum_client::Client; fn main() -> Result<(), bdk::Error> { let client = Client::new("ssl://electrum.blockstream.info:60002")?; + let blockchain = ElectrumBlockchain::from(client); let wallet = Wallet::new( "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), bitcoin::Network::Testnet, MemoryDatabase::default(), - ElectrumBlockchain::from(client) )?; - wallet.sync(noop_progress(), None)?; + wallet.sync(&blockchain, SyncOptions::default())?; println!("Descriptor balance: {} SAT", wallet.get_balance()?); @@ -87,7 +87,7 @@ fn main() -> Result<(), bdk::Error> { //! use bdk::wallet::AddressIndex::New; //! //! fn main() -> Result<(), bdk::Error> { -//! let wallet = Wallet::new_offline( +//! let wallet = Wallet::new( //! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", //! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), //! bitcoin::Network::Testnet, @@ -108,9 +108,9 @@ fn main() -> Result<(), bdk::Error> { ### Example ```no_run -use bdk::{FeeRate, Wallet}; +use bdk::{FeeRate, Wallet, SyncOptions}; use bdk::database::MemoryDatabase; -use bdk::blockchain::{noop_progress, ElectrumBlockchain}; +use bdk::blockchain::ElectrumBlockchain; use bdk::electrum_client::Client; use bitcoin::consensus::serialize; @@ -123,10 +123,10 @@ fn main() -> Result<(), bdk::Error> { Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), bitcoin::Network::Testnet, MemoryDatabase::default(), - ElectrumBlockchain::from(client) )?; + let blockchain = ElectrumBlockchain::from(client); - wallet.sync(noop_progress(), None)?; + wallet.sync(&blockchain, SyncOptions::default())?; let send_to = wallet.get_address(New)?; let (psbt, details) = { @@ -160,7 +160,7 @@ fn main() -> Result<(), bdk::Error> { //! use bdk::database::MemoryDatabase; //! //! fn main() -> Result<(), bdk::Error> { -//! let wallet = Wallet::new_offline( +//! let wallet = Wallet::new( //! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", //! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), //! bitcoin::Network::Testnet, @@ -272,6 +272,7 @@ pub use wallet::address_validator; pub use wallet::signer; pub use wallet::signer::SignOptions; pub use wallet::tx_builder::TxBuilder; +pub use wallet::SyncOptions; pub use wallet::Wallet; /// Get the version of BDK at runtime diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index 30d926ce..6ac91e88 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -361,10 +361,10 @@ macro_rules! bdk_blockchain_tests { mod bdk_blockchain_tests { use $crate::bitcoin::{Transaction, Network}; use $crate::testutils::blockchain_tests::TestClient; - use $crate::blockchain::noop_progress; + use $crate::blockchain::Blockchain; use $crate::database::MemoryDatabase; use $crate::types::KeychainKind; - use $crate::{Wallet, FeeRate}; + use $crate::{Wallet, FeeRate, SyncOptions}; use $crate::testutils; use super::*; @@ -375,11 +375,11 @@ macro_rules! bdk_blockchain_tests { $block } - fn get_wallet_from_descriptors(descriptors: &(String, Option), test_client: &TestClient) -> Wallet<$blockchain, MemoryDatabase> { - Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new(), get_blockchain(test_client)).unwrap() + fn get_wallet_from_descriptors(descriptors: &(String, Option)) -> Wallet { + Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new()).unwrap() } - fn init_single_sig() -> (Wallet<$blockchain, MemoryDatabase>, (String, Option), TestClient) { + fn init_single_sig() -> (Wallet, $blockchain, (String, Option), TestClient) { let _ = env_logger::try_init(); let descriptors = testutils! { @@ -387,13 +387,14 @@ macro_rules! bdk_blockchain_tests { }; let test_client = TestClient::default(); - let wallet = get_wallet_from_descriptors(&descriptors, &test_client); + let blockchain = get_blockchain(&test_client); + let wallet = get_wallet_from_descriptors(&descriptors); // rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool #[cfg(feature = "test-rpc")] - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - (wallet, descriptors, test_client) + (wallet, blockchain, descriptors, test_client) } #[test] @@ -401,7 +402,7 @@ macro_rules! bdk_blockchain_tests { use std::ops::Deref; use crate::database::Database; - let (wallet, descriptors, mut test_client) = init_single_sig(); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); let tx = testutils! { @tx ( (@external descriptors, 0) => 50_000 ) @@ -414,7 +415,7 @@ macro_rules! bdk_blockchain_tests { #[cfg(not(feature = "test-rpc"))] assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none"); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); @@ -429,7 +430,7 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_stop_gap_20() { - let (wallet, descriptors, mut test_client) = init_single_sig(); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); test_client.receive(testutils! { @tx ( (@external descriptors, 5) => 50_000 ) @@ -438,7 +439,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 25) => 50_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 100_000, "incorrect balance"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs"); @@ -446,16 +447,16 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_before_and_after_receive() { - let (wallet, descriptors, mut test_client) = init_single_sig(); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 0); test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs"); @@ -463,13 +464,13 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_multiple_outputs_same_tx() { - let (wallet, descriptors, mut test_client) = init_single_sig(); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); let txid = test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 105_000, "incorrect balance"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs"); @@ -484,7 +485,7 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_receive_multi() { - let (wallet, descriptors, mut test_client) = init_single_sig(); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) @@ -493,7 +494,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 5) => 25_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs"); @@ -502,32 +503,32 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_address_reuse() { - let (wallet, descriptors, mut test_client) = init_single_sig(); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000); test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 25_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); } #[test] fn test_sync_receive_rbf_replaced() { - let (wallet, descriptors, mut test_client) = init_single_sig(); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); let txid = test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs"); @@ -541,7 +542,7 @@ macro_rules! bdk_blockchain_tests { let new_txid = test_client.bump_fee(&txid); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after bump"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs after bump"); @@ -559,13 +560,13 @@ macro_rules! bdk_blockchain_tests { #[cfg(not(feature = "esplora"))] #[test] fn test_sync_reorg_block() { - let (wallet, descriptors, mut test_client) = init_single_sig(); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); let txid = test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs"); @@ -578,7 +579,7 @@ macro_rules! bdk_blockchain_tests { // Invalidate 1 block test_client.invalidate(1); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after invalidate"); @@ -589,15 +590,15 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_after_send() { - let (wallet, descriptors, mut test_client) = init_single_sig(); - println!("{}", descriptors.0); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + println!("{}", descriptors.0); let node_addr = test_client.get_node_address(None); test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); let mut builder = wallet.build_tx(); @@ -607,8 +608,8 @@ macro_rules! bdk_blockchain_tests { assert!(finalized, "Cannot finalize transaction"); let tx = psbt.extract_tx(); println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); - wallet.broadcast(&tx).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + blockchain.broadcast(&tx).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs"); @@ -619,24 +620,24 @@ macro_rules! bdk_blockchain_tests { /// The coins should only be received once! #[test] fn test_sync_double_receive() { - let (wallet, descriptors, mut test_client) = init_single_sig(); - let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None), &test_client); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None)); // need to sync so rpc can start watching - receiver_wallet.sync(noop_progress(), None).unwrap(); + receiver_wallet.sync(&blockchain, SyncOptions::default()).unwrap(); test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).expect("sync"); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address; let tx1 = { let mut builder = wallet.build_tx(); builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf(); - let (mut psbt, _details) = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + let (mut psbt, _details) = builder.finish().expect("building first tx"); + let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing first tx"); assert!(finalized, "Cannot finalize transaction"); psbt.extract_tx() }; @@ -644,22 +645,22 @@ macro_rules! bdk_blockchain_tests { let tx2 = { let mut builder = wallet.build_tx(); builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf().fee_rate(FeeRate::from_sat_per_vb(5.0)); - let (mut psbt, _details) = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + let (mut psbt, _details) = builder.finish().expect("building replacement tx"); + let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing replacement tx"); assert!(finalized, "Cannot finalize transaction"); psbt.extract_tx() }; - wallet.broadcast(&tx1).unwrap(); - wallet.broadcast(&tx2).unwrap(); + blockchain.broadcast(&tx1).expect("broadcasting first"); + blockchain.broadcast(&tx2).expect("broadcasting replacement"); - receiver_wallet.sync(noop_progress(), None).unwrap(); - assert_eq!(receiver_wallet.get_balance().unwrap(), 49_000, "should have received coins once and only once"); + receiver_wallet.sync(&blockchain, SyncOptions::default()).expect("syncing receiver"); + assert_eq!(receiver_wallet.get_balance().expect("balance"), 49_000, "should have received coins once and only once"); } #[test] fn test_sync_many_sends_to_a_single_address() { - let (wallet, descriptors, mut test_client) = init_single_sig(); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); for _ in 0..4 { // split this up into multiple blocks so rpc doesn't get angry @@ -678,22 +679,22 @@ macro_rules! bdk_blockchain_tests { }); } - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 100_000); } #[test] fn test_update_confirmation_time_after_generate() { - let (wallet, descriptors, mut test_client) = init_single_sig(); - println!("{}", descriptors.0); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + println!("{}", descriptors.0); let node_addr = test_client.get_node_address(None); let received_txid = test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::>(); @@ -701,7 +702,7 @@ macro_rules! bdk_blockchain_tests { assert!(details.confirmation_time.is_none()); test_client.generate(1, Some(node_addr)); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::>(); let details = tx_map.get(&received_txid).unwrap(); @@ -711,13 +712,13 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_outgoing_from_scratch() { - let (wallet, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + let node_addr = test_client.get_node_address(None); let received_txid = test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); let mut builder = wallet.build_tx(); @@ -726,25 +727,26 @@ macro_rules! bdk_blockchain_tests { let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); - let sent_txid = wallet.broadcast(&psbt.extract_tx()).unwrap(); + let sent_tx = psbt.extract_tx(); + blockchain.broadcast(&sent_tx).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive"); // empty wallet - let wallet = get_wallet_from_descriptors(&descriptors, &test_client); + let wallet = get_wallet_from_descriptors(&descriptors); #[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti test_client.generate(1, Some(node_addr)); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::>(); let received = tx_map.get(&received_txid).unwrap(); assert_eq!(received.received, 50_000, "incorrect received from receiver"); assert_eq!(received.sent, 0, "incorrect sent from receiver"); - let sent = tx_map.get(&sent_txid).unwrap(); + let sent = tx_map.get(&sent_tx.txid()).unwrap(); assert_eq!(sent.received, details.received, "incorrect received from sender"); assert_eq!(sent.sent, details.sent, "incorrect sent from sender"); assert_eq!(sent.fee.unwrap_or(0), details.fee.unwrap_or(0), "incorrect fees from sender"); @@ -752,14 +754,14 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_long_change_chain() { - let (wallet, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + let node_addr = test_client.get_node_address(None); test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); let mut total_sent = 0; @@ -769,38 +771,38 @@ macro_rules! bdk_blockchain_tests { let (mut psbt, details) = builder.finish().unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); - wallet.broadcast(&psbt.extract_tx()).unwrap(); + blockchain.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); total_sent += 5_000 + details.fee.unwrap_or(0); } - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance after chain"); // empty wallet - let wallet = get_wallet_from_descriptors(&descriptors, &test_client); + let wallet = get_wallet_from_descriptors(&descriptors); #[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti test_client.generate(1, Some(node_addr)); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance empty wallet"); } #[test] fn test_sync_bump_fee_basic() { - let (wallet, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + let node_addr = test_client.get_node_address(None); test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); let mut builder = wallet.build_tx(); @@ -808,8 +810,8 @@ macro_rules! bdk_blockchain_tests { let (mut psbt, details) = builder.finish().unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); - wallet.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + blockchain.broadcast(&psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees"); assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance from received"); @@ -818,8 +820,8 @@ macro_rules! bdk_blockchain_tests { let (mut new_psbt, new_details) = builder.finish().expect("fee bump tx"); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); - wallet.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump"); assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance from received after bump"); @@ -828,14 +830,14 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_bump_fee_remove_change() { - let (wallet, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + let node_addr = test_client.get_node_address(None); test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); let mut builder = wallet.build_tx(); @@ -843,8 +845,8 @@ macro_rules! bdk_blockchain_tests { let (mut psbt, details) = builder.finish().unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); - wallet.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + blockchain.broadcast(&psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send"); assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect received after send"); @@ -853,8 +855,8 @@ macro_rules! bdk_blockchain_tests { let (mut new_psbt, new_details) = builder.finish().unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); - wallet.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after change removal"); assert_eq!(new_details.received, 0, "incorrect received after change removal"); @@ -863,14 +865,14 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_bump_fee_add_input_simple() { - let (wallet, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + let node_addr = test_client.get_node_address(None); test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); let mut builder = wallet.build_tx(); @@ -878,8 +880,8 @@ macro_rules! bdk_blockchain_tests { let (mut psbt, details) = builder.finish().unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); - wallet.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + blockchain.broadcast(&psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send"); assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send"); @@ -888,22 +890,22 @@ macro_rules! bdk_blockchain_tests { let (mut new_psbt, new_details) = builder.finish().unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); - wallet.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(new_details.sent, 75_000, "incorrect sent"); assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance after add input"); } #[test] fn test_sync_bump_fee_add_input_no_change() { - let (wallet, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + let node_addr = test_client.get_node_address(None); test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); let mut builder = wallet.build_tx(); @@ -911,8 +913,8 @@ macro_rules! bdk_blockchain_tests { let (mut psbt, details) = builder.finish().unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); - wallet.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + blockchain.broadcast(&psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send"); assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send"); @@ -923,8 +925,8 @@ macro_rules! bdk_blockchain_tests { let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); - wallet.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(new_details.sent, 75_000, "incorrect sent"); assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after add input"); assert_eq!(new_details.received, 0, "incorrect received after add input"); @@ -933,13 +935,13 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_add_data() { - let (wallet, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + let node_addr = test_client.get_node_address(None); let _ = test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); let mut builder = wallet.build_tx(); @@ -952,22 +954,22 @@ macro_rules! bdk_blockchain_tests { let tx = psbt.extract_tx(); let serialized_tx = bitcoin::consensus::encode::serialize(&tx); assert!(serialized_tx.windows(data.len()).any(|e| e==data), "cannot find op_return data in transaction"); - let sent_txid = wallet.broadcast(&tx).unwrap(); + blockchain.broadcast(&tx).unwrap(); test_client.generate(1, Some(node_addr)); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send"); let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::>(); - let _ = tx_map.get(&sent_txid).unwrap(); + let _ = tx_map.get(&tx.txid()).unwrap(); } #[test] fn test_sync_receive_coinbase() { - let (wallet, _, mut test_client) = init_single_sig(); + let (wallet, blockchain, _, mut test_client) = init_single_sig(); let wallet_addr = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address; - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance"); test_client.generate(1, Some(wallet_addr)); @@ -980,7 +982,7 @@ macro_rules! bdk_blockchain_tests { } - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase"); } @@ -993,7 +995,7 @@ macro_rules! bdk_blockchain_tests { use bitcoincore_rpc::jsonrpc::serde_json::Value; use bitcoincore_rpc::{Auth, Client, RpcApi}; - let (wallet, descriptors, mut test_client) = init_single_sig(); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); // TODO remove once rust-bitcoincore-rpc with PR 199 released // https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/199 @@ -1056,7 +1058,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "wallet has incorrect balance"); // 4. Send 25_000 sats from test BDK wallet to test bitcoind node taproot wallet @@ -1067,8 +1069,8 @@ macro_rules! bdk_blockchain_tests { let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized, "wallet cannot finalize transaction"); let tx = psbt.extract_tx(); - wallet.broadcast(&tx).unwrap(); - wallet.sync(noop_progress(), None).unwrap(); + blockchain.broadcast(&tx).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), details.received, "wallet has incorrect balance after send"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "wallet has incorrect number of txs"); assert_eq!(wallet.list_unspent().unwrap().len(), 1, "wallet has incorrect number of unspents"); @@ -1095,7 +1097,7 @@ macro_rules! bdk_blockchain_tests { // 2. // Core (#2) -> Us (#4) - let (wallet, _, mut test_client) = init_single_sig(); + let (wallet, blockchain, _, mut test_client) = init_single_sig(); let bdk_address = wallet.get_address(AddressIndex::New).unwrap().address; let core_address = test_client.get_new_address(None, None).unwrap(); let tx = testutils! { @@ -1106,7 +1108,7 @@ macro_rules! bdk_blockchain_tests { let txid_1 = test_client.receive(tx); let tx_1: Transaction = deserialize(&test_client.get_transaction(&txid_1, None).unwrap().hex).unwrap(); let vout_1 = tx_1.output.into_iter().position(|o| o.script_pubkey == core_address.script_pubkey()).unwrap() as u32; - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); let tx_1 = wallet.list_transactions(false).unwrap().into_iter().find(|tx| tx.txid == txid_1).unwrap(); assert_eq!(tx_1.received, 50_000); assert_eq!(tx_1.sent, 0); @@ -1117,7 +1119,7 @@ macro_rules! bdk_blockchain_tests { }; let txid_2 = test_client.receive(tx); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); let tx_2 = wallet.list_transactions(false).unwrap().into_iter().find(|tx| tx.txid == txid_2).unwrap(); assert_eq!(tx_2.received, 10_000); assert_eq!(tx_2.sent, 0); diff --git a/src/wallet/address_validator.rs b/src/wallet/address_validator.rs index ba675337..a0e418ab 100644 --- a/src/wallet/address_validator.rs +++ b/src/wallet/address_validator.rs @@ -55,7 +55,7 @@ //! } //! //! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; -//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?; +//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?; //! wallet.add_address_validator(Arc::new(PrintAddressAndContinue)); //! //! let address = wallet.get_address(New)?; diff --git a/src/wallet/export.rs b/src/wallet/export.rs index 85898345..f531989e 100644 --- a/src/wallet/export.rs +++ b/src/wallet/export.rs @@ -30,7 +30,7 @@ //! }"#; //! //! let import = WalletExport::from_str(import)?; -//! let wallet = Wallet::new_offline( +//! let wallet = Wallet::new( //! &import.descriptor(), //! import.change_descriptor().as_ref(), //! Network::Testnet, @@ -45,7 +45,7 @@ //! # use bdk::database::*; //! # use bdk::wallet::export::*; //! # use bdk::*; -//! let wallet = Wallet::new_offline( +//! let wallet = Wallet::new( //! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)", //! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"), //! Network::Testnet, @@ -111,8 +111,8 @@ impl WalletExport { /// /// If the database is empty or `include_blockheight` is false, the `blockheight` field /// returned will be `0`. - pub fn export_wallet( - wallet: &Wallet, + pub fn export_wallet( + wallet: &Wallet, label: &str, include_blockheight: bool, ) -> Result { @@ -241,7 +241,7 @@ mod test { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; - let wallet = Wallet::new_offline( + let wallet = Wallet::new( descriptor, Some(change_descriptor), Network::Bitcoin, @@ -265,8 +265,7 @@ mod test { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; - let wallet = - Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap(); + let wallet = Wallet::new(descriptor, None, Network::Bitcoin, get_test_db()).unwrap(); WalletExport::export_wallet(&wallet, "Test Label", true).unwrap(); } @@ -279,7 +278,7 @@ mod test { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)"; - let wallet = Wallet::new_offline( + let wallet = Wallet::new( descriptor, Some(change_descriptor), Network::Bitcoin, @@ -302,7 +301,7 @@ mod test { [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\ ))"; - let wallet = Wallet::new_offline( + let wallet = Wallet::new( descriptor, Some(change_descriptor), Network::Testnet, @@ -322,7 +321,7 @@ mod test { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; - let wallet = Wallet::new_offline( + let wallet = Wallet::new( descriptor, Some(change_descriptor), Network::Bitcoin, diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 93a0d01e..fd105478 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -55,7 +55,7 @@ use signer::{SignOptions, Signer, SignerOrdering, SignersContainer}; use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams}; use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx}; -use crate::blockchain::{Blockchain, Progress}; +use crate::blockchain::{GetHeight, NoopProgress, Progress, WalletSync}; use crate::database::memory::MemoryDatabase; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime}; use crate::descriptor::derived::AsDerived; @@ -75,16 +75,17 @@ const CACHE_ADDR_BATCH_SIZE: u32 = 100; /// A Bitcoin wallet /// -/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a -/// [`blockchain`](trait@crate::blockchain::Blockchain) and implements the basic functions that a Bitcoin wallets -/// needs to operate, like [generating addresses](Wallet::get_address), [returning the balance](Wallet::get_balance), -/// [creating transactions](Wallet::build_tx), etc. +/// The `Wallet` struct acts as a way of coherently interfacing with output descriptors and related transactions. +/// Its main components are: /// -/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided -/// implements [`Blockchain`], or "offline" if it is the unit type `()`. Offline wallets only expose -/// methods that don't need any interaction with the blockchain to work. +/// 1. output *descriptors* from which it can derive addresses. +/// 2. A [`Database`] where it tracks transactions and utxos related to the descriptors. +/// 3. [`Signer`]s that can contribute signatures to addresses instantiated from the descriptors. +/// +/// [`Database`]: crate::database::Database +/// [`Signer`]: crate::signer::Signer #[derive(Debug)] -pub struct Wallet { +pub struct Wallet { descriptor: ExtendedDescriptor, change_descriptor: Option, @@ -95,88 +96,11 @@ pub struct Wallet { network: Network, - current_height: Option, - - client: B, database: RefCell, secp: SecpCtx, } -impl Wallet<(), D> -where - D: BatchDatabase, -{ - /// Create a new "offline" wallet - pub fn new_offline( - descriptor: E, - change_descriptor: Option, - network: Network, - database: D, - ) -> Result { - Self::_new(descriptor, change_descriptor, network, database, (), None) - } -} - -impl Wallet -where - D: BatchDatabase, -{ - fn _new( - descriptor: E, - change_descriptor: Option, - network: Network, - mut database: D, - client: B, - current_height: Option, - ) -> Result { - let secp = Secp256k1::new(); - - let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?; - database.check_descriptor_checksum( - KeychainKind::External, - get_checksum(&descriptor.to_string())?.as_bytes(), - )?; - let signers = Arc::new(SignersContainer::from(keymap)); - let (change_descriptor, change_signers) = match change_descriptor { - Some(desc) => { - let (change_descriptor, change_keymap) = - into_wallet_descriptor_checked(desc, &secp, network)?; - database.check_descriptor_checksum( - KeychainKind::Internal, - get_checksum(&change_descriptor.to_string())?.as_bytes(), - )?; - - let change_signers = Arc::new(SignersContainer::from(change_keymap)); - // if !parsed.same_structure(descriptor.as_ref()) { - // return Err(Error::DifferentDescriptorStructure); - // } - - (Some(change_descriptor), change_signers) - } - None => (None, Arc::new(SignersContainer::new())), - }; - - Ok(Wallet { - descriptor, - change_descriptor, - signers, - change_signers, - address_validators: Vec::new(), - network, - current_height, - client, - database: RefCell::new(database), - secp, - }) - } - - /// Get the Bitcoin network the wallet is using. - pub fn network(&self) -> Network { - self.network - } -} - /// The address index selection strategy to use to derived an address from the wallet's external /// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`. #[derive(Debug)] @@ -232,11 +156,83 @@ impl fmt::Display for AddressInfo { } } -// offline actions, always available -impl Wallet +#[derive(Debug, Default)] +/// Options to a [`sync`]. +/// +/// [`sync`]: Wallet::sync +pub struct SyncOptions { + /// The progress tracker which may be informed when progress is made. + pub progress: Option>, +} + +impl Wallet where D: BatchDatabase, { + #[deprecated = "Just use Wallet::new -- all wallets are offline now!"] + /// Create a new "offline" wallet + pub fn new_offline( + descriptor: E, + change_descriptor: Option, + network: Network, + database: D, + ) -> Result { + Self::new(descriptor, change_descriptor, network, database) + } + + /// Create a wallet. + /// + /// The only way this can fail is if the descriptors passed in do not match the checksums in `database`. + pub fn new( + descriptor: E, + change_descriptor: Option, + network: Network, + mut database: D, + ) -> Result { + let secp = Secp256k1::new(); + + let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?; + database.check_descriptor_checksum( + KeychainKind::External, + get_checksum(&descriptor.to_string())?.as_bytes(), + )?; + let signers = Arc::new(SignersContainer::from(keymap)); + let (change_descriptor, change_signers) = match change_descriptor { + Some(desc) => { + let (change_descriptor, change_keymap) = + into_wallet_descriptor_checked(desc, &secp, network)?; + database.check_descriptor_checksum( + KeychainKind::Internal, + get_checksum(&change_descriptor.to_string())?.as_bytes(), + )?; + + let change_signers = Arc::new(SignersContainer::from(change_keymap)); + // if !parsed.same_structure(descriptor.as_ref()) { + // return Err(Error::DifferentDescriptorStructure); + // } + + (Some(change_descriptor), change_signers) + } + None => (None, Arc::new(SignersContainer::new())), + }; + + Ok(Wallet { + descriptor, + change_descriptor, + signers, + change_signers, + address_validators: Vec::new(), + network, + database: RefCell::new(database), + secp, + }) + } + + /// Get the Bitcoin network the wallet is using. + pub fn network(&self) -> Network { + self.network + } + // Return a newly derived address for the specified `keychain`. fn get_new_address(&self, keychain: KeychainKind) -> Result { let incremented_index = self.fetch_and_increment_index(keychain)?; @@ -487,7 +483,7 @@ where /// ``` /// /// [`TxBuilder`]: crate::TxBuilder - pub fn build_tx(&self) -> TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, CreateTx> { + pub fn build_tx(&self) -> TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, CreateTx> { TxBuilder { wallet: self, params: TxParams::default(), @@ -830,7 +826,7 @@ where pub fn build_fee_bump( &self, txid: Txid, - ) -> Result, Error> { + ) -> Result, Error> { let mut details = match self.database.borrow().get_tx(&txid, true)? { None => return Err(Error::TransactionNotFound), Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound), @@ -1061,7 +1057,11 @@ where .borrow() .get_tx(&input.previous_output.txid, false)? .map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(u32::MAX)); - let current_height = sign_options.assume_height.or(self.current_height); + let last_sync_height = self + .database() + .get_sync_time()? + .map(|sync_time| sync_time.block_time.height); + let current_height = sign_options.assume_height.or(last_sync_height); debug!( "Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}", @@ -1512,61 +1512,35 @@ where pub fn database(&self) -> impl std::ops::Deref + '_ { self.database.borrow() } -} - -impl Wallet -where - B: Blockchain, - D: BatchDatabase, -{ - /// Create a new "online" wallet - #[maybe_async] - pub fn new( - descriptor: E, - change_descriptor: Option, - network: Network, - database: D, - client: B, - ) -> Result { - let current_height = Some(maybe_await!(client.get_height())? as u32); - Self::_new( - descriptor, - change_descriptor, - network, - database, - client, - current_height, - ) - } /// Sync the internal database with the blockchain #[maybe_async] - pub fn sync( + pub fn sync( &self, - progress_update: P, - max_address_param: Option, + blockchain: &B, + sync_opts: SyncOptions, ) -> Result<(), Error> { debug!("Begin sync..."); - let run_setup = - self.ensure_addresses_cached(max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE))?; + let SyncOptions { progress } = sync_opts; + let progress = progress.unwrap_or_else(|| Box::new(NoopProgress)); + + let run_setup = self.ensure_addresses_cached(CACHE_ADDR_BATCH_SIZE)?; debug!("run_setup: {}", run_setup); // TODO: what if i generate an address first and cache some addresses? // TODO: we should sync if generating an address triggers a new batch to be stored if run_setup { - maybe_await!(self - .client - .setup(self.database.borrow_mut().deref_mut(), progress_update,))?; + maybe_await!( + blockchain.wallet_setup(self.database.borrow_mut().deref_mut(), progress,) + )?; } else { - maybe_await!(self - .client - .sync(self.database.borrow_mut().deref_mut(), progress_update,))?; + maybe_await!(blockchain.wallet_sync(self.database.borrow_mut().deref_mut(), progress,))?; } let sync_time = SyncTime { block_time: BlockTime { - height: maybe_await!(self.client.get_height())?, + height: maybe_await!(blockchain.get_height())?, timestamp: time::get_timestamp(), }, }; @@ -1575,31 +1549,18 @@ where Ok(()) } - - /// Return a reference to the internal blockchain client - pub fn client(&self) -> &B { - &self.client - } - - /// Broadcast a transaction to the network - #[maybe_async] - pub fn broadcast(&self, tx: &Transaction) -> Result { - maybe_await!(self.client.broadcast(tx))?; - - Ok(tx.txid()) - } } /// Return a fake wallet that appears to be funded for testing. pub fn get_funded_wallet( descriptor: &str, ) -> ( - Wallet<(), MemoryDatabase>, + Wallet, (String, Option), bitcoin::Txid, ) { let descriptors = testutils!(@descriptors (descriptor)); - let wallet = Wallet::new_offline( + let wallet = Wallet::new( &descriptors.0, None, Network::Regtest, @@ -1649,7 +1610,7 @@ pub(crate) mod test { #[test] fn test_cache_addresses_fixed() { let db = MemoryDatabase::new(); - let wallet = Wallet::new_offline( + let wallet = Wallet::new( "wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)", None, Network::Testnet, @@ -1683,7 +1644,7 @@ pub(crate) mod test { #[test] fn test_cache_addresses() { let db = MemoryDatabase::new(); - let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap(); + let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap(); assert_eq!( wallet.get_address(New).unwrap().to_string(), @@ -1711,7 +1672,7 @@ pub(crate) mod test { #[test] fn test_cache_addresses_refill() { let db = MemoryDatabase::new(); - let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap(); + let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap(); assert_eq!( wallet.get_address(New).unwrap().to_string(), @@ -3809,7 +3770,7 @@ pub(crate) mod test { #[test] fn test_unused_address() { let db = MemoryDatabase::new(); - let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", + let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap(); assert_eq!( @@ -3826,7 +3787,7 @@ pub(crate) mod test { fn test_next_unused_address() { let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; let descriptors = testutils!(@descriptors (descriptor)); - let wallet = Wallet::new_offline( + let wallet = Wallet::new( &descriptors.0, None, Network::Testnet, @@ -3855,7 +3816,7 @@ pub(crate) mod test { #[test] fn test_peek_address_at_index() { let db = MemoryDatabase::new(); - let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", + let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap(); assert_eq!( @@ -3888,7 +3849,7 @@ pub(crate) mod test { #[test] fn test_peek_address_at_index_not_derivable() { let db = MemoryDatabase::new(); - let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)", + let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)", None, Network::Testnet, db).unwrap(); assert_eq!( @@ -3910,7 +3871,7 @@ pub(crate) mod test { #[test] fn test_reset_address_index() { let db = MemoryDatabase::new(); - let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", + let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap(); // new index 0 @@ -3947,7 +3908,7 @@ pub(crate) mod test { #[test] fn test_returns_index_and_address() { let db = MemoryDatabase::new(); - let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", + let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap(); // new index 0 @@ -4020,7 +3981,7 @@ pub(crate) mod test { fn test_get_address() { use crate::descriptor::template::Bip84; let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); - let wallet = Wallet::new_offline( + let wallet = Wallet::new( Bip84(key, KeychainKind::External), Some(Bip84(key, KeychainKind::Internal)), Network::Regtest, @@ -4040,7 +4001,7 @@ pub(crate) mod test { Address::from_str("bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa").unwrap() ); - let wallet = Wallet::new_offline( + let wallet = Wallet::new( Bip84(key, KeychainKind::External), None, Network::Regtest, diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs index 88b98461..15cec186 100644 --- a/src/wallet/signer.rs +++ b/src/wallet/signer.rs @@ -72,7 +72,7 @@ //! let custom_signer = CustomSigner::connect(); //! //! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; -//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?; +//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?; //! wallet.add_signer( //! KeychainKind::External, //! SignerOrdering(200), diff --git a/src/wallet/tx_builder.rs b/src/wallet/tx_builder.rs index 66b03785..e03a9356 100644 --- a/src/wallet/tx_builder.rs +++ b/src/wallet/tx_builder.rs @@ -120,8 +120,8 @@ impl TxBuilderContext for BumpFee {} /// [`finish`]: Self::finish /// [`coin_selection`]: Self::coin_selection #[derive(Debug)] -pub struct TxBuilder<'a, B, D, Cs, Ctx> { - pub(crate) wallet: &'a Wallet, +pub struct TxBuilder<'a, D, Cs, Ctx> { + pub(crate) wallet: &'a Wallet, pub(crate) params: TxParams, pub(crate) coin_selection: Cs, pub(crate) phantom: PhantomData, @@ -170,7 +170,7 @@ impl std::default::Default for FeePolicy { } } -impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> { +impl<'a, Cs: Clone, Ctx, D> Clone for TxBuilder<'a, D, Cs, Ctx> { fn clone(&self) -> Self { TxBuilder { wallet: self.wallet, @@ -182,8 +182,8 @@ impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> { } // methods supported by both contexts, for any CoinSelectionAlgorithm -impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> - TxBuilder<'a, B, D, Cs, Ctx> +impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> + TxBuilder<'a, D, Cs, Ctx> { /// Set a custom fee rate pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self { @@ -508,7 +508,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderConte pub fn coin_selection>( self, coin_selection: P, - ) -> TxBuilder<'a, B, D, P, Ctx> { + ) -> TxBuilder<'a, D, P, Ctx> { TxBuilder { wallet: self.wallet, params: self.params, @@ -547,7 +547,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderConte } } -impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm> TxBuilder<'a, B, D, Cs, CreateTx> { +impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> { /// Replace the recipients already added with a new list pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self { self.params.recipients = recipients; @@ -614,7 +614,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm> TxBuilder<'a, B, D, } // methods supported only by bump_fee -impl<'a, B, D: BatchDatabase> TxBuilder<'a, B, D, DefaultCoinSelectionAlgorithm, BumpFee> { +impl<'a, D: BatchDatabase> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> { /// Explicitly tells the wallet that it is allowed to reduce the fee of the output matching this /// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet /// will attempt to find a change output to shrink instead. diff --git a/src/wallet/verify.rs b/src/wallet/verify.rs index 9b563339..0f12e056 100644 --- a/src/wallet/verify.rs +++ b/src/wallet/verify.rs @@ -17,7 +17,7 @@ use std::fmt; use bitcoin::consensus::serialize; use bitcoin::{OutPoint, Transaction, Txid}; -use crate::blockchain::Blockchain; +use crate::blockchain::GetTx; use crate::database::Database; use crate::error::Error; @@ -29,7 +29,7 @@ use crate::error::Error; /// Depending on the [capabilities](crate::blockchain::Blockchain::get_capabilities) of the /// [`Blockchain`] backend, the method could fail when called with old "historical" transactions or /// with unconfirmed transactions that have been evicted from the backend's memory. -pub fn verify_tx( +pub fn verify_tx( tx: &Transaction, database: &D, blockchain: &B, @@ -104,43 +104,18 @@ impl_error!(bitcoinconsensus::Error, Consensus, VerifyError); #[cfg(test)] mod test { - use std::collections::HashSet; - + use super::*; + use crate::database::{BatchOperations, MemoryDatabase}; use bitcoin::consensus::encode::deserialize; use bitcoin::hashes::hex::FromHex; use bitcoin::{Transaction, Txid}; - use crate::blockchain::{Blockchain, Capability, Progress}; - use crate::database::{BatchDatabase, BatchOperations, MemoryDatabase}; - use crate::FeeRate; - - use super::*; - struct DummyBlockchain; - impl Blockchain for DummyBlockchain { - fn get_capabilities(&self) -> HashSet { - Default::default() - } - fn setup( - &self, - _database: &mut D, - _progress_update: P, - ) -> Result<(), Error> { - Ok(()) - } + impl GetTx for DummyBlockchain { fn get_tx(&self, _txid: &Txid) -> Result, Error> { Ok(None) } - fn broadcast(&self, _tx: &Transaction) -> Result<(), Error> { - Ok(()) - } - fn get_height(&self) -> Result { - Ok(42) - } - fn estimate_fee(&self, _target: usize) -> Result { - Ok(FeeRate::default_min_relay_fee()) - } } #[test]