diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b35dd130..a82e1e038 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`) +- Depreciated `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 d9491400f..8f2c7e749 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 560a6df50..85a23560f 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 78b69d6cc..ce875b4d5 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 0af034cb7..c3a3cb4f3 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 8470959d5..c93cf4e2c 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>(()) //! ``` @@ -88,20 +44,20 @@ macro_rules! impl_from { }; } -macro_rules! impl_inner_method { - ( $self:expr, $name:ident $(, $args:expr)* ) => { - match $self { - #[cfg(feature = "electrum")] - AnyBlockchain::Electrum(inner) => inner.$name( $($args, )* ), - #[cfg(feature = "esplora")] - AnyBlockchain::Esplora(inner) => inner.$name( $($args, )* ), - #[cfg(feature = "compact_filters")] - AnyBlockchain::CompactFilters(inner) => inner.$name( $($args, )* ), - #[cfg(feature = "rpc")] - AnyBlockchain::Rpc(inner) => inner.$name( $($args, )* ), - } - } -} +// macro_rules! impl_inner_method { +// ( $self:expr, $name:ident $(, $args:expr)* ) => { +// match $self { +// #[cfg(feature = "electrum")] +// AnyBlockchain::Electrum(inner) => inner.$name( $($args, )* ), +// #[cfg(feature = "esplora")] +// AnyBlockchain::Esplora(inner) => inner.$name( $($args, )* ), +// #[cfg(feature = "compact_filters")] +// AnyBlockchain::CompactFilters(inner) => inner.$name( $($args, )* ), +// #[cfg(feature = "rpc")] +// AnyBlockchain::Rpc(inner) => inner.$name( $($args, )* ), +// } +// } +// } /// Type that can contain any of the [`Blockchain`] types defined by the library /// @@ -127,41 +83,60 @@ pub enum AnyBlockchain { Rpc(rpc::RpcBlockchain), } -#[maybe_async] -impl Blockchain for AnyBlockchain { - fn get_capabilities(&self) -> HashSet { - 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 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)) - } -} +// TODO remove? +// impl Blockchain for AnyBlockchain { +// fn get_capabilities(&self) -> HashSet { +// maybe_await!(impl_inner_method!(self, get_capabilities)) +// } + +// 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)) +// } +// } + +// impl GetHeight for AnyBlockchain { +// fn get_height(&self) -> Result { +// maybe_await!(impl_inner_method!(self, get_height)) +// } +// } + +// impl GetTx for AnyBlockchain { +// fn get_tx(&self, txid: &Txid) -> Result, Error> { +// maybe_await!(impl_inner_method!(self, get_tx, txid)) +// } +// } + +// 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 +// )) +// } +// } impl_from!(electrum::ElectrumBlockchain, AnyBlockchain, Electrum, #[cfg(feature = "electrum")]); impl_from!(esplora::EsploraBlockchain, AnyBlockchain, Esplora, #[cfg(feature = "esplora")]); @@ -222,30 +197,30 @@ pub enum AnyBlockchainConfig { Rpc(rpc::RpcConfig), } -impl ConfigurableBlockchain for AnyBlockchain { - type Config = AnyBlockchainConfig; +// impl ConfigurableBlockchain for AnyBlockchain { +// type Config = AnyBlockchainConfig; - fn from_config(config: &Self::Config) -> Result { - Ok(match config { - #[cfg(feature = "electrum")] - AnyBlockchainConfig::Electrum(inner) => { - AnyBlockchain::Electrum(electrum::ElectrumBlockchain::from_config(inner)?) - } - #[cfg(feature = "esplora")] - AnyBlockchainConfig::Esplora(inner) => { - AnyBlockchain::Esplora(esplora::EsploraBlockchain::from_config(inner)?) - } - #[cfg(feature = "compact_filters")] - AnyBlockchainConfig::CompactFilters(inner) => AnyBlockchain::CompactFilters( - compact_filters::CompactFiltersBlockchain::from_config(inner)?, - ), - #[cfg(feature = "rpc")] - AnyBlockchainConfig::Rpc(inner) => { - AnyBlockchain::Rpc(rpc::RpcBlockchain::from_config(inner)?) - } - }) - } -} +// fn from_config(config: &Self::Config) -> Result { +// Ok(match config { +// #[cfg(feature = "electrum")] +// AnyBlockchainConfig::Electrum(inner) => { +// AnyBlockchain::Electrum(electrum::ElectrumBlockchain::from_config(inner)?) +// } +// #[cfg(feature = "esplora")] +// AnyBlockchainConfig::Esplora(inner) => { +// AnyBlockchain::Esplora(esplora::EsploraBlockchain::from_config(inner)?) +// } +// #[cfg(feature = "compact_filters")] +// AnyBlockchainConfig::CompactFilters(inner) => AnyBlockchain::CompactFilters( +// compact_filters::CompactFiltersBlockchain::from_config(inner)?, +// ), +// #[cfg(feature = "rpc")] +// AnyBlockchainConfig::Rpc(inner) => { +// AnyBlockchain::Rpc(rpc::RpcBlockchain::from_config(inner)?) +// } +// }) +// } +// } impl_from!(electrum::ElectrumBlockchainConfig, AnyBlockchainConfig, Electrum, #[cfg(feature = "electrum")]); impl_from!(esplora::EsploraBlockchainConfig, AnyBlockchainConfig, Esplora, #[cfg(feature = "esplora")]); diff --git a/src/blockchain/compact_filters/mod.rs b/src/blockchain/compact_filters/mod.rs index 56e9efc3a..e7e235b5b 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 1ab0db1c3..0b8691bc1 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/mod.rs b/src/blockchain/esplora/mod.rs index 83c5c3ddd..606c39411 100644 --- a/src/blockchain/esplora/mod.rs +++ b/src/blockchain/esplora/mod.rs @@ -28,8 +28,8 @@ use crate::error::Error; use crate::FeeRate; #[cfg(feature = "reqwest")] -mod reqwest; - +// TODO: implement the new async traits with this one +// mod reqwest; #[cfg(feature = "reqwest")] pub use self::reqwest::*; @@ -118,8 +118,6 @@ pub struct EsploraBlockchainConfig { /// Number of parallel requests sent to the esplora service (default: 4) #[serde(skip_serializing_if = "Option::is_none")] pub concurrency: Option, - /// Stop searching addresses for transactions after finding an unused gap of this length. - pub stop_gap: usize, /// Socket timeout. #[serde(skip_serializing_if = "Option::is_none")] pub timeout: Option, @@ -127,12 +125,11 @@ pub struct EsploraBlockchainConfig { impl EsploraBlockchainConfig { /// create a config with default values given the base url and stop gap - pub fn new(base_url: String, stop_gap: usize) -> Self { + pub fn new(base_url: String) -> Self { Self { base_url, proxy: None, timeout: None, - stop_gap, concurrency: None, } } diff --git a/src/blockchain/esplora/reqwest.rs b/src/blockchain/esplora/reqwest.rs index 494c6d307..111c82eb6 100644 --- a/src/blockchain/esplora/reqwest.rs +++ b/src/blockchain/esplora/reqwest.rs @@ -79,7 +79,6 @@ impl EsploraBlockchain { } } -#[maybe_async] impl Blockchain for EsploraBlockchain { fn get_capabilities(&self) -> HashSet { vec![ @@ -91,10 +90,33 @@ 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) + } +} + +impl GetHeight for EsploraBlockchain { + fn get_height(&self) -> Result { + Ok(await_or_block!(self.url_client._get_height())?) + } +} + +impl GetTx for EsploraBlockchain { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + Ok(await_or_block!(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)?; @@ -180,23 +202,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 856f6958e..38a494bfe 100644 --- a/src/blockchain/esplora/ureq.rs +++ b/src/blockchain/esplora/ureq.rs @@ -24,7 +24,7 @@ use ureq::{Agent, Proxy, Response}; use bitcoin::consensus::{deserialize, serialize}; use bitcoin::hashes::hex::{FromHex, ToHex}; use bitcoin::hashes::{sha256, Hash}; -use bitcoin::{BlockHeader, Script, Transaction, Txid}; +use bitcoin::{BlockHeader, Script, Transaction, TxOut, Txid}; use super::api::Tx; use crate::blockchain::esplora::EsploraError; @@ -46,20 +46,18 @@ struct UrlClient { #[derive(Debug)] pub struct EsploraBlockchain { url_client: UrlClient, - stop_gap: usize, concurrency: u8, } impl EsploraBlockchain { /// Create a new instance of the client from a base URL and the `stop_gap`. - pub fn new(base_url: &str, stop_gap: usize) -> Self { + pub fn new(base_url: &str) -> Self { EsploraBlockchain { url_client: UrlClient { url: base_url.to_string(), agent: Agent::new(), }, concurrency: super::DEFAULT_CONCURRENT_REQUESTS, - stop_gap, } } @@ -74,6 +72,72 @@ impl EsploraBlockchain { self.concurrency = concurrency; self } + + /// TODO + pub fn fetch_related_transactions( + &self, + mut scripts: impl Iterator, + stop_gap: usize, + ) -> Result<(Vec<(Vec>, Transaction)>, u32), Error> { + let mut empty_scripts = 0; + let mut last_active_index = 0; + let mut index = 0; + let mut transactions = vec![]; + loop { + let handles = (0..self.concurrency) + .filter_map(|_| { + let script = scripts.next()?; + let client = self.url_client.clone(); + Some(std::thread::spawn(move || { + let mut related_txs: Vec = client._scripthash_txs(&script, None)?; + + let n_confirmed = + related_txs.iter().filter(|tx| tx.status.confirmed).count(); + // esplora pages on 25 confirmed transactions. If there's 25 or more we + // keep requesting to see if there's more. + if n_confirmed >= 25 { + loop { + let new_related_txs: Vec = client._scripthash_txs( + &script, + Some(related_txs.last().unwrap().txid), + )?; + let n = new_related_txs.len(); + related_txs.extend(new_related_txs); + // we've reached the end + if n < 25 { + break; + } + } + } + Result::<_, Error>::Ok(related_txs) + })) + }) + .collect::>(); + + let n_handles = handles.len(); + + for handle in handles { + let txs = handle.join().unwrap()?; // TODO: don't unwrap return error + if txs.is_empty() { + empty_scripts += 1; + } else { + empty_scripts = 0; + last_active_index = index + 1; + } + index += 1; + + for tx in txs { + transactions.push((tx.previous_outputs(), tx.to_tx())) + } + } + + if n_handles == 0 || empty_scripts >= stop_gap { + break; + } + } + + Ok((transactions, last_active_index)) + } } impl Blockchain for EsploraBlockchain { @@ -87,13 +151,37 @@ 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)?; + let mut request = script_sync::start(database, 20)?; let mut tx_index: HashMap = HashMap::new(); let batch_update = loop { request = match request { @@ -179,24 +267,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 { @@ -350,7 +420,7 @@ impl ConfigurableBlockchain for EsploraBlockchain { .proxy(Proxy::new(proxy).map_err(|e| Error::Esplora(Box::new(e.into())))?); } - let mut blockchain = EsploraBlockchain::new(config.base_url.as_str(), config.stop_gap) + let mut blockchain = EsploraBlockchain::new(config.base_url.as_str()) .with_agent(agent_builder.build()); if let Some(concurrency) = config.concurrency { @@ -369,3 +439,13 @@ impl From for EsploraError { } } } + +// mod test { +// use super::*; + +// #[test] +// fn setup_session_is_send_and_static() { +// fn assert_send_static() {} +// assert_send_static::() +// } +// } diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index bbf0303df..d097f3670 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -17,9 +17,7 @@ //! [`Blockchain`] that can be implemented to build customized backends. use std::collections::HashSet; -use std::ops::Deref; use std::sync::mpsc::{channel, Receiver, Sender}; -use std::sync::Arc; use bitcoin::{Transaction, Txid}; @@ -85,29 +83,47 @@ 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. +pub trait GetHeight { + /// Return the current height + fn get_height(&self) -> Result; +} + +/// 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. +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 +140,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)) + 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 +161,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 +188,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 +203,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`] @@ -217,39 +223,47 @@ impl Progress for LogProgress { } } -#[maybe_async] -impl Blockchain for Arc { - fn get_capabilities(&self) -> HashSet { - 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)) - } -} +// TODO: Do we need this? +// impl Blockchain for Arc { +// fn get_capabilities(&self) -> HashSet { +// maybe_await!(self.deref().get_capabilities()) +// } + +// fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { +// maybe_await!(self.deref().broadcast(tx)) +// } + +// fn estimate_fee(&self, target: usize) -> Result { +// maybe_await!(self.deref().estimate_fee(target)) +// } +// } + +// impl GetTx for Arc { +// fn get_tx(&self, txid: &Txid) -> Result, Error> { +// maybe_await!(self.deref().get_tx(txid)) +// } +// } + +// impl GetHeight for Arc { +// fn get_height(&self) -> Result { +// maybe_await!(self.deref().get_height()) +// } +// } + +// 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 1b56cbaad..e4a798460 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 8b626e4b7..c4c74dc70 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 afde6fee1..0d5b7ef3d 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 9b5d025e9..fb4e7b04d 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 0423dc4f6..5ad5b40da 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, @@ -233,8 +233,8 @@ extern crate bip39; #[cfg(any(target_arch = "wasm32", feature = "async-interface"))] #[macro_use] extern crate async_trait; -#[macro_use] -extern crate bdk_macros; +// #[macro_use] +// extern crate bdk_macros; #[cfg(feature = "compact_filters")] extern crate lazy_static; @@ -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 30d926cee..5ba6f2669 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,11 +1119,32 @@ 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); } + + #[test] + #[cfg(feature = "use-esplora-ureq")] + fn test_new_sync() { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + let _ = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); + + let script_iter = wallet.script_iter(KeychainKind::External); + + let (txs, keychain_index) = blockchain.fetch_related_transactions(script_iter, 10).unwrap(); + + dbg!(&txs); + assert_eq!(keychain_index,1); + assert_eq!(txs.len(), 1); + + wallet.apply_full_sync(KeychainKind::External, txs, keychain_index).unwrap(); + + assert_eq!(wallet.get_balance().unwrap(), 50_000); + } } }; diff --git a/src/wallet/address_validator.rs b/src/wallet/address_validator.rs index ba6753373..a0e418abe 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 85898345f..f531989e0 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 93a0d01e2..3f4c17691 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -16,10 +16,10 @@ use std::cell::RefCell; use std::collections::HashMap; use std::collections::{BTreeMap, HashSet}; -use std::fmt; use std::ops::{Deref, DerefMut}; use std::str::FromStr; use std::sync::Arc; +use std::fmt; use bitcoin::secp256k1::Secp256k1; @@ -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: +/// +/// 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. /// -/// 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. +/// [`Database`]: crate::database::Database +/// [`Signer`]: crate::signer::Signer #[derive(Debug)] -pub struct Wallet { +pub struct Wallet { descriptor: ExtendedDescriptor, change_descriptor: Option, @@ -95,18 +96,80 @@ pub struct Wallet { network: Network, - current_height: Option, - - client: B, database: RefCell, secp: SecpCtx, } -impl Wallet<(), D> +/// 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)] +pub enum AddressIndex { + /// Return a new address after incrementing the current descriptor index. + New, + /// Return the address for the current descriptor index if it has not been used in a received + /// transaction. Otherwise return a new address as with [`AddressIndex::New`]. + /// + /// Use with caution, if the wallet has not yet detected an address has been used it could + /// return an already used address. This function is primarily meant for situations where the + /// caller is untrusted; for example when deriving donation addresses on-demand for a public + /// web page. + LastUnused, + /// Return the address for a specific descriptor index. Does not change the current descriptor + /// index used by `AddressIndex::New` and `AddressIndex::LastUsed`. + /// + /// Use with caution, if an index is given that is less than the current descriptor index + /// then the returned address may have already been used. + Peek(u32), + /// Return the address for a specific descriptor index and reset the current descriptor index + /// used by `AddressIndex::New` and `AddressIndex::LastUsed` to this value. + /// + /// Use with caution, if an index is given that is less than the current descriptor index + /// then the returned address and subsequent addresses returned by calls to `AddressIndex::New` + /// and `AddressIndex::LastUsed` may have already been used. Also if the index is reset to a + /// value earlier than the [`crate::blockchain::Blockchain`] stop_gap (default is 20) then a + /// larger stop_gap should be used to monitor for all possibly used addresses. + Reset(u32), +} + +/// A derived address and the index it was found at +/// For convenience this automatically derefs to `Address` +#[derive(Debug, PartialEq)] +pub struct AddressInfo { + /// Child index of this address + pub index: u32, + /// Address + pub address: Address, +} + +impl Deref for AddressInfo { + type Target = Address; + + fn deref(&self) -> &Self::Target { + &self.address + } +} + +impl fmt::Display for AddressInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.address) + } +} + +#[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, @@ -114,21 +177,17 @@ where network: Network, database: D, ) -> Result { - Self::_new(descriptor, change_descriptor, network, database, (), None) + Self::new(descriptor, change_descriptor, network, database) } -} -impl Wallet -where - D: BatchDatabase, -{ - fn _new( + /// 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, - client: B, - current_height: Option, ) -> Result { let secp = Secp256k1::new(); @@ -164,8 +223,6 @@ where change_signers, address_validators: Vec::new(), network, - current_height, - client, database: RefCell::new(database), secp, }) @@ -175,69 +232,8 @@ where 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)] -pub enum AddressIndex { - /// Return a new address after incrementing the current descriptor index. - New, - /// Return the address for the current descriptor index if it has not been used in a received - /// transaction. Otherwise return a new address as with [`AddressIndex::New`]. - /// - /// Use with caution, if the wallet has not yet detected an address has been used it could - /// return an already used address. This function is primarily meant for situations where the - /// caller is untrusted; for example when deriving donation addresses on-demand for a public - /// web page. - LastUnused, - /// Return the address for a specific descriptor index. Does not change the current descriptor - /// index used by `AddressIndex::New` and `AddressIndex::LastUsed`. - /// - /// Use with caution, if an index is given that is less than the current descriptor index - /// then the returned address may have already been used. - Peek(u32), - /// Return the address for a specific descriptor index and reset the current descriptor index - /// used by `AddressIndex::New` and `AddressIndex::LastUsed` to this value. - /// - /// Use with caution, if an index is given that is less than the current descriptor index - /// then the returned address and subsequent addresses returned by calls to `AddressIndex::New` - /// and `AddressIndex::LastUsed` may have already been used. Also if the index is reset to a - /// value earlier than the [`crate::blockchain::Blockchain`] stop_gap (default is 20) then a - /// larger stop_gap should be used to monitor for all possibly used addresses. - Reset(u32), -} - -/// A derived address and the index it was found at -/// For convenience this automatically derefs to `Address` -#[derive(Debug, PartialEq)] -pub struct AddressInfo { - /// Child index of this address - pub index: u32, - /// Address - pub address: Address, -} - -impl Deref for AddressInfo { - type Target = Address; - - fn deref(&self) -> &Self::Target { - &self.address - } -} - -impl fmt::Display for AddressInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.address) - } -} -// offline actions, always available -impl Wallet -where - D: BatchDatabase, -{ - // Return a newly derived address for the specified `keychain`. + // Return a newly derived address using the external descriptor 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,32 @@ 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,))?; + blockchain.wallet_setup(self.database.borrow_mut().deref_mut(), progress)? } else { - maybe_await!(self - .client - .sync(self.database.borrow_mut().deref_mut(), progress_update,))?; + 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: blockchain.get_height()?, timestamp: time::get_timestamp(), }, }; @@ -1576,17 +1547,186 @@ where Ok(()) } - /// Return a reference to the internal blockchain client - pub fn client(&self) -> &B { - &self.client + /// TOOD + pub fn script_iter(&self, keychain: KeychainKind) -> impl Iterator { + let descriptor = self.get_descriptor_for_keychain(keychain).clone(); + + let end = if descriptor.is_deriveable() { + u32::MAX + } else { + 1 + }; + + let secp = self.secp.clone(); + (0..end).map(move |i| descriptor.as_derived(i, &secp).script_pubkey()) } - /// Broadcast a transaction to the network - #[maybe_async] - pub fn broadcast(&self, tx: &Transaction) -> Result { - maybe_await!(self.client.broadcast(tx))?; + /// TODO + pub fn apply_full_sync( + &self, + keychain: KeychainKind, + transactions: Vec<(Vec>, Transaction)>, + last_active_index: u32, + ) -> Result<(), Error> { + self.ensure_addresses_cached(last_active_index)?; + let db = &mut *self.database.borrow_mut(); + let tx_details = transactions + .into_iter() + .map(|(vout, tx)| { + { + let txid = tx.txid(); + debug!("applying tx {} in full_sync", txid); + let mut sent: u64 = 0; + let mut received: u64 = 0; + let mut inputs_sum: u64 = 0; + let mut outputs_sum: u64 = 0; + + for (txout, (_input_index, input)) in + vout.into_iter().zip(tx.input.iter().enumerate()) + { + let txout = match txout { + Some(txout) => txout, + None => { + // skip coinbase inputs + debug_assert!( + input.previous_output.is_null(), + "prevout should only be missing for coinbase" + ); + continue; + } + }; + // Verify this input if requested via feature flag + // #[cfg(feature = "verify")] + // { + // use crate::wallet::verify::VerifyError; + // let serialized_tx = bitcoin::consensus::serialize(&tx); + // bitcoinconsensus::verify( + // txout.script_pubkey.to_bytes().as_ref(), + // txout.value, + // &serialized_tx, + // _input_index, + // ) + // .map_err(VerifyError::from)?; + // } + inputs_sum += txout.value; + if db.is_mine(&txout.script_pubkey)? { + sent += txout.value; + } + } + + for out in &tx.output { + outputs_sum += out.value; + if db.is_mine(&out.script_pubkey)? { + received += out.value; + } + } + // we need to saturating sub since we want coinbase txs to map to 0 fee and + // this subtraction will be negative for coinbase txs. + let fee = inputs_sum.saturating_sub(outputs_sum); + Result::<_, Error>::Ok(TransactionDetails { + txid, + transaction: Some(tx), + received, + sent, + // we're going to fill this in later + confirmation_time: None, + fee: Some(fee), + }) + } + }) + .collect::, _>>()?; + let existing_txs = db.iter_txs(true)?; + let existing_txids: HashSet = existing_txs.iter().map(|tx| tx.txid).collect(); + let finished_txs = Self::make_txs_consistent(&tx_details); + let observed_txids: HashSet = finished_txs.iter().map(|tx| tx.txid).collect(); + let txids_to_delete = existing_txids.difference(&observed_txids); + + let mut batch = db.begin_batch(); + + // Delete old txs that no longer exist + for txid in txids_to_delete { + if let Some(raw_tx) = db.get_raw_tx(txid)? { + for i in 0..raw_tx.output.len() { + // Also delete any utxos from the txs that no longer exist. + let _ = batch.del_utxo(&OutPoint { + txid: *txid, + vout: i as u32, + })?; + } + } else { + // TODO: change to some kind of DB error + unreachable!("we should always have the raw tx"); + } + batch.del_tx(txid, true)?; + } - Ok(tx.txid()) + // Set every tx we observed + for finished_tx in &finished_txs { + let tx = finished_tx + .transaction + .as_ref() + .expect("transaction will always be present here"); + for (i, output) in tx.output.iter().enumerate() { + if let Some((keychain, _)) = + db.get_path_from_script_pubkey(&output.script_pubkey)? + { + // add utxos we own from the new transactions we've seen. + batch.set_utxo(&LocalUtxo { + outpoint: OutPoint { + txid: finished_tx.txid, + vout: i as u32, + }, + txout: output.clone(), + keychain, + })?; + } + } + batch.set_tx(finished_tx)?; + } + + // we don't do this in the loop above since we may want to delete some of the utxos we + // just added in case there are new tranasactions that spend form each other. + for finished_tx in &finished_txs { + let tx = finished_tx + .transaction + .as_ref() + .expect("transaction will always be present here"); + for input in &tx.input { + // Delete any spent utxos + batch.del_utxo(&input.previous_output)?; + } + } + + batch.set_last_index(keychain, last_active_index as u32)?; + + db.commit_batch(batch)?; + + Ok(()) + } + + /// Remove conflicting transactions -- tie breaking them by fee. + fn make_txs_consistent(txs: &[TransactionDetails]) -> Vec<&TransactionDetails> { + let mut utxo_index: HashMap = HashMap::default(); + for tx in txs { + for input in &tx.transaction.as_ref().unwrap().input { + utxo_index + .entry(input.previous_output) + .and_modify(|existing| match (tx.fee, existing.fee) { + (Some(fee), Some(existing_fee)) if fee > existing_fee => *existing = tx, + (Some(_), None) => *existing = tx, + _ => { /* leave it the same */ } + }) + .or_insert(tx); + } + } + + utxo_index + .into_iter() + .map(|(_, tx)| (tx.txid, tx)) + .collect::>() + .into_iter() + .map(|(_, tx)| tx) + .collect() } } @@ -1594,12 +1734,12 @@ where 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 +1789,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 +1823,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 +1851,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 +3949,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 +3966,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 +3995,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 +4028,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 +4050,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 +4087,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 +4160,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 +4180,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 88b98461c..15cec186e 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 66b037854..e03a93565 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 9b5633398..0f12e0560 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]