From 326bfe82a89eaa79acf90c34442234c7e53e1caf Mon Sep 17 00:00:00 2001 From: LLFourn Date: Wed, 26 Jan 2022 15:17:48 +1100 Subject: [PATCH 1/9] Remove Blockchain from wallet Although somewhat convenient to have, coupling the Wallet with the blockchain trait causes development friction and complexity. What if sometimes the wallet is "offline" (no access to the blockchain) but sometimes its online? The only thing the Wallet needs the blockchain for is to sync. But not all applications will even use the sync method and the sync method doesn't require the full blockchain functionality. So we instead pass the blockchain in when we want to sync. - To further reduce the coupling with blockchain I removed the get_height call from `new` and just use the height of the last sync in the database. - I split up the blockchain trait a bit into subtraits. --- CHANGELOG.md | 4 + README.md | 4 +- examples/address_validator.rs | 3 +- examples/compact_filters_balance.rs | 5 +- examples/compiler.rs | 2 +- src/blockchain/any.rs | 98 ++++------- src/blockchain/compact_filters/mod.rs | 50 +++--- src/blockchain/electrum.rs | 52 +++--- src/blockchain/esplora/reqwest.rs | 42 +++-- src/blockchain/esplora/ureq.rs | 42 ++--- src/blockchain/mod.rs | 96 ++++++----- src/blockchain/rpc.rs | 60 ++++--- src/database/any.rs | 6 +- src/database/memory.rs | 2 +- src/descriptor/template.rs | 18 +- src/lib.rs | 12 +- src/testutils/blockchain_tests.rs | 206 +++++++++++----------- src/wallet/address_validator.rs | 2 +- src/wallet/export.rs | 19 +-- src/wallet/mod.rs | 237 ++++++++++---------------- src/wallet/signer.rs | 2 +- src/wallet/tx_builder.rs | 16 +- 22 files changed, 470 insertions(+), 508 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17bf1624..73a1bfe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed default verification from `wallet::sync`. sync-time verification is added in `script_sync` and is activated by `verify` feature flag. - `verify` flag removed from `TransactionDetails`. +- 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 argument. ## [v0.16.1] - [v0.16.0] diff --git a/README.md b/README.md index d9491400..af449ac0 100644 --- a/README.md +++ b/README.md @@ -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, @@ -135,7 +135,7 @@ use bdk::{Wallet, SignOptions, database::MemoryDatabase}; use bitcoin::consensus::deserialize; fn main() -> Result<(), bdk::Error> { - let wallet = Wallet::new_offline( + let wallet = Wallet::new( "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), bitcoin::Network::Testnet, diff --git a/examples/address_validator.rs b/examples/address_validator.rs index 560a6df5..85a23560 100644 --- a/examples/address_validator.rs +++ b/examples/address_validator.rs @@ -48,8 +48,7 @@ impl AddressValidator for DummyValidator { fn main() -> Result<(), bdk::Error> { let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))"; - let mut wallet = - Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?; + let mut wallet = Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new())?; wallet.add_address_validator(Arc::new(DummyValidator)); diff --git a/examples/compact_filters_balance.rs b/examples/compact_filters_balance.rs index 78b69d6c..2154de4f 100644 --- a/examples/compact_filters_balance.rs +++ b/examples/compact_filters_balance.rs @@ -35,9 +35,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, noop_progress(), None).unwrap(); info!("balance: {}", wallet.get_balance()?); Ok(()) } diff --git a/examples/compiler.rs b/examples/compiler.rs index 0af034cb..c3a3cb4f 100644 --- a/examples/compiler.rs +++ b/examples/compiler.rs @@ -89,7 +89,7 @@ fn main() -> Result<(), Box> { .transpose() .unwrap() .unwrap_or(Network::Testnet); - let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?; + let wallet = Wallet::new(&format!("{}", descriptor), None, network, database)?; info!("... First address: {}", wallet.get_address(New)?); diff --git a/src/blockchain/any.rs b/src/blockchain/any.rs index 8470959d..4ff9459f 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 //! blockchain type supported using a single line of code: //! //! ```no_run //! # use bitcoin::Network; //! # use bdk::blockchain::*; -//! # use bdk::database::MemoryDatabase; -//! # use bdk::Wallet; //! # #[cfg(all(feature = "esplora", feature = "ureq"))] //! # { //! let config = serde_json::from_str("...")?; //! let blockchain = AnyBlockchain::from_config(&config)?; -//! let wallet = Wallet::new( -//! "...", -//! None, -//! Network::Testnet, -//! MemoryDatabase::default(), -//! blockchain, -//! )?; +//! let height = blockchain.get_height(); //! # } //! # Ok::<(), bdk::Error>(()) //! ``` @@ -133,21 +89,6 @@ impl Blockchain for AnyBlockchain { maybe_await!(impl_inner_method!(self, get_capabilities)) } - fn setup( - &self, - database: &mut D, - progress_update: P, - ) -> Result<(), Error> { - maybe_await!(impl_inner_method!(self, setup, database, progress_update)) - } - fn sync( - &self, - database: &mut D, - progress_update: P, - ) -> Result<(), Error> { - maybe_await!(impl_inner_method!(self, sync, database, progress_update)) - } - fn get_tx(&self, txid: &Txid) -> Result, Error> { maybe_await!(impl_inner_method!(self, get_tx, txid)) } @@ -155,11 +96,44 @@ impl Blockchain for AnyBlockchain { maybe_await!(impl_inner_method!(self, broadcast, tx)) } + fn estimate_fee(&self, target: usize) -> Result { + maybe_await!(impl_inner_method!(self, estimate_fee, target)) + } +} + +#[maybe_async] +impl GetHeight for AnyBlockchain { fn get_height(&self) -> Result { maybe_await!(impl_inner_method!(self, get_height)) } - fn estimate_fee(&self, target: usize) -> Result { - maybe_await!(impl_inner_method!(self, estimate_fee, target)) +} + +#[maybe_async] +impl WalletSync for AnyBlockchain { + fn wallet_sync( + &self, + database: &mut D, + progress_update: P, + ) -> Result<(), Error> { + maybe_await!(impl_inner_method!( + self, + wallet_sync, + database, + progress_update + )) + } + + fn wallet_setup( + &self, + database: &mut D, + progress_update: P, + ) -> Result<(), Error> { + maybe_await!(impl_inner_method!( + self, + wallet_setup, + database, + progress_update + )) } } diff --git a/src/blockchain/compact_filters/mod.rs b/src/blockchain/compact_filters/mod.rs index 56e9efc3..1fd99fc3 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 super::{Blockchain, Capability, ConfigurableBlockchain, GetHeight, Progress, WalletSync}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::error::Error; use crate::types::{KeychainKind, LocalUtxo, TransactionDetails}; @@ -225,8 +225,33 @@ impl Blockchain for CompactFiltersBlockchain { vec![Capability::FullHistory].into_iter().collect() } + 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 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 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, @@ -430,27 +455,6 @@ impl Blockchain for CompactFiltersBlockchain { Ok(()) } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(self.peers[0] - .get_mempool() - .get_tx(&Inventory::Transaction(*txid))) - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - self.peers[0].broadcast_tx(tx.clone())?; - - Ok(()) - } - - fn get_height(&self) -> Result { - Ok(self.headers.get_height()? as u32) - } - - fn estimate_fee(&self, _target: usize) -> Result { - // TODO - Ok(FeeRate::default()) - } } /// Data to connect to a Bitcoin P2P peer diff --git a/src/blockchain/electrum.rs b/src/blockchain/electrum.rs index 1ab0db1c..40efdd55 100644 --- a/src/blockchain/electrum.rs +++ b/src/blockchain/electrum.rs @@ -68,7 +68,34 @@ impl Blockchain for ElectrumBlockchain { .collect() } - fn setup( + 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 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 WalletSync for ElectrumBlockchain { + fn wallet_setup( &self, database: &mut D, _progress_update: P, @@ -207,29 +234,6 @@ impl Blockchain for ElectrumBlockchain { database.commit_batch(batch_update)?; Ok(()) } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(self.client.transaction_get(txid).map(Option::Some)?) - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - Ok(self.client.transaction_broadcast(tx).map(|_| ())?) - } - - fn get_height(&self) -> Result { - // TODO: unsubscribe when added to the client, or is there a better call to use here? - - Ok(self - .client - .block_headers_subscribe() - .map(|data| data.height as u32)?) - } - - fn estimate_fee(&self, target: usize) -> Result { - Ok(FeeRate::from_btc_per_kvb( - self.client.estimate_fee(target)? as f32 - )) - } } struct TxCache<'a, 'b, D> { diff --git a/src/blockchain/esplora/reqwest.rs b/src/blockchain/esplora/reqwest.rs index 494c6d30..d64bf563 100644 --- a/src/blockchain/esplora/reqwest.rs +++ b/src/blockchain/esplora/reqwest.rs @@ -91,7 +91,30 @@ impl Blockchain for EsploraBlockchain { .collect() } - fn setup( + 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 estimate_fee(&self, target: usize) -> Result { + let estimates = await_or_block!(self.url_client._get_fee_estimates())?; + super::into_fee_rate(target, estimates) + } +} + +#[maybe_async] +impl GetHeight for EsploraBlockchain { + fn get_height(&self) -> Result { + Ok(await_or_block!(self.url_client._get_height())?) + } +} + +#[maybe_async] +impl WalletSync for EsploraBlockchain { + fn wallet_setup( &self, database: &mut D, _progress_update: P, @@ -180,23 +203,6 @@ impl Blockchain for EsploraBlockchain { Ok(()) } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(await_or_block!(self.url_client._get_tx(txid))?) - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - Ok(await_or_block!(self.url_client._broadcast(tx))?) - } - - fn get_height(&self) -> Result { - Ok(await_or_block!(self.url_client._get_height())?) - } - - fn estimate_fee(&self, target: usize) -> Result { - let estimates = await_or_block!(self.url_client._get_fee_estimates())?; - super::into_fee_rate(target, estimates) - } } impl UrlClient { diff --git a/src/blockchain/esplora/ureq.rs b/src/blockchain/esplora/ureq.rs index 856f6958..ad0dd773 100644 --- a/src/blockchain/esplora/ureq.rs +++ b/src/blockchain/esplora/ureq.rs @@ -87,7 +87,29 @@ impl Blockchain for EsploraBlockchain { .collect() } - fn setup( + 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 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 WalletSync for EsploraBlockchain { + fn wallet_setup( &self, database: &mut D, _progress_update: P, @@ -179,24 +201,6 @@ impl Blockchain for EsploraBlockchain { Ok(()) } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(self.url_client._get_tx(txid)?) - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - let _txid = self.url_client._broadcast(tx)?; - Ok(()) - } - - fn get_height(&self) -> Result { - Ok(self.url_client._get_height()?) - } - - fn estimate_fee(&self, target: usize) -> Result { - let estimates = self.url_client._get_fee_estimates()?; - super::into_fee_rate(target, estimates) - } } impl UrlClient { diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index bbf0303d..8f3e2523 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -86,28 +86,45 @@ 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 { /// Return the set of [`Capability`] supported by this backend fn get_capabilities(&self) -> HashSet; + /// 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>; + /// Estimate the fee rate required to confirm a transaction in a given `target` of blocks + fn estimate_fee(&self, target: usize) -> Result; +} +/// Trait for getting the current height of the blockchain. +#[maybe_async] +pub trait GetHeight { + /// Return the current height + fn get_height(&self) -> Result; +} + +/// Trait for blockchains that can sync by updating the database directly. +#[maybe_async] +pub trait WalletSync { /// Setup the backend and populate the internal database for the first time /// - /// This method is the equivalent of [`Blockchain::sync`], but it's guaranteed to only be + /// This method is the equivalent of [`Self::wallet_sync`], but it's guaranteed to only be /// called once, at the first [`Wallet::sync`](crate::wallet::Wallet::sync). /// /// The rationale behind the distinction between `sync` and `setup` is that some custom backends /// might need to perform specific actions only the first time they are synced. /// /// For types that do not have that distinction, only this method can be implemented, since - /// [`Blockchain::sync`] defaults to calling this internally if not overridden. - fn setup( + /// [`WalletSync::wallet_sync`] defaults to calling this internally if not overridden. + /// Populate the internal database with transactions and UTXOs + fn wallet_setup( &self, database: &mut D, progress_update: P, ) -> 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 +141,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, ) -> Result<(), Error> { - maybe_await!(self.setup(database, progress_update)) + maybe_await!(self.wallet_setup(database, progress_update)) } - - /// Fetch a transaction from the blockchain given its txid - fn get_tx(&self, txid: &Txid) -> Result, Error>; - /// Broadcast a transaction - fn broadcast(&self, tx: &Transaction) -> Result<(), Error>; - - /// Return the current height - fn get_height(&self) -> Result; - /// Estimate the fee rate required to confirm a transaction in a given `target` of blocks - fn estimate_fee(&self, target: usize) -> Result; } /// Trait for [`Blockchain`] types that can be created given a configuration @@ -155,9 +162,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 { /// Send a new progress update /// /// The `progress` value should be in the range 0.0 - 100.0, and the `message` value is an @@ -223,22 +230,6 @@ impl Blockchain for Arc { maybe_await!(self.deref().get_capabilities()) } - fn setup( - &self, - database: &mut D, - progress_update: P, - ) -> Result<(), Error> { - maybe_await!(self.deref().setup(database, progress_update)) - } - - fn sync( - &self, - database: &mut D, - progress_update: P, - ) -> Result<(), Error> { - maybe_await!(self.deref().sync(database, progress_update)) - } - fn get_tx(&self, txid: &Txid) -> Result, Error> { maybe_await!(self.deref().get_tx(txid)) } @@ -246,10 +237,33 @@ impl Blockchain for Arc { maybe_await!(self.deref().broadcast(tx)) } - fn get_height(&self) -> Result { - maybe_await!(self.deref().get_height()) - } fn estimate_fee(&self, target: usize) -> Result { maybe_await!(self.deref().estimate_fee(target)) } } + +#[maybe_async] +impl GetHeight for Arc { + fn get_height(&self) -> Result { + maybe_await!(self.deref().get_height()) + } +} + +#[maybe_async] +impl WalletSync for Arc { + fn wallet_setup( + &self, + database: &mut D, + progress_update: P, + ) -> Result<(), Error> { + maybe_await!(self.deref().wallet_setup(database, progress_update)) + } + + fn wallet_sync( + &self, + database: &mut D, + progress_update: P, + ) -> Result<(), Error> { + maybe_await!(self.deref().wallet_sync(database, progress_update)) + } +} diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs index 1b56cbaa..403f0bbd 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -33,7 +33,9 @@ use crate::bitcoin::consensus::deserialize; use crate::bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid}; -use crate::blockchain::{Blockchain, Capability, ConfigurableBlockchain, Progress}; +use crate::blockchain::{ + Blockchain, Capability, ConfigurableBlockchain, GetHeight, Progress, WalletSync, +}; use crate::database::{BatchDatabase, DatabaseUtils}; use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails}; use bitcoincore_rpc::json::{ @@ -139,7 +141,34 @@ impl Blockchain for RpcBlockchain { self.capabilities.clone() } - fn setup( + 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 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 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, @@ -187,10 +216,10 @@ 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, @@ -324,29 +353,6 @@ impl Blockchain for RpcBlockchain { Ok(()) } - - fn get_tx(&self, txid: &Txid) -> Result, Error> { - Ok(Some(self.client.get_raw_transaction(txid, None)?)) - } - - fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { - Ok(self.client.send_raw_transaction(tx).map(|_| ())?) - } - - fn get_height(&self) -> Result { - Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?) - } - - fn estimate_fee(&self, target: usize) -> Result { - let sat_per_kb = self - .client - .estimate_smart_fee(target as u16, None)? - .fee_rate - .ok_or(Error::FeeRateUnavailable)? - .as_sat() as f64; - - Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32)) - } } impl ConfigurableBlockchain for RpcBlockchain { diff --git a/src/database/any.rs b/src/database/any.rs index 8b626e4b..c4c74dc7 100644 --- a/src/database/any.rs +++ b/src/database/any.rs @@ -23,12 +23,12 @@ //! # use bdk::database::{AnyDatabase, MemoryDatabase}; //! # use bdk::{Wallet}; //! let memory = MemoryDatabase::default(); -//! let wallet_memory = Wallet::new_offline("...", None, Network::Testnet, memory)?; +//! let wallet_memory = Wallet::new("...", None, Network::Testnet, memory)?; //! //! # #[cfg(feature = "key-value-db")] //! # { //! let sled = sled::open("my-database")?.open_tree("default_tree")?; -//! let wallet_sled = Wallet::new_offline("...", None, Network::Testnet, sled)?; +//! let wallet_sled = Wallet::new("...", None, Network::Testnet, sled)?; //! # } //! # Ok::<(), bdk::Error>(()) //! ``` @@ -42,7 +42,7 @@ //! # use bdk::{Wallet}; //! let config = serde_json::from_str("...")?; //! let database = AnyDatabase::from_config(&config)?; -//! let wallet = Wallet::new_offline("...", None, Network::Testnet, database)?; +//! let wallet = Wallet::new("...", None, Network::Testnet, database)?; //! # Ok::<(), bdk::Error>(()) //! ``` diff --git a/src/database/memory.rs b/src/database/memory.rs index afde6fee..0d5b7ef3 100644 --- a/src/database/memory.rs +++ b/src/database/memory.rs @@ -554,7 +554,7 @@ macro_rules! doctest_wallet { Some(100), ); - $crate::Wallet::new_offline( + $crate::Wallet::new( &descriptors.0, descriptors.1.as_ref(), Network::Regtest, diff --git a/src/descriptor/template.rs b/src/descriptor/template.rs index 9b5d025e..fb4e7b04 100644 --- a/src/descriptor/template.rs +++ b/src/descriptor/template.rs @@ -79,7 +79,7 @@ impl IntoWalletDescriptor for T { /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// P2Pkh(key), /// None, /// Network::Testnet, @@ -113,7 +113,7 @@ impl> DescriptorTemplate for P2Pkh { /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// P2Wpkh_P2Sh(key), /// None, /// Network::Testnet, @@ -148,7 +148,7 @@ impl> DescriptorTemplate for P2Wpkh_P2Sh { /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// P2Wpkh(key), /// None, /// Network::Testnet, @@ -186,7 +186,7 @@ impl> DescriptorTemplate for P2Wpkh { /// use bdk::template::Bip44; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip44(key.clone(), KeychainKind::External), /// Some(Bip44(key, KeychainKind::Internal)), /// Network::Testnet, @@ -226,7 +226,7 @@ impl> DescriptorTemplate for Bip44 { /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip44Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, @@ -262,7 +262,7 @@ impl> DescriptorTemplate for Bip44Public { /// use bdk::template::Bip49; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip49(key.clone(), KeychainKind::External), /// Some(Bip49(key, KeychainKind::Internal)), /// Network::Testnet, @@ -302,7 +302,7 @@ impl> DescriptorTemplate for Bip49 { /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip49Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, @@ -338,7 +338,7 @@ impl> DescriptorTemplate for Bip49Public { /// use bdk::template::Bip84; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip84(key.clone(), KeychainKind::External), /// Some(Bip84(key, KeychainKind::Internal)), /// Network::Testnet, @@ -378,7 +378,7 @@ impl> DescriptorTemplate for Bip84 { /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; -/// let wallet = Wallet::new_offline( +/// let wallet = Wallet::new( /// Bip84Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, diff --git a/src/lib.rs b/src/lib.rs index 0423dc4f..b998d36a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,15 +60,15 @@ 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, noop_progress(), None)?; 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, @@ -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, noop_progress(), None)?; 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, diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index 02047d67..cf6be5a7 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -361,7 +361,7 @@ 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, noop_progress}; use $crate::database::MemoryDatabase; use $crate::types::KeychainKind; use $crate::{Wallet, FeeRate}; @@ -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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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,16 +620,16 @@ 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, noop_progress(), None).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, noop_progress(), None).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address; @@ -650,16 +651,16 @@ macro_rules! bdk_blockchain_tests { psbt.extract_tx() }; - wallet.broadcast(&tx1).unwrap(); - wallet.broadcast(&tx2).unwrap(); + blockchain.broadcast(&tx1).unwrap(); + blockchain.broadcast(&tx2).unwrap(); - receiver_wallet.sync(noop_progress(), None).unwrap(); + receiver_wallet.sync(&blockchain, noop_progress(), None).unwrap(); assert_eq!(receiver_wallet.get_balance().unwrap(), 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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).unwrap(); total_sent += 5_000 + details.fee.unwrap_or(0); } - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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().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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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, noop_progress(), None).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"); diff --git a/src/wallet/address_validator.rs b/src/wallet/address_validator.rs index ba675337..a0e418ab 100644 --- a/src/wallet/address_validator.rs +++ b/src/wallet/address_validator.rs @@ -55,7 +55,7 @@ //! } //! //! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; -//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?; +//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?; //! wallet.add_address_validator(Arc::new(PrintAddressAndContinue)); //! //! let address = wallet.get_address(New)?; diff --git a/src/wallet/export.rs b/src/wallet/export.rs index 85898345..f531989e 100644 --- a/src/wallet/export.rs +++ b/src/wallet/export.rs @@ -30,7 +30,7 @@ //! }"#; //! //! let import = WalletExport::from_str(import)?; -//! let wallet = Wallet::new_offline( +//! let wallet = Wallet::new( //! &import.descriptor(), //! import.change_descriptor().as_ref(), //! Network::Testnet, @@ -45,7 +45,7 @@ //! # use bdk::database::*; //! # use bdk::wallet::export::*; //! # use bdk::*; -//! let wallet = Wallet::new_offline( +//! let wallet = Wallet::new( //! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)", //! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"), //! Network::Testnet, @@ -111,8 +111,8 @@ impl WalletExport { /// /// If the database is empty or `include_blockheight` is false, the `blockheight` field /// returned will be `0`. - pub fn export_wallet( - wallet: &Wallet, + pub fn export_wallet( + wallet: &Wallet, label: &str, include_blockheight: bool, ) -> Result { @@ -241,7 +241,7 @@ mod test { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; - let wallet = Wallet::new_offline( + let wallet = Wallet::new( descriptor, Some(change_descriptor), Network::Bitcoin, @@ -265,8 +265,7 @@ mod test { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; - let wallet = - Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap(); + let wallet = Wallet::new(descriptor, None, Network::Bitcoin, get_test_db()).unwrap(); WalletExport::export_wallet(&wallet, "Test Label", true).unwrap(); } @@ -279,7 +278,7 @@ mod test { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)"; - let wallet = Wallet::new_offline( + let wallet = Wallet::new( descriptor, Some(change_descriptor), Network::Bitcoin, @@ -302,7 +301,7 @@ mod test { [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\ ))"; - let wallet = Wallet::new_offline( + let wallet = Wallet::new( descriptor, Some(change_descriptor), Network::Testnet, @@ -322,7 +321,7 @@ mod test { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; - let wallet = Wallet::new_offline( + let wallet = Wallet::new( descriptor, Some(change_descriptor), Network::Bitcoin, diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index a24f922d..9effd1c0 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -55,7 +55,7 @@ use signer::{SignOptions, Signer, SignerOrdering, SignersContainer}; use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams}; use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx}; -use crate::blockchain::{Blockchain, Progress}; +use crate::blockchain::{GetHeight, Progress, WalletSync}; use crate::database::memory::MemoryDatabase; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime}; use crate::descriptor::derived::AsDerived; @@ -75,16 +75,17 @@ const CACHE_ADDR_BATCH_SIZE: u32 = 100; /// A Bitcoin wallet /// -/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a -/// [`blockchain`](trait@crate::blockchain::Blockchain) and implements the basic functions that a Bitcoin wallets -/// needs to operate, like [generating addresses](Wallet::get_address), [returning the balance](Wallet::get_balance), -/// [creating transactions](Wallet::build_tx), etc. +/// The `Wallet` struct acts as a way of coherently interfacing with output descriptors and related transactions. +/// Its main components are: /// -/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided -/// implements [`Blockchain`], or "offline" if it is the unit type `()`. Offline wallets only expose -/// methods that don't need any interaction with the blockchain to work. +/// 1. output *descriptors* from which it can derive addresses. +/// 2. A [`Database`] where it tracks transactions and utxos related to the descriptors. +/// 3. [`Signer`]s that can contribute signatures to addresses instantiated from the descriptors. +/// +/// [`Database`]: crate::database::Database +/// [`Signer`]: crate::signer::Signer #[derive(Debug)] -pub struct Wallet { +pub struct Wallet { descriptor: ExtendedDescriptor, change_descriptor: Option, @@ -95,88 +96,11 @@ pub struct Wallet { network: Network, - current_height: Option, - - client: B, database: RefCell, secp: SecpCtx, } -impl Wallet<(), D> -where - D: BatchDatabase, -{ - /// Create a new "offline" wallet - pub fn new_offline( - descriptor: E, - change_descriptor: Option, - network: Network, - database: D, - ) -> Result { - Self::_new(descriptor, change_descriptor, network, database, (), None) - } -} - -impl Wallet -where - D: BatchDatabase, -{ - fn _new( - descriptor: E, - change_descriptor: Option, - network: Network, - mut database: D, - client: B, - current_height: Option, - ) -> Result { - let secp = Secp256k1::new(); - - let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?; - database.check_descriptor_checksum( - KeychainKind::External, - get_checksum(&descriptor.to_string())?.as_bytes(), - )?; - let signers = Arc::new(SignersContainer::from(keymap)); - let (change_descriptor, change_signers) = match change_descriptor { - Some(desc) => { - let (change_descriptor, change_keymap) = - into_wallet_descriptor_checked(desc, &secp, network)?; - database.check_descriptor_checksum( - KeychainKind::Internal, - get_checksum(&change_descriptor.to_string())?.as_bytes(), - )?; - - let change_signers = Arc::new(SignersContainer::from(change_keymap)); - // if !parsed.same_structure(descriptor.as_ref()) { - // return Err(Error::DifferentDescriptorStructure); - // } - - (Some(change_descriptor), change_signers) - } - None => (None, Arc::new(SignersContainer::new())), - }; - - Ok(Wallet { - descriptor, - change_descriptor, - signers, - change_signers, - address_validators: Vec::new(), - network, - current_height, - client, - database: RefCell::new(database), - secp, - }) - } - - /// Get the Bitcoin network the wallet is using. - pub fn network(&self) -> Network { - self.network - } -} - /// The address index selection strategy to use to derived an address from the wallet's external /// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`. #[derive(Debug)] @@ -232,11 +156,63 @@ impl fmt::Display for AddressInfo { } } -// offline actions, always available -impl Wallet +impl Wallet where D: BatchDatabase, { + /// Create a wallet. + /// + /// The only way this can fail is if the descriptors passed in do not match the checksums in `database`. + pub fn new( + descriptor: E, + change_descriptor: Option, + network: Network, + mut database: D, + ) -> Result { + let secp = Secp256k1::new(); + + let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?; + database.check_descriptor_checksum( + KeychainKind::External, + get_checksum(&descriptor.to_string())?.as_bytes(), + )?; + let signers = Arc::new(SignersContainer::from(keymap)); + let (change_descriptor, change_signers) = match change_descriptor { + Some(desc) => { + let (change_descriptor, change_keymap) = + into_wallet_descriptor_checked(desc, &secp, network)?; + database.check_descriptor_checksum( + KeychainKind::Internal, + get_checksum(&change_descriptor.to_string())?.as_bytes(), + )?; + + let change_signers = Arc::new(SignersContainer::from(change_keymap)); + // if !parsed.same_structure(descriptor.as_ref()) { + // return Err(Error::DifferentDescriptorStructure); + // } + + (Some(change_descriptor), change_signers) + } + None => (None, Arc::new(SignersContainer::new())), + }; + + Ok(Wallet { + descriptor, + change_descriptor, + signers, + change_signers, + address_validators: Vec::new(), + network, + database: RefCell::new(database), + secp, + }) + } + + /// Get the Bitcoin network the wallet is using. + pub fn network(&self) -> Network { + self.network + } + // Return a newly derived address using the external descriptor fn get_new_address(&self) -> Result { let incremented_index = self.fetch_and_increment_index(KeychainKind::External)?; @@ -422,7 +398,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(), @@ -762,7 +738,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), @@ -993,7 +969,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` = {:?}", @@ -1453,35 +1433,15 @@ where } } -impl Wallet +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, + blockchain: &B, progress_update: P, max_address_param: Option, ) -> Result<(), Error> { @@ -1527,18 +1487,18 @@ where // TODO: what if i generate an address first and cache some addresses? // TODO: we should sync if generating an address triggers a new batch to be stored if run_setup { - maybe_await!(self - .client - .setup(self.database.borrow_mut().deref_mut(), progress_update,))?; + maybe_await!( + blockchain.wallet_setup(self.database.borrow_mut().deref_mut(), progress_update,) + )?; } else { - maybe_await!(self - .client - .sync(self.database.borrow_mut().deref_mut(), progress_update,))?; + maybe_await!( + blockchain.wallet_sync(self.database.borrow_mut().deref_mut(), progress_update,) + )?; } let sync_time = SyncTime { block_time: BlockTime { - height: maybe_await!(self.client.get_height())?, + height: maybe_await!(blockchain.get_height())?, timestamp: time::get_timestamp(), }, }; @@ -1547,31 +1507,18 @@ where Ok(()) } - - /// Return a reference to the internal blockchain client - pub fn client(&self) -> &B { - &self.client - } - - /// Broadcast a transaction to the network - #[maybe_async] - pub fn broadcast(&self, tx: &Transaction) -> Result { - maybe_await!(self.client.broadcast(tx))?; - - Ok(tx.txid()) - } } /// Return a fake wallet that appears to be funded for testing. pub fn get_funded_wallet( descriptor: &str, ) -> ( - Wallet<(), MemoryDatabase>, + Wallet, (String, Option), bitcoin::Txid, ) { let descriptors = testutils!(@descriptors (descriptor)); - let wallet = Wallet::new_offline( + let wallet = Wallet::new( &descriptors.0, None, Network::Regtest, @@ -1621,7 +1568,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, @@ -1655,7 +1602,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(), @@ -1683,7 +1630,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(), @@ -3781,7 +3728,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!( @@ -3798,7 +3745,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, @@ -3827,7 +3774,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!( @@ -3860,7 +3807,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!( @@ -3882,7 +3829,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 @@ -3919,7 +3866,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 diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs index 88b98461..15cec186 100644 --- a/src/wallet/signer.rs +++ b/src/wallet/signer.rs @@ -72,7 +72,7 @@ //! let custom_signer = CustomSigner::connect(); //! //! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; -//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?; +//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?; //! wallet.add_signer( //! KeychainKind::External, //! SignerOrdering(200), diff --git a/src/wallet/tx_builder.rs b/src/wallet/tx_builder.rs index 66b03785..e03a9356 100644 --- a/src/wallet/tx_builder.rs +++ b/src/wallet/tx_builder.rs @@ -120,8 +120,8 @@ impl TxBuilderContext for BumpFee {} /// [`finish`]: Self::finish /// [`coin_selection`]: Self::coin_selection #[derive(Debug)] -pub struct TxBuilder<'a, B, D, Cs, Ctx> { - pub(crate) wallet: &'a Wallet, +pub struct TxBuilder<'a, D, Cs, Ctx> { + pub(crate) wallet: &'a Wallet, pub(crate) params: TxParams, pub(crate) coin_selection: Cs, pub(crate) phantom: PhantomData, @@ -170,7 +170,7 @@ impl std::default::Default for FeePolicy { } } -impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> { +impl<'a, Cs: Clone, Ctx, D> Clone for TxBuilder<'a, D, Cs, Ctx> { fn clone(&self) -> Self { TxBuilder { wallet: self.wallet, @@ -182,8 +182,8 @@ impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> { } // methods supported by both contexts, for any CoinSelectionAlgorithm -impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> - TxBuilder<'a, B, D, Cs, Ctx> +impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> + TxBuilder<'a, D, Cs, Ctx> { /// Set a custom fee rate pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self { @@ -508,7 +508,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderConte pub fn coin_selection>( self, coin_selection: P, - ) -> TxBuilder<'a, B, D, P, Ctx> { + ) -> TxBuilder<'a, D, P, Ctx> { TxBuilder { wallet: self.wallet, params: self.params, @@ -547,7 +547,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderConte } } -impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm> TxBuilder<'a, B, D, Cs, CreateTx> { +impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> { /// Replace the recipients already added with a new list pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self { self.params.recipients = recipients; @@ -614,7 +614,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm> TxBuilder<'a, B, D, } // methods supported only by bump_fee -impl<'a, B, D: BatchDatabase> TxBuilder<'a, B, D, DefaultCoinSelectionAlgorithm, BumpFee> { +impl<'a, D: BatchDatabase> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> { /// Explicitly tells the wallet that it is allowed to reduce the fee of the output matching this /// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet /// will attempt to find a change output to shrink instead. From 410a51355bb47dbf3401cbe6f19546255c6721e0 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Thu, 27 Jan 2022 16:52:53 +1100 Subject: [PATCH 2/9] Add SyncOptions as the second argument to Wallet::sync The current options are awkward and it would be good if we could introduce more in the future without breaking changes. --- CHANGELOG.md | 13 +++- README.md | 17 +++-- examples/compact_filters_balance.rs | 3 +- src/blockchain/any.rs | 8 +-- src/blockchain/compact_filters/mod.rs | 4 +- src/blockchain/electrum.rs | 4 +- src/blockchain/esplora/reqwest.rs | 4 +- src/blockchain/esplora/ureq.rs | 4 +- src/blockchain/mod.rs | 22 +++--- src/blockchain/rpc.rs | 8 +-- src/lib.rs | 13 ++-- src/testutils/blockchain_tests.rs | 96 +++++++++++++-------------- src/wallet/mod.rs | 38 ++++++----- 13 files changed, 124 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a1bfe0..319fb628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed default verification from `wallet::sync`. sync-time verification is added in `script_sync` and is activated by `verify` feature flag. - `verify` flag removed from `TransactionDetails`. -- Removed Blockchain from Wallet. -- Removed `Wallet::broadcast` (just use blockchain.broadcast) + +### 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 argument. +- 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`. ## [v0.16.1] - [v0.16.0] diff --git a/README.md b/README.md index af449ac0..8f2c7e74 100644 --- a/README.md +++ b/README.md @@ -41,21 +41,21 @@ The `bdk` library aims to be the core building block for Bitcoin wallets of any ```rust,no_run use bdk::Wallet; use bdk::database::MemoryDatabase; -use bdk::blockchain::{noop_progress, ElectrumBlockchain}; +use bdk::blockchain::ElectrumBlockchain; +use bdk::SyncOptions; use bdk::electrum_client::Client; fn main() -> Result<(), bdk::Error> { - let client = Client::new("ssl://electrum.blockstream.info:60002")?; + let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); let wallet = Wallet::new( "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), bitcoin::Network::Testnet, MemoryDatabase::default(), - ElectrumBlockchain::from(client) )?; - wallet.sync(noop_progress(), None)?; + wallet.sync(&blockchain, SyncOptions::default())?; println!("Descriptor balance: {} SAT", wallet.get_balance()?); @@ -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) = { diff --git a/examples/compact_filters_balance.rs b/examples/compact_filters_balance.rs index 2154de4f..ce875b4d 100644 --- a/examples/compact_filters_balance.rs +++ b/examples/compact_filters_balance.rs @@ -10,7 +10,6 @@ // licenses. use bdk::blockchain::compact_filters::*; -use bdk::blockchain::noop_progress; use bdk::database::MemoryDatabase; use bdk::*; use bitcoin::*; @@ -36,7 +35,7 @@ fn main() -> Result<(), CompactFiltersError> { let database = MemoryDatabase::default(); let wallet = Arc::new(Wallet::new(descriptor, None, Network::Testnet, database).unwrap()); - wallet.sync(&blockchain, noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); info!("balance: {}", wallet.get_balance()?); Ok(()) } diff --git a/src/blockchain/any.rs b/src/blockchain/any.rs index 4ff9459f..ef4abffa 100644 --- a/src/blockchain/any.rs +++ b/src/blockchain/any.rs @@ -110,10 +110,10 @@ impl GetHeight for AnyBlockchain { #[maybe_async] impl WalletSync for AnyBlockchain { - fn wallet_sync( + fn wallet_sync( &self, database: &mut D, - progress_update: P, + progress_update: Box, ) -> Result<(), Error> { maybe_await!(impl_inner_method!( self, @@ -123,10 +123,10 @@ impl WalletSync for AnyBlockchain { )) } - fn wallet_setup( + fn wallet_setup( &self, database: &mut D, - progress_update: P, + progress_update: Box, ) -> Result<(), Error> { maybe_await!(impl_inner_method!( self, diff --git a/src/blockchain/compact_filters/mod.rs b/src/blockchain/compact_filters/mod.rs index 1fd99fc3..d0b96421 100644 --- a/src/blockchain/compact_filters/mod.rs +++ b/src/blockchain/compact_filters/mod.rs @@ -251,10 +251,10 @@ impl GetHeight for CompactFiltersBlockchain { impl WalletSync for CompactFiltersBlockchain { #[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop. - fn wallet_setup( + fn wallet_setup( &self, database: &mut D, - progress_update: P, + progress_update: Box, ) -> Result<(), Error> { let first_peer = &self.peers[0]; diff --git a/src/blockchain/electrum.rs b/src/blockchain/electrum.rs index 40efdd55..b10b90d5 100644 --- a/src/blockchain/electrum.rs +++ b/src/blockchain/electrum.rs @@ -95,10 +95,10 @@ impl GetHeight for ElectrumBlockchain { } impl WalletSync for ElectrumBlockchain { - fn wallet_setup( + 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(); diff --git a/src/blockchain/esplora/reqwest.rs b/src/blockchain/esplora/reqwest.rs index d64bf563..0a9ed4dd 100644 --- a/src/blockchain/esplora/reqwest.rs +++ b/src/blockchain/esplora/reqwest.rs @@ -114,10 +114,10 @@ impl GetHeight for EsploraBlockchain { #[maybe_async] impl WalletSync for EsploraBlockchain { - fn wallet_setup( + 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)?; diff --git a/src/blockchain/esplora/ureq.rs b/src/blockchain/esplora/ureq.rs index ad0dd773..3e240f43 100644 --- a/src/blockchain/esplora/ureq.rs +++ b/src/blockchain/esplora/ureq.rs @@ -109,10 +109,10 @@ impl GetHeight for EsploraBlockchain { } impl WalletSync for EsploraBlockchain { - fn wallet_setup( + 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)?; diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 8f3e2523..5962fca2 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -118,10 +118,10 @@ pub trait WalletSync { /// For types that do not have that distinction, only this method can be implemented, since /// [`WalletSync::wallet_sync`] defaults to calling this internally if not overridden. /// Populate the internal database with transactions and UTXOs - fn wallet_setup( + fn wallet_setup( &self, database: &mut D, - progress_update: P, + progress_update: Box, ) -> Result<(), Error>; /// If not overridden, it defaults to calling [`Self::wallet_setup`] internally. @@ -141,10 +141,10 @@ pub trait WalletSync { /// [`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 wallet_sync( + fn wallet_sync( &self, database: &mut D, - progress_update: P, + progress_update: Box, ) -> Result<(), Error> { maybe_await!(self.wallet_setup(database, progress_update)) } @@ -164,7 +164,7 @@ pub type ProgressData = (f32, Option); /// Trait for types that can receive and process progress updates during [`WalletSync::wallet_sync`] and /// [`WalletSync::wallet_setup`] -pub trait Progress: Send + 'static { +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 @@ -189,7 +189,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`] @@ -204,7 +204,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`] @@ -251,18 +251,18 @@ impl GetHeight for Arc { #[maybe_async] impl WalletSync for Arc { - fn wallet_setup( + fn wallet_setup( &self, database: &mut D, - progress_update: P, + progress_update: Box, ) -> Result<(), Error> { maybe_await!(self.deref().wallet_setup(database, progress_update)) } - fn wallet_sync( + fn wallet_sync( &self, database: &mut D, - progress_update: P, + 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 403f0bbd..b96af993 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -168,10 +168,10 @@ impl GetHeight for RpcBlockchain { } impl WalletSync for RpcBlockchain { - fn wallet_setup( + 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))?); @@ -219,10 +219,10 @@ impl WalletSync for RpcBlockchain { self.wallet_sync(database, progress_update) } - fn wallet_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] { diff --git a/src/lib.rs b/src/lib.rs index b998d36a..4b8a2228 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,9 +53,9 @@ ### 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> { @@ -68,7 +68,7 @@ fn main() -> Result<(), bdk::Error> { MemoryDatabase::default(), )?; - wallet.sync(&blockchain, noop_progress(), None)?; + wallet.sync(&blockchain, SyncOptions::default())?; println!("Descriptor balance: {} SAT", wallet.get_balance()?); @@ -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; @@ -126,7 +126,7 @@ fn main() -> Result<(), bdk::Error> { )?; let blockchain = ElectrumBlockchain::from(client); - wallet.sync(&blockchain, noop_progress(), None)?; + wallet.sync(&blockchain, SyncOptions::default())?; let send_to = wallet.get_address(New)?; let (psbt, details) = { @@ -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 cf6be5a7..68858a84 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::{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::*; @@ -392,7 +392,7 @@ macro_rules! bdk_blockchain_tests { // rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool #[cfg(feature = "test-rpc")] - wallet.sync(&blockchain, noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); (wallet, blockchain, descriptors, test_client) } @@ -415,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(&blockchain, 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"); @@ -439,7 +439,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 25) => 50_000 ) }); - wallet.sync(&blockchain, 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"); @@ -449,14 +449,14 @@ macro_rules! bdk_blockchain_tests { fn test_sync_before_and_after_receive() { let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - wallet.sync(&blockchain, 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(&blockchain, 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"); @@ -470,7 +470,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 ) }); - wallet.sync(&blockchain, 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"); @@ -494,7 +494,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 5) => 25_000 ) }); - wallet.sync(&blockchain, 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"); @@ -509,14 +509,14 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(&blockchain, 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(&blockchain, noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); } @@ -528,7 +528,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true ) }); - wallet.sync(&blockchain, 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"); @@ -542,7 +542,7 @@ macro_rules! bdk_blockchain_tests { let new_txid = test_client.bump_fee(&txid); - wallet.sync(&blockchain, 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"); @@ -566,7 +566,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true ) }); - wallet.sync(&blockchain, 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"); @@ -579,7 +579,7 @@ macro_rules! bdk_blockchain_tests { // Invalidate 1 block test_client.invalidate(1); - wallet.sync(&blockchain, noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after invalidate"); @@ -598,7 +598,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(&blockchain, 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(); @@ -609,7 +609,7 @@ macro_rules! bdk_blockchain_tests { let tx = psbt.extract_tx(); println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); blockchain.broadcast(&tx).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).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"); @@ -623,13 +623,13 @@ macro_rules! bdk_blockchain_tests { 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(&blockchain, 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(&blockchain, noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address; @@ -654,7 +654,7 @@ macro_rules! bdk_blockchain_tests { blockchain.broadcast(&tx1).unwrap(); blockchain.broadcast(&tx2).unwrap(); - receiver_wallet.sync(&blockchain, noop_progress(), None).unwrap(); + receiver_wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(receiver_wallet.get_balance().unwrap(), 49_000, "should have received coins once and only once"); } @@ -679,7 +679,7 @@ macro_rules! bdk_blockchain_tests { }); } - wallet.sync(&blockchain, noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 100_000); } @@ -694,7 +694,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(&blockchain, 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::>(); @@ -702,7 +702,7 @@ macro_rules! bdk_blockchain_tests { assert!(details.confirmation_time.is_none()); test_client.generate(1, Some(node_addr)); - wallet.sync(&blockchain, 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(); @@ -718,7 +718,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(&blockchain, 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(); @@ -730,7 +730,7 @@ macro_rules! bdk_blockchain_tests { let sent_tx = psbt.extract_tx(); blockchain.broadcast(&sent_tx).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive"); // empty wallet @@ -739,7 +739,7 @@ macro_rules! bdk_blockchain_tests { #[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti test_client.generate(1, Some(node_addr)); - wallet.sync(&blockchain, 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(); @@ -761,7 +761,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(&blockchain, 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; @@ -773,12 +773,12 @@ macro_rules! bdk_blockchain_tests { assert!(finalized, "Cannot finalize transaction"); blockchain.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); total_sent += 5_000 + details.fee.unwrap_or(0); } - wallet.sync(&blockchain, 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 @@ -788,7 +788,7 @@ macro_rules! bdk_blockchain_tests { #[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti test_client.generate(1, Some(node_addr)); - wallet.sync(&blockchain, noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance empty wallet"); } @@ -802,7 +802,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) }); - wallet.sync(&blockchain, 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(); @@ -811,7 +811,7 @@ macro_rules! bdk_blockchain_tests { let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); blockchain.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).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"); @@ -821,7 +821,7 @@ macro_rules! bdk_blockchain_tests { let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).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"); @@ -837,7 +837,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) }); - wallet.sync(&blockchain, 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(); @@ -846,7 +846,7 @@ macro_rules! bdk_blockchain_tests { let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); blockchain.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).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"); @@ -856,7 +856,7 @@ macro_rules! bdk_blockchain_tests { let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).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"); @@ -872,7 +872,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) }); - wallet.sync(&blockchain, 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(); @@ -881,7 +881,7 @@ macro_rules! bdk_blockchain_tests { let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); blockchain.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).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"); @@ -891,7 +891,7 @@ macro_rules! bdk_blockchain_tests { let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).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"); } @@ -905,7 +905,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) }); - wallet.sync(&blockchain, 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(); @@ -914,7 +914,7 @@ macro_rules! bdk_blockchain_tests { let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); blockchain.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).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"); @@ -926,7 +926,7 @@ macro_rules! bdk_blockchain_tests { let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); assert!(finalized, "Cannot finalize transaction"); blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).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"); @@ -941,7 +941,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(&blockchain, 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(); @@ -956,7 +956,7 @@ macro_rules! bdk_blockchain_tests { assert!(serialized_tx.windows(data.len()).any(|e| e==data), "cannot find op_return data in transaction"); blockchain.broadcast(&tx).unwrap(); test_client.generate(1, Some(node_addr)); - wallet.sync(&blockchain, 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::>(); @@ -969,7 +969,7 @@ macro_rules! bdk_blockchain_tests { let wallet_addr = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address; - wallet.sync(&blockchain, 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)); @@ -982,7 +982,7 @@ macro_rules! bdk_blockchain_tests { } - wallet.sync(&blockchain, noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase"); } @@ -1058,7 +1058,7 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000 ) }); - wallet.sync(&blockchain, 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 @@ -1070,7 +1070,7 @@ macro_rules! bdk_blockchain_tests { assert!(finalized, "wallet cannot finalize transaction"); let tx = psbt.extract_tx(); blockchain.broadcast(&tx).unwrap(); - wallet.sync(&blockchain, noop_progress(), None).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"); diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 9effd1c0..a8d6a62c 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -55,7 +55,7 @@ use signer::{SignOptions, Signer, SignerOrdering, SignersContainer}; use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams}; use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx}; -use crate::blockchain::{GetHeight, Progress, WalletSync}; +use crate::blockchain::{GetHeight, NoopProgress, Progress, WalletSync}; use crate::database::memory::MemoryDatabase; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime}; use crate::descriptor::derived::AsDerived; @@ -156,6 +156,17 @@ impl fmt::Display for AddressInfo { } } +#[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>, + /// The maximum number of addresses sync on. + pub max_addresses: Option, +} + impl Wallet where D: BatchDatabase, @@ -1431,27 +1442,26 @@ where pub fn database(&self) -> impl std::ops::Deref + '_ { self.database.borrow() } -} -impl Wallet -where - D: BatchDatabase, -{ /// Sync the internal database with the blockchain #[maybe_async] - pub fn sync( + pub fn sync( &self, blockchain: &B, - progress_update: P, - max_address_param: Option, + sync_opts: SyncOptions, ) -> Result<(), Error> { debug!("Begin sync..."); let mut run_setup = false; + let SyncOptions { + max_addresses, + progress, + } = sync_opts; + let progress = progress.unwrap_or_else(|| Box::new(NoopProgress)); let max_address = match self.descriptor.is_deriveable() { false => 0, - true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE), + true => max_addresses.unwrap_or(CACHE_ADDR_BATCH_SIZE), }; debug!("max_address {}", max_address); if self @@ -1468,7 +1478,7 @@ where if let Some(change_descriptor) = &self.change_descriptor { let max_address = match change_descriptor.is_deriveable() { false => 0, - true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE), + true => max_addresses.unwrap_or(CACHE_ADDR_BATCH_SIZE), }; if self @@ -1488,12 +1498,10 @@ where // TODO: we should sync if generating an address triggers a new batch to be stored if run_setup { maybe_await!( - blockchain.wallet_setup(self.database.borrow_mut().deref_mut(), progress_update,) + blockchain.wallet_setup(self.database.borrow_mut().deref_mut(), progress,) )?; } else { - maybe_await!( - blockchain.wallet_sync(self.database.borrow_mut().deref_mut(), progress_update,) - )?; + maybe_await!(blockchain.wallet_sync(self.database.borrow_mut().deref_mut(), progress,))?; } let sync_time = SyncTime { From dcd90f8b61287164dae60f039848d3605eab8616 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Wed, 23 Feb 2022 09:52:18 +1100 Subject: [PATCH 3/9] Restore but depreciate new_offline --- src/wallet/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index a8d6a62c..b37a108c 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -171,6 +171,18 @@ impl Wallet where D: BatchDatabase, { + + #[deprecated = "Just use Wallet::new -- all wallets are offline now!"] + /// Create a new "offline" wallet + pub fn new_offline( + descriptor: E, + change_descriptor: Option, + network: Network, + database: D, + ) -> Result { + Self::new(descriptor, change_descriptor, network, database) + } + /// Create a wallet. /// /// The only way this can fail is if the descriptors passed in do not match the checksums in `database`. From c0e75fc1a8560cdacf705ff60ef8833befd8280c Mon Sep 17 00:00:00 2001 From: LLFourn Date: Wed, 23 Feb 2022 10:38:35 +1100 Subject: [PATCH 4/9] Split get_tx into its own trait to make supporting verify_tx easier --- src/blockchain/any.rs | 10 +++++--- src/blockchain/compact_filters/mod.rs | 16 ++++++------ src/blockchain/electrum.rs | 10 +++++--- src/blockchain/esplora/reqwest.rs | 11 ++++++--- src/blockchain/esplora/ureq.rs | 10 +++++--- src/blockchain/mod.rs | 21 +++++++++++----- src/blockchain/rpc.rs | 14 +++++------ src/testutils/blockchain_tests.rs | 6 ++--- src/wallet/mod.rs | 1 - src/wallet/verify.rs | 35 ++++----------------------- 10 files changed, 65 insertions(+), 69 deletions(-) diff --git a/src/blockchain/any.rs b/src/blockchain/any.rs index ef4abffa..079524ae 100644 --- a/src/blockchain/any.rs +++ b/src/blockchain/any.rs @@ -89,9 +89,6 @@ impl Blockchain for AnyBlockchain { maybe_await!(impl_inner_method!(self, get_capabilities)) } - 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)) } @@ -108,6 +105,13 @@ impl GetHeight for AnyBlockchain { } } +#[maybe_async] +impl GetTx for AnyBlockchain { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + maybe_await!(impl_inner_method!(self, get_tx, txid)) + } +} + #[maybe_async] impl WalletSync for AnyBlockchain { fn wallet_sync( diff --git a/src/blockchain/compact_filters/mod.rs b/src/blockchain/compact_filters/mod.rs index d0b96421..e7e235b5 100644 --- a/src/blockchain/compact_filters/mod.rs +++ b/src/blockchain/compact_filters/mod.rs @@ -67,7 +67,7 @@ mod peer; mod store; mod sync; -use super::{Blockchain, Capability, ConfigurableBlockchain, GetHeight, Progress, WalletSync}; +use crate::blockchain::*; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::error::Error; use crate::types::{KeychainKind, LocalUtxo, TransactionDetails}; @@ -225,12 +225,6 @@ impl Blockchain for CompactFiltersBlockchain { vec![Capability::FullHistory].into_iter().collect() } - 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())?; @@ -249,6 +243,14 @@ impl GetHeight for CompactFiltersBlockchain { } } +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 wallet_setup( diff --git a/src/blockchain/electrum.rs b/src/blockchain/electrum.rs index b10b90d5..0b8691bc 100644 --- a/src/blockchain/electrum.rs +++ b/src/blockchain/electrum.rs @@ -68,10 +68,6 @@ impl Blockchain for ElectrumBlockchain { .collect() } - 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(|_| ())?) } @@ -94,6 +90,12 @@ impl GetHeight for ElectrumBlockchain { } } +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, diff --git a/src/blockchain/esplora/reqwest.rs b/src/blockchain/esplora/reqwest.rs index 0a9ed4dd..2141b8e6 100644 --- a/src/blockchain/esplora/reqwest.rs +++ b/src/blockchain/esplora/reqwest.rs @@ -91,10 +91,6 @@ impl Blockchain for EsploraBlockchain { .collect() } - 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))?) } @@ -112,6 +108,13 @@ impl GetHeight for EsploraBlockchain { } } +#[maybe_async] +impl GetTx for EsploraBlockchain { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + Ok(await_or_block!(self.url_client._get_tx(txid))?) + } +} + #[maybe_async] impl WalletSync for EsploraBlockchain { fn wallet_setup( diff --git a/src/blockchain/esplora/ureq.rs b/src/blockchain/esplora/ureq.rs index 3e240f43..9bfa378f 100644 --- a/src/blockchain/esplora/ureq.rs +++ b/src/blockchain/esplora/ureq.rs @@ -87,10 +87,6 @@ impl Blockchain for EsploraBlockchain { .collect() } - 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(()) @@ -108,6 +104,12 @@ impl GetHeight for EsploraBlockchain { } } +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, diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 5962fca2..714fdf6a 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -86,11 +86,9 @@ pub enum Capability { /// Trait that defines the actions that must be supported by a blockchain backend #[maybe_async] -pub trait Blockchain: WalletSync + GetHeight { +pub trait Blockchain: WalletSync + GetHeight + GetTx { /// Return the set of [`Capability`] supported by this backend fn get_capabilities(&self) -> HashSet; - /// 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>; /// Estimate the fee rate required to confirm a transaction in a given `target` of blocks @@ -104,6 +102,13 @@ pub trait GetHeight { fn get_height(&self) -> Result; } +#[maybe_async] +/// Trait for getting a transaction by txid +pub trait GetTx { + /// Fetch a transaction given its txid + fn get_tx(&self, txid: &Txid) -> Result, Error>; +} + /// Trait for blockchains that can sync by updating the database directly. #[maybe_async] pub trait WalletSync { @@ -230,9 +235,6 @@ impl Blockchain for Arc { maybe_await!(self.deref().get_capabilities()) } - 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)) } @@ -242,6 +244,13 @@ impl Blockchain for Arc { } } +#[maybe_async] +impl GetTx for Arc { + fn get_tx(&self, txid: &Txid) -> Result, Error> { + maybe_await!(self.deref().get_tx(txid)) + } +} + #[maybe_async] impl GetHeight for Arc { fn get_height(&self) -> Result { diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs index b96af993..a474a0e2 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -33,9 +33,7 @@ use crate::bitcoin::consensus::deserialize; use crate::bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid}; -use crate::blockchain::{ - Blockchain, Capability, ConfigurableBlockchain, GetHeight, Progress, WalletSync, -}; +use crate::blockchain::*; use crate::database::{BatchDatabase, DatabaseUtils}; use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails}; use bitcoincore_rpc::json::{ @@ -141,10 +139,6 @@ impl Blockchain for RpcBlockchain { self.capabilities.clone() } - 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(|_| ())?) } @@ -161,6 +155,12 @@ impl Blockchain for RpcBlockchain { } } +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)?) diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index 68858a84..eefdd75b 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -1097,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! { @@ -1108,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); @@ -1119,7 +1119,7 @@ macro_rules! bdk_blockchain_tests { }; let txid_2 = test_client.receive(tx); - wallet.sync(noop_progress(), None).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); let tx_2 = wallet.list_transactions(false).unwrap().into_iter().find(|tx| tx.txid == txid_2).unwrap(); assert_eq!(tx_2.received, 10_000); assert_eq!(tx_2.sent, 0); diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index b37a108c..d0f86344 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -171,7 +171,6 @@ impl Wallet where D: BatchDatabase, { - #[deprecated = "Just use Wallet::new -- all wallets are offline now!"] /// Create a new "offline" wallet pub fn new_offline( diff --git a/src/wallet/verify.rs b/src/wallet/verify.rs index 9b563339..0f12e056 100644 --- a/src/wallet/verify.rs +++ b/src/wallet/verify.rs @@ -17,7 +17,7 @@ use std::fmt; use bitcoin::consensus::serialize; use bitcoin::{OutPoint, Transaction, Txid}; -use crate::blockchain::Blockchain; +use crate::blockchain::GetTx; use crate::database::Database; use crate::error::Error; @@ -29,7 +29,7 @@ use crate::error::Error; /// Depending on the [capabilities](crate::blockchain::Blockchain::get_capabilities) of the /// [`Blockchain`] backend, the method could fail when called with old "historical" transactions or /// with unconfirmed transactions that have been evicted from the backend's memory. -pub fn verify_tx( +pub fn verify_tx( tx: &Transaction, database: &D, blockchain: &B, @@ -104,43 +104,18 @@ impl_error!(bitcoinconsensus::Error, Consensus, VerifyError); #[cfg(test)] mod test { - use std::collections::HashSet; - + use super::*; + use crate::database::{BatchOperations, MemoryDatabase}; use bitcoin::consensus::encode::deserialize; use bitcoin::hashes::hex::FromHex; use bitcoin::{Transaction, Txid}; - use crate::blockchain::{Blockchain, Capability, Progress}; - use crate::database::{BatchDatabase, BatchOperations, MemoryDatabase}; - use crate::FeeRate; - - use super::*; - struct DummyBlockchain; - impl Blockchain for DummyBlockchain { - fn get_capabilities(&self) -> HashSet { - Default::default() - } - fn setup( - &self, - _database: &mut D, - _progress_update: P, - ) -> Result<(), Error> { - Ok(()) - } + impl GetTx for DummyBlockchain { fn get_tx(&self, _txid: &Txid) -> Result, Error> { Ok(None) } - fn broadcast(&self, _tx: &Transaction) -> Result<(), Error> { - Ok(()) - } - fn get_height(&self) -> Result { - Ok(42) - } - fn estimate_fee(&self, _target: usize) -> Result { - Ok(FeeRate::default_min_relay_fee()) - } } #[test] From 035307ef54ebc330b1760bbdd17bdab3b167cfeb Mon Sep 17 00:00:00 2001 From: LLFourn Date: Thu, 24 Feb 2022 20:32:47 +1100 Subject: [PATCH 5/9] [rpc] Filter out unrelated transactions For some reason while doing the "remove blockchain from wallet PR" I changed the tests around in what I thought was a benign way. But it meant (apparently) that both parties "test_sync_double_receive" were using the same "blockchain". This meant that when the blockchain was RPC they both imported their addresses to it and got each other's results when syncing. This bugged out the sync and this commit fixes that. --- src/blockchain/rpc.rs | 32 ++++++++++++++++--------------- src/testutils/blockchain_tests.rs | 20 +++++++++---------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs index a474a0e2..e4a79846 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -318,22 +318,24 @@ impl WalletSync 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 { diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index eefdd75b..b54da105 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -621,7 +621,7 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_double_receive() { let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None)); + let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None)); // need to sync so rpc can start watching receiver_wallet.sync(&blockchain, SyncOptions::default()).unwrap(); @@ -629,15 +629,15 @@ macro_rules! bdk_blockchain_tests { @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) }); - wallet.sync(&blockchain, SyncOptions::default()).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() }; @@ -645,17 +645,17 @@ 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() }; - blockchain.broadcast(&tx1).unwrap(); - blockchain.broadcast(&tx2).unwrap(); + blockchain.broadcast(&tx1).expect("broadcasting first"); + blockchain.broadcast(&tx2).expect("broadcasting replacement"); - receiver_wallet.sync(&blockchain, SyncOptions::default()).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] From fbb50ad1c886a9cb716146fa823558d7d655c7b7 Mon Sep 17 00:00:00 2001 From: Lloyd Fournier Date: Thu, 24 Feb 2022 20:59:21 +1100 Subject: [PATCH 6/9] apply doc suggestions from @notmandatory Co-authored-by: Steve Myers --- src/blockchain/any.rs | 2 +- src/wallet/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blockchain/any.rs b/src/blockchain/any.rs index 079524ae..91f448b3 100644 --- a/src/blockchain/any.rs +++ b/src/blockchain/any.rs @@ -16,7 +16,7 @@ //! //! ## Example //! -//! 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 diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index d0f86344..4006ccb2 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -163,7 +163,7 @@ impl fmt::Display for AddressInfo { pub struct SyncOptions { /// The progress tracker which may be informed when progress is made. pub progress: Option>, - /// The maximum number of addresses sync on. + /// The maximum number of new addresses to derive and cache on sync. pub max_addresses: Option, } From 45767fcaf7c65d5020f99fdc35c147acd6e8d037 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Mon, 7 Mar 2022 10:44:41 +1100 Subject: [PATCH 7/9] Remove max_addresses sync param You can do this with ensure_addresses_cached if you really want to. --- CHANGELOG.md | 3 ++- src/wallet/mod.rs | 10 ++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cad538d5..f0f06be4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ To decouple the `Wallet` from the `Blockchain` we've made major changes: - 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_addreses_cached`. ## [v0.16.1] - [v0.16.0] @@ -432,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/src/wallet/mod.rs b/src/wallet/mod.rs index 97f7bbb8..5ee2b9a4 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -163,8 +163,6 @@ impl fmt::Display for AddressInfo { pub struct SyncOptions { /// The progress tracker which may be informed when progress is made. pub progress: Option>, - /// The maximum number of new addresses to derive and cache on sync. - pub max_addresses: Option, } impl Wallet @@ -1524,14 +1522,10 @@ where ) -> Result<(), Error> { debug!("Begin sync..."); - let SyncOptions { - max_addresses, - progress, - } = sync_opts; + let SyncOptions { progress } = sync_opts; let progress = progress.unwrap_or_else(|| Box::new(NoopProgress)); - let run_setup = - self.ensure_addresses_cached(max_addresses.unwrap_or(CACHE_ADDR_BATCH_SIZE))?; + 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? From 660faab1e20defe86a7baf132e52928f538b9cbb Mon Sep 17 00:00:00 2001 From: LLFourn Date: Tue, 8 Mar 2022 14:00:29 +1100 Subject: [PATCH 8/9] Fix typo in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0f06be4..a82e1e03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ To decouple the `Wallet` from the `Blockchain` we've made major changes: - 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_addreses_cached`. +- 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] From 0cc4700bd67be84bb5cb0814bf5c8aa9fc3f3cdc Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 8 Mar 2022 20:02:47 -0600 Subject: [PATCH 9/9] Fix typo in CHANGELOG and doc in wallet/mod.rs --- CHANGELOG.md | 2 +- src/wallet/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a82e1e03..9ca6b8d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ 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) +- Deprecated `Wallet::new_offline` (all wallets are offline now) - Changed `Wallet::sync` to take a `Blockchain`. - Stop making a request for the block height when calling `Wallet:new`. - Added `SyncOptions` to capture extra (future) arguments to `Wallet::sync`. diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 5ee2b9a4..fd105478 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -233,7 +233,7 @@ where self.network } - // Return a newly derived address using the external descriptor + // Return a newly derived address for the specified `keychain`. fn get_new_address(&self, keychain: KeychainKind) -> Result { let incremented_index = self.fetch_and_increment_index(keychain)?;