Merge bitcoindevkit/bdk#535: Remove blockchain from wallet

0cc4700bd67be84bb5cb0814bf5c8aa9fc3f3cdc Fix typo in CHANGELOG and doc in wallet/mod.rs (Steve Myers)
660faab1e20defe86a7baf132e52928f538b9cbb Fix typo in CHANGELOG (LLFourn)
45767fcaf7c65d5020f99fdc35c147acd6e8d037 Remove max_addresses sync param (LLFourn)
fbb50ad1c886a9cb716146fa823558d7d655c7b7 apply doc suggestions from @notmandatory (Lloyd Fournier)
035307ef54ebc330b1760bbdd17bdab3b167cfeb [rpc] Filter out unrelated transactions (LLFourn)
c0e75fc1a8560cdacf705ff60ef8833befd8280c Split get_tx into its own trait (LLFourn)
dcd90f8b61287164dae60f039848d3605eab8616 Restore but depreciate new_offline (LLFourn)
410a51355bb47dbf3401cbe6f19546255c6721e0 Add SyncOptions as the second argument to Wallet::sync (LLFourn)
326bfe82a89eaa79acf90c34442234c7e53e1caf Remove Blockchain from wallet (LLFourn)

Pull request description:

  While trying to finish #490 I thought that it'd be better to try the idea of getting rid of a lot of the async macros and just having tow different traits for sync and async stuff. While trying to do that I felt that this needed to be done first.

  The goal of this change is to decouple the wallet from the blockchain trait. A wallet is something that keeps track of UTXOs and transactions (and can sign things). The only reason it should need to talk to the blockchain is if doing a `sync`. So we remove all superfluous calls to the blockchain and precisely define the requirements for the blockchain to be used to sync with two new traits: `WalletSync` and `GetHeight`.

  1. Stop requesting height when wallet is created
  2. `new_offline` is just `new` now.
  3. a `WalletSync + GetHeight` is now the first argument to `sync`.
  4. `SyncOptions` replaces the existing options to `sync` and allows for less friction when making breaking changes in the fuutre (no more noop_progress!).
  5. We `Box` the `Progress` now to avoid type parameters
  6. broadcast has been removed from `Wallet`. You just use the blockchain directly to broadcast now.

  ### Notes to the reviewers

  This needs #502 before it can be merged but can reviewed now.
  Be on the look up for stale documentation from this change.
  Our doc build looks broken because of ureq or something.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing
  * [x] I've updated `CHANGELOG.md`

Top commit has no ACKs.

Tree-SHA512: a8f3e21e45359be642b1f30d4ac1ba74785439e1b770dbeab0a5a4b8aab1eef4bb6b855aea4382e289257bc890fa713ca832a8b6c9655f7a59e96d412b4da3e6
This commit is contained in:
Steve Myers 2022-03-09 10:44:08 -06:00
commit 3e4678d8e3
No known key found for this signature in database
GPG Key ID: 8105A46B22C2D051
23 changed files with 586 additions and 604 deletions

View File

@ -11,6 +11,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `get_internal_address` to allow you to get internal addresses just as you get external addresses. - Add `get_internal_address` to allow you to get internal addresses just as you get external addresses.
- added `ensure_addresses_cached` to `Wallet` to let offline wallets load and cache addresses in their database - added `ensure_addresses_cached` to `Wallet` to let offline wallets load and cache addresses in their database
### Sync API change
To decouple the `Wallet` from the `Blockchain` we've made major changes:
- Removed `Blockchain` from Wallet.
- Removed `Wallet::broadcast` (just use `Blockchain::broadcast`)
- Deprecated `Wallet::new_offline` (all wallets are offline now)
- Changed `Wallet::sync` to take a `Blockchain`.
- Stop making a request for the block height when calling `Wallet:new`.
- Added `SyncOptions` to capture extra (future) arguments to `Wallet::sync`.
- Removed `max_addresses` sync parameter which determined how many addresses to cache before syncing since this can just be done with `ensure_addresses_cached`.
## [v0.16.1] - [v0.16.0] ## [v0.16.1] - [v0.16.0]
- Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0 - Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0
@ -421,4 +433,4 @@ final transaction is created by calling `finish` on the builder.
[v0.14.0]: https://github.com/bitcoindevkit/bdk/compare/v0.13.0...v0.14.0 [v0.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.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.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 [v0.16.1]: https://github.com/bitcoindevkit/bdk/compare/v0.16.0...v0.16.1

View File

@ -41,21 +41,21 @@ The `bdk` library aims to be the core building block for Bitcoin wallets of any
```rust,no_run ```rust,no_run
use bdk::Wallet; use bdk::Wallet;
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;
use bdk::blockchain::{noop_progress, ElectrumBlockchain}; use bdk::blockchain::ElectrumBlockchain;
use bdk::SyncOptions;
use bdk::electrum_client::Client; use bdk::electrum_client::Client;
fn main() -> Result<(), bdk::Error> { 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( let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet, bitcoin::Network::Testnet,
MemoryDatabase::default(), MemoryDatabase::default(),
ElectrumBlockchain::from(client)
)?; )?;
wallet.sync(noop_progress(), None)?; wallet.sync(&blockchain, SyncOptions::default())?;
println!("Descriptor balance: {} SAT", wallet.get_balance()?); println!("Descriptor balance: {} SAT", wallet.get_balance()?);
@ -70,7 +70,7 @@ use bdk::{Wallet, database::MemoryDatabase};
use bdk::wallet::AddressIndex::New; use bdk::wallet::AddressIndex::New;
fn main() -> Result<(), bdk::Error> { fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new_offline( let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet, bitcoin::Network::Testnet,
@ -88,9 +88,9 @@ fn main() -> Result<(), bdk::Error> {
### Create a transaction ### Create a transaction
```rust,no_run ```rust,no_run
use bdk::{FeeRate, Wallet}; use bdk::{FeeRate, Wallet, SyncOptions};
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;
use bdk::blockchain::{noop_progress, ElectrumBlockchain}; use bdk::blockchain::ElectrumBlockchain;
use bdk::electrum_client::Client; use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex::New; use bdk::wallet::AddressIndex::New;
@ -98,16 +98,15 @@ use bdk::wallet::AddressIndex::New;
use bitcoin::consensus::serialize; use bitcoin::consensus::serialize;
fn main() -> Result<(), bdk::Error> { 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( let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet, bitcoin::Network::Testnet,
MemoryDatabase::default(), MemoryDatabase::default(),
ElectrumBlockchain::from(client)
)?; )?;
wallet.sync(noop_progress(), None)?; wallet.sync(&blockchain, SyncOptions::default())?;
let send_to = wallet.get_address(New)?; let send_to = wallet.get_address(New)?;
let (psbt, details) = { let (psbt, details) = {
@ -135,7 +134,7 @@ use bdk::{Wallet, SignOptions, database::MemoryDatabase};
use bitcoin::consensus::deserialize; use bitcoin::consensus::deserialize;
fn main() -> Result<(), bdk::Error> { fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new_offline( let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
bitcoin::Network::Testnet, bitcoin::Network::Testnet,

View File

@ -48,8 +48,7 @@ impl AddressValidator for DummyValidator {
fn main() -> Result<(), bdk::Error> { fn main() -> Result<(), bdk::Error> {
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))"; let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
let mut wallet = let mut wallet = Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
wallet.add_address_validator(Arc::new(DummyValidator)); wallet.add_address_validator(Arc::new(DummyValidator));

View File

@ -10,7 +10,6 @@
// licenses. // licenses.
use bdk::blockchain::compact_filters::*; use bdk::blockchain::compact_filters::*;
use bdk::blockchain::noop_progress;
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;
use bdk::*; use bdk::*;
use bitcoin::*; use bitcoin::*;
@ -35,9 +34,8 @@ fn main() -> Result<(), CompactFiltersError> {
let descriptor = "wpkh(tpubD6NzVbkrYhZ4X2yy78HWrr1M9NT8dKeWfzNiQqDdMqqa9UmmGztGGz6TaLFGsLfdft5iu32gxq1T4eMNxExNNWzVCpf9Y6JZi5TnqoC9wJq/*)"; let descriptor = "wpkh(tpubD6NzVbkrYhZ4X2yy78HWrr1M9NT8dKeWfzNiQqDdMqqa9UmmGztGGz6TaLFGsLfdft5iu32gxq1T4eMNxExNNWzVCpf9Y6JZi5TnqoC9wJq/*)";
let database = MemoryDatabase::default(); let database = MemoryDatabase::default();
let wallet = let wallet = Arc::new(Wallet::new(descriptor, None, Network::Testnet, database).unwrap());
Arc::new(Wallet::new(descriptor, None, Network::Testnet, database, blockchain).unwrap()); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
info!("balance: {}", wallet.get_balance()?); info!("balance: {}", wallet.get_balance()?);
Ok(()) Ok(())
} }

View File

@ -89,7 +89,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.transpose() .transpose()
.unwrap() .unwrap()
.unwrap_or(Network::Testnet); .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)?); info!("... First address: {}", wallet.get_address(New)?);

View File

@ -16,61 +16,17 @@
//! //!
//! ## Example //! ## Example
//! //!
//! In this example both `wallet_electrum` and `wallet_esplora` have the same type of //! When paired with the use of [`ConfigurableBlockchain`], it allows creating any
//! `Wallet<AnyBlockchain, MemoryDatabase>`. 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<AnyBlockchain, _> = 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<AnyBlockchain, _> = 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: //! blockchain type supported using a single line of code:
//! //!
//! ```no_run //! ```no_run
//! # use bitcoin::Network; //! # use bitcoin::Network;
//! # use bdk::blockchain::*; //! # use bdk::blockchain::*;
//! # use bdk::database::MemoryDatabase;
//! # use bdk::Wallet;
//! # #[cfg(all(feature = "esplora", feature = "ureq"))] //! # #[cfg(all(feature = "esplora", feature = "ureq"))]
//! # { //! # {
//! let config = serde_json::from_str("...")?; //! let config = serde_json::from_str("...")?;
//! let blockchain = AnyBlockchain::from_config(&config)?; //! let blockchain = AnyBlockchain::from_config(&config)?;
//! let wallet = Wallet::new( //! let height = blockchain.get_height();
//! "...",
//! None,
//! Network::Testnet,
//! MemoryDatabase::default(),
//! blockchain,
//! )?;
//! # } //! # }
//! # Ok::<(), bdk::Error>(()) //! # Ok::<(), bdk::Error>(())
//! ``` //! ```
@ -133,33 +89,55 @@ impl Blockchain for AnyBlockchain {
maybe_await!(impl_inner_method!(self, get_capabilities)) maybe_await!(impl_inner_method!(self, get_capabilities))
} }
fn setup<D: BatchDatabase, P: 'static + Progress>(
&self,
database: &mut D,
progress_update: P,
) -> Result<(), Error> {
maybe_await!(impl_inner_method!(self, setup, database, progress_update))
}
fn sync<D: BatchDatabase, P: 'static + Progress>(
&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<Option<Transaction>, Error> {
maybe_await!(impl_inner_method!(self, get_tx, txid))
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
maybe_await!(impl_inner_method!(self, broadcast, tx)) maybe_await!(impl_inner_method!(self, broadcast, tx))
} }
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
maybe_await!(impl_inner_method!(self, estimate_fee, target))
}
}
#[maybe_async]
impl GetHeight for AnyBlockchain {
fn get_height(&self) -> Result<u32, Error> { fn get_height(&self) -> Result<u32, Error> {
maybe_await!(impl_inner_method!(self, get_height)) maybe_await!(impl_inner_method!(self, get_height))
} }
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> { }
maybe_await!(impl_inner_method!(self, estimate_fee, target))
#[maybe_async]
impl GetTx for AnyBlockchain {
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
maybe_await!(impl_inner_method!(self, get_tx, txid))
}
}
#[maybe_async]
impl WalletSync for AnyBlockchain {
fn wallet_sync<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(impl_inner_method!(
self,
wallet_sync,
database,
progress_update
))
}
fn wallet_setup<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(impl_inner_method!(
self,
wallet_setup,
database,
progress_update
))
} }
} }

View File

@ -67,7 +67,7 @@ mod peer;
mod store; mod store;
mod sync; mod sync;
use super::{Blockchain, Capability, ConfigurableBlockchain, Progress}; use crate::blockchain::*;
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::error::Error; use crate::error::Error;
use crate::types::{KeychainKind, LocalUtxo, TransactionDetails}; use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
@ -225,11 +225,38 @@ impl Blockchain for CompactFiltersBlockchain {
vec![Capability::FullHistory].into_iter().collect() vec![Capability::FullHistory].into_iter().collect()
} }
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
self.peers[0].broadcast_tx(tx.clone())?;
Ok(())
}
fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
// TODO
Ok(FeeRate::default())
}
}
impl GetHeight for CompactFiltersBlockchain {
fn get_height(&self) -> Result<u32, Error> {
Ok(self.headers.get_height()? as u32)
}
}
impl GetTx for CompactFiltersBlockchain {
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, 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. #[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
fn setup<D: BatchDatabase, P: 'static + Progress>( fn wallet_setup<D: BatchDatabase>(
&self, &self,
database: &mut D, database: &mut D,
progress_update: P, progress_update: Box<dyn Progress>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let first_peer = &self.peers[0]; let first_peer = &self.peers[0];
@ -430,27 +457,6 @@ impl Blockchain for CompactFiltersBlockchain {
Ok(()) Ok(())
} }
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, 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<u32, Error> {
Ok(self.headers.get_height()? as u32)
}
fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
// TODO
Ok(FeeRate::default())
}
} }
/// Data to connect to a Bitcoin P2P peer /// Data to connect to a Bitcoin P2P peer

View File

@ -68,10 +68,39 @@ impl Blockchain for ElectrumBlockchain {
.collect() .collect()
} }
fn setup<D: BatchDatabase, P: Progress>( fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
Ok(self.client.transaction_broadcast(tx).map(|_| ())?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
Ok(FeeRate::from_btc_per_kvb(
self.client.estimate_fee(target)? as f32
))
}
}
impl GetHeight for ElectrumBlockchain {
fn get_height(&self) -> Result<u32, Error> {
// TODO: unsubscribe when added to the client, or is there a better call to use here?
Ok(self
.client
.block_headers_subscribe()
.map(|data| data.height as u32)?)
}
}
impl GetTx for ElectrumBlockchain {
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(self.client.transaction_get(txid).map(Option::Some)?)
}
}
impl WalletSync for ElectrumBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self, &self,
database: &mut D, database: &mut D,
_progress_update: P, _progress_update: Box<dyn Progress>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut request = script_sync::start(database, self.stop_gap)?; let mut request = script_sync::start(database, self.stop_gap)?;
let mut block_times = HashMap::<u32, u32>::new(); let mut block_times = HashMap::<u32, u32>::new();
@ -207,29 +236,6 @@ impl Blockchain for ElectrumBlockchain {
database.commit_batch(batch_update)?; database.commit_batch(batch_update)?;
Ok(()) Ok(())
} }
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, 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<u32, Error> {
// 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<FeeRate, Error> {
Ok(FeeRate::from_btc_per_kvb(
self.client.estimate_fee(target)? as f32
))
}
} }
struct TxCache<'a, 'b, D> { struct TxCache<'a, 'b, D> {

View File

@ -91,10 +91,36 @@ impl Blockchain for EsploraBlockchain {
.collect() .collect()
} }
fn setup<D: BatchDatabase, P: Progress>( fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
Ok(await_or_block!(self.url_client._broadcast(tx))?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
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<u32, Error> {
Ok(await_or_block!(self.url_client._get_height())?)
}
}
#[maybe_async]
impl GetTx for EsploraBlockchain {
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(await_or_block!(self.url_client._get_tx(txid))?)
}
}
#[maybe_async]
impl WalletSync for EsploraBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self, &self,
database: &mut D, database: &mut D,
_progress_update: P, _progress_update: Box<dyn Progress>,
) -> Result<(), Error> { ) -> Result<(), Error> {
use crate::blockchain::script_sync::Request; use crate::blockchain::script_sync::Request;
let mut request = script_sync::start(database, self.stop_gap)?; let mut request = script_sync::start(database, self.stop_gap)?;
@ -180,23 +206,6 @@ impl Blockchain for EsploraBlockchain {
Ok(()) Ok(())
} }
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, 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<u32, Error> {
Ok(await_or_block!(self.url_client._get_height())?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
let estimates = await_or_block!(self.url_client._get_fee_estimates())?;
super::into_fee_rate(target, estimates)
}
} }
impl UrlClient { impl UrlClient {

View File

@ -87,10 +87,34 @@ impl Blockchain for EsploraBlockchain {
.collect() .collect()
} }
fn setup<D: BatchDatabase, P: Progress>( fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
let _txid = self.url_client._broadcast(tx)?;
Ok(())
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
let estimates = self.url_client._get_fee_estimates()?;
super::into_fee_rate(target, estimates)
}
}
impl GetHeight for EsploraBlockchain {
fn get_height(&self) -> Result<u32, Error> {
Ok(self.url_client._get_height()?)
}
}
impl GetTx for EsploraBlockchain {
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(self.url_client._get_tx(txid)?)
}
}
impl WalletSync for EsploraBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self, &self,
database: &mut D, database: &mut D,
_progress_update: P, _progress_update: Box<dyn Progress>,
) -> Result<(), Error> { ) -> Result<(), Error> {
use crate::blockchain::script_sync::Request; use crate::blockchain::script_sync::Request;
let mut request = script_sync::start(database, self.stop_gap)?; let mut request = script_sync::start(database, self.stop_gap)?;
@ -179,24 +203,6 @@ impl Blockchain for EsploraBlockchain {
Ok(()) Ok(())
} }
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, 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<u32, Error> {
Ok(self.url_client._get_height()?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
let estimates = self.url_client._get_fee_estimates()?;
super::into_fee_rate(target, estimates)
}
} }
impl UrlClient { impl UrlClient {

View File

@ -86,28 +86,50 @@ pub enum Capability {
/// Trait that defines the actions that must be supported by a blockchain backend /// Trait that defines the actions that must be supported by a blockchain backend
#[maybe_async] #[maybe_async]
pub trait Blockchain { pub trait Blockchain: WalletSync + GetHeight + GetTx {
/// Return the set of [`Capability`] supported by this backend /// Return the set of [`Capability`] supported by this backend
fn get_capabilities(&self) -> HashSet<Capability>; fn get_capabilities(&self) -> HashSet<Capability>;
/// 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<FeeRate, Error>;
}
/// Trait for getting the current height of the blockchain.
#[maybe_async]
pub trait GetHeight {
/// Return the current height
fn get_height(&self) -> Result<u32, Error>;
}
#[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<Option<Transaction>, Error>;
}
/// Trait for blockchains that can sync by updating the database directly.
#[maybe_async]
pub trait WalletSync {
/// Setup the backend and populate the internal database for the first time /// 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). /// 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 /// 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. /// 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 /// 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. /// [`WalletSync::wallet_sync`] defaults to calling this internally if not overridden.
fn setup<D: BatchDatabase, P: 'static + Progress>( /// Populate the internal database with transactions and UTXOs
fn wallet_setup<D: BatchDatabase>(
&self, &self,
database: &mut D, database: &mut D,
progress_update: P, progress_update: Box<dyn Progress>,
) -> Result<(), Error>; ) -> Result<(), Error>;
/// Populate the internal database with transactions and UTXOs
/// /// If not overridden, it defaults to calling [`Self::wallet_setup`] internally.
/// If not overridden, it defaults to calling [`Blockchain::setup`] internally.
/// ///
/// This method should implement the logic required to iterate over the list of the wallet's /// 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 /// script_pubkeys using [`Database::iter_script_pubkeys`] and look for relevant transactions
@ -124,23 +146,13 @@ pub trait Blockchain {
/// [`BatchOperations::set_tx`]: crate::database::BatchOperations::set_tx /// [`BatchOperations::set_tx`]: crate::database::BatchOperations::set_tx
/// [`BatchOperations::set_utxo`]: crate::database::BatchOperations::set_utxo /// [`BatchOperations::set_utxo`]: crate::database::BatchOperations::set_utxo
/// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo /// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo
fn sync<D: BatchDatabase, P: 'static + Progress>( fn wallet_sync<D: BatchDatabase>(
&self, &self,
database: &mut D, database: &mut D,
progress_update: P, progress_update: Box<dyn Progress>,
) -> Result<(), Error> { ) -> 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<Option<Transaction>, Error>;
/// Broadcast a transaction
fn broadcast(&self, tx: &Transaction) -> Result<(), Error>;
/// Return the current height
fn get_height(&self) -> Result<u32, Error>;
/// Estimate the fee rate required to confirm a transaction in a given `target` of blocks
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error>;
} }
/// Trait for [`Blockchain`] types that can be created given a configuration /// Trait for [`Blockchain`] types that can be created given a configuration
@ -155,9 +167,9 @@ pub trait ConfigurableBlockchain: Blockchain + Sized {
/// Data sent with a progress update over a [`channel`] /// Data sent with a progress update over a [`channel`]
pub type ProgressData = (f32, Option<String>); pub type ProgressData = (f32, Option<String>);
/// Trait for types that can receive and process progress updates during [`Blockchain::sync`] and /// Trait for types that can receive and process progress updates during [`WalletSync::wallet_sync`] and
/// [`Blockchain::setup`] /// [`WalletSync::wallet_setup`]
pub trait Progress: Send { pub trait Progress: Send + 'static + core::fmt::Debug {
/// Send a new progress update /// Send a new progress update
/// ///
/// The `progress` value should be in the range 0.0 - 100.0, and the `message` value is an /// The `progress` value should be in the range 0.0 - 100.0, and the `message` value is an
@ -182,7 +194,7 @@ impl Progress for Sender<ProgressData> {
} }
/// Type that implements [`Progress`] and drops every update received /// Type that implements [`Progress`] and drops every update received
#[derive(Clone, Copy)] #[derive(Clone, Copy, Default, Debug)]
pub struct NoopProgress; pub struct NoopProgress;
/// Create a new instance of [`NoopProgress`] /// Create a new instance of [`NoopProgress`]
@ -197,7 +209,7 @@ impl Progress for NoopProgress {
} }
/// Type that implements [`Progress`] and logs at level `INFO` every update received /// Type that implements [`Progress`] and logs at level `INFO` every update received
#[derive(Clone, Copy)] #[derive(Clone, Copy, Default, Debug)]
pub struct LogProgress; pub struct LogProgress;
/// Create a new instance of [`LogProgress`] /// Create a new instance of [`LogProgress`]
@ -223,33 +235,44 @@ impl<T: Blockchain> Blockchain for Arc<T> {
maybe_await!(self.deref().get_capabilities()) maybe_await!(self.deref().get_capabilities())
} }
fn setup<D: BatchDatabase, P: 'static + Progress>(
&self,
database: &mut D,
progress_update: P,
) -> Result<(), Error> {
maybe_await!(self.deref().setup(database, progress_update))
}
fn sync<D: BatchDatabase, P: 'static + Progress>(
&self,
database: &mut D,
progress_update: P,
) -> Result<(), Error> {
maybe_await!(self.deref().sync(database, progress_update))
}
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
maybe_await!(self.deref().get_tx(txid))
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
maybe_await!(self.deref().broadcast(tx)) maybe_await!(self.deref().broadcast(tx))
} }
fn get_height(&self) -> Result<u32, Error> {
maybe_await!(self.deref().get_height())
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> { fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
maybe_await!(self.deref().estimate_fee(target)) maybe_await!(self.deref().estimate_fee(target))
} }
} }
#[maybe_async]
impl<T: GetTx> GetTx for Arc<T> {
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
maybe_await!(self.deref().get_tx(txid))
}
}
#[maybe_async]
impl<T: GetHeight> GetHeight for Arc<T> {
fn get_height(&self) -> Result<u32, Error> {
maybe_await!(self.deref().get_height())
}
}
#[maybe_async]
impl<T: WalletSync> WalletSync for Arc<T> {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(self.deref().wallet_setup(database, progress_update))
}
fn wallet_sync<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(self.deref().wallet_sync(database, progress_update))
}
}

View File

@ -33,7 +33,7 @@
use crate::bitcoin::consensus::deserialize; use crate::bitcoin::consensus::deserialize;
use crate::bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid}; use crate::bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid};
use crate::blockchain::{Blockchain, Capability, ConfigurableBlockchain, Progress}; use crate::blockchain::*;
use crate::database::{BatchDatabase, DatabaseUtils}; use crate::database::{BatchDatabase, DatabaseUtils};
use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails}; use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
use bitcoincore_rpc::json::{ use bitcoincore_rpc::json::{
@ -139,10 +139,39 @@ impl Blockchain for RpcBlockchain {
self.capabilities.clone() self.capabilities.clone()
} }
fn setup<D: BatchDatabase, P: 'static + Progress>( fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
Ok(self.client.send_raw_transaction(tx).map(|_| ())?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
let sat_per_kb = self
.client
.estimate_smart_fee(target as u16, None)?
.fee_rate
.ok_or(Error::FeeRateUnavailable)?
.as_sat() as f64;
Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
}
}
impl GetTx for RpcBlockchain {
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(Some(self.client.get_raw_transaction(txid, None)?))
}
}
impl GetHeight for RpcBlockchain {
fn get_height(&self) -> Result<u32, Error> {
Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
}
}
impl WalletSync for RpcBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self, &self,
database: &mut D, database: &mut D,
progress_update: P, progress_update: Box<dyn Progress>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut scripts_pubkeys = database.iter_script_pubkeys(Some(KeychainKind::External))?; let mut scripts_pubkeys = database.iter_script_pubkeys(Some(KeychainKind::External))?;
scripts_pubkeys.extend(database.iter_script_pubkeys(Some(KeychainKind::Internal))?); scripts_pubkeys.extend(database.iter_script_pubkeys(Some(KeychainKind::Internal))?);
@ -187,13 +216,13 @@ impl Blockchain for RpcBlockchain {
} }
} }
self.sync(database, progress_update) self.wallet_sync(database, progress_update)
} }
fn sync<D: BatchDatabase, P: 'static + Progress>( fn wallet_sync<D: BatchDatabase>(
&self, &self,
db: &mut D, db: &mut D,
_progress_update: P, _progress_update: Box<dyn Progress>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut indexes = HashMap::new(); let mut indexes = HashMap::new();
for keykind in &[KeychainKind::External, KeychainKind::Internal] { for keykind in &[KeychainKind::External, KeychainKind::Internal] {
@ -289,22 +318,24 @@ impl Blockchain for RpcBlockchain {
} }
} }
let current_utxos: HashSet<_> = current_utxo // Filter out trasactions that are for script pubkeys that aren't in this wallet.
let current_utxos = current_utxo
.into_iter() .into_iter()
.map(|u| { .filter_map(
Ok(LocalUtxo { |u| match db.get_path_from_script_pubkey(&u.script_pub_key) {
outpoint: OutPoint::new(u.txid, u.vout), Err(e) => Some(Err(e)),
keychain: db Ok(None) => None,
.get_path_from_script_pubkey(&u.script_pub_key)? Ok(Some(path)) => Some(Ok(LocalUtxo {
.ok_or(Error::TransactionNotFound)? outpoint: OutPoint::new(u.txid, u.vout),
.0, keychain: path.0,
txout: TxOut { txout: TxOut {
value: u.amount.as_sat(), value: u.amount.as_sat(),
script_pubkey: u.script_pub_key, script_pubkey: u.script_pub_key,
}, },
}) })),
}) },
.collect::<Result<_, Error>>()?; )
.collect::<Result<HashSet<_>, Error>>()?;
let spent: HashSet<_> = known_utxos.difference(&current_utxos).collect(); let spent: HashSet<_> = known_utxos.difference(&current_utxos).collect();
for s in spent { for s in spent {
@ -324,29 +355,6 @@ impl Blockchain for RpcBlockchain {
Ok(()) Ok(())
} }
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, 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<u32, Error> {
Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
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 { impl ConfigurableBlockchain for RpcBlockchain {

View File

@ -23,12 +23,12 @@
//! # use bdk::database::{AnyDatabase, MemoryDatabase}; //! # use bdk::database::{AnyDatabase, MemoryDatabase};
//! # use bdk::{Wallet}; //! # use bdk::{Wallet};
//! let memory = MemoryDatabase::default(); //! 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")] //! # #[cfg(feature = "key-value-db")]
//! # { //! # {
//! let sled = sled::open("my-database")?.open_tree("default_tree")?; //! 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>(()) //! # Ok::<(), bdk::Error>(())
//! ``` //! ```
@ -42,7 +42,7 @@
//! # use bdk::{Wallet}; //! # use bdk::{Wallet};
//! let config = serde_json::from_str("...")?; //! let config = serde_json::from_str("...")?;
//! let database = AnyDatabase::from_config(&config)?; //! 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>(()) //! # Ok::<(), bdk::Error>(())
//! ``` //! ```

View File

@ -554,7 +554,7 @@ macro_rules! doctest_wallet {
Some(100), Some(100),
); );
$crate::Wallet::new_offline( $crate::Wallet::new(
&descriptors.0, &descriptors.0,
descriptors.1.as_ref(), descriptors.1.as_ref(),
Network::Regtest, Network::Regtest,

View File

@ -79,7 +79,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
/// ///
/// let key = /// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new(
/// P2Pkh(key), /// P2Pkh(key),
/// None, /// None,
/// Network::Testnet, /// Network::Testnet,
@ -113,7 +113,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
/// ///
/// let key = /// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new(
/// P2Wpkh_P2Sh(key), /// P2Wpkh_P2Sh(key),
/// None, /// None,
/// Network::Testnet, /// Network::Testnet,
@ -148,7 +148,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
/// ///
/// let key = /// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new(
/// P2Wpkh(key), /// P2Wpkh(key),
/// None, /// None,
/// Network::Testnet, /// Network::Testnet,
@ -186,7 +186,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
/// use bdk::template::Bip44; /// use bdk::template::Bip44;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new(
/// Bip44(key.clone(), KeychainKind::External), /// Bip44(key.clone(), KeychainKind::External),
/// Some(Bip44(key, KeychainKind::Internal)), /// Some(Bip44(key, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
@ -226,7 +226,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new(
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External), /// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)), /// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
@ -262,7 +262,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
/// use bdk::template::Bip49; /// use bdk::template::Bip49;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new(
/// Bip49(key.clone(), KeychainKind::External), /// Bip49(key.clone(), KeychainKind::External),
/// Some(Bip49(key, KeychainKind::Internal)), /// Some(Bip49(key, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
@ -302,7 +302,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new(
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External), /// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)), /// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
@ -338,7 +338,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
/// use bdk::template::Bip84; /// use bdk::template::Bip84;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new(
/// Bip84(key.clone(), KeychainKind::External), /// Bip84(key.clone(), KeychainKind::External),
/// Some(Bip84(key, KeychainKind::Internal)), /// Some(Bip84(key, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
@ -378,7 +378,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new(
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External), /// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)), /// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,

View File

@ -53,22 +53,22 @@
### Example ### Example
```no_run ```no_run
use bdk::Wallet; use bdk::{Wallet, SyncOptions};
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;
use bdk::blockchain::{noop_progress, ElectrumBlockchain}; use bdk::blockchain::ElectrumBlockchain;
use bdk::electrum_client::Client; use bdk::electrum_client::Client;
fn main() -> Result<(), bdk::Error> { fn main() -> Result<(), bdk::Error> {
let client = Client::new("ssl://electrum.blockstream.info:60002")?; let client = Client::new("ssl://electrum.blockstream.info:60002")?;
let blockchain = ElectrumBlockchain::from(client);
let wallet = Wallet::new( let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet, bitcoin::Network::Testnet,
MemoryDatabase::default(), MemoryDatabase::default(),
ElectrumBlockchain::from(client)
)?; )?;
wallet.sync(noop_progress(), None)?; wallet.sync(&blockchain, SyncOptions::default())?;
println!("Descriptor balance: {} SAT", wallet.get_balance()?); println!("Descriptor balance: {} SAT", wallet.get_balance()?);
@ -87,7 +87,7 @@ fn main() -> Result<(), bdk::Error> {
//! use bdk::wallet::AddressIndex::New; //! use bdk::wallet::AddressIndex::New;
//! //!
//! fn main() -> Result<(), bdk::Error> { //! fn main() -> Result<(), bdk::Error> {
//! let wallet = Wallet::new_offline( //! let wallet = Wallet::new(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", //! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), //! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
//! bitcoin::Network::Testnet, //! bitcoin::Network::Testnet,
@ -108,9 +108,9 @@ fn main() -> Result<(), bdk::Error> {
### Example ### Example
```no_run ```no_run
use bdk::{FeeRate, Wallet}; use bdk::{FeeRate, Wallet, SyncOptions};
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;
use bdk::blockchain::{noop_progress, ElectrumBlockchain}; use bdk::blockchain::ElectrumBlockchain;
use bdk::electrum_client::Client; use bdk::electrum_client::Client;
use bitcoin::consensus::serialize; use bitcoin::consensus::serialize;
@ -123,10 +123,10 @@ fn main() -> Result<(), bdk::Error> {
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet, bitcoin::Network::Testnet,
MemoryDatabase::default(), MemoryDatabase::default(),
ElectrumBlockchain::from(client)
)?; )?;
let blockchain = ElectrumBlockchain::from(client);
wallet.sync(noop_progress(), None)?; wallet.sync(&blockchain, SyncOptions::default())?;
let send_to = wallet.get_address(New)?; let send_to = wallet.get_address(New)?;
let (psbt, details) = { let (psbt, details) = {
@ -160,7 +160,7 @@ fn main() -> Result<(), bdk::Error> {
//! use bdk::database::MemoryDatabase; //! use bdk::database::MemoryDatabase;
//! //!
//! fn main() -> Result<(), bdk::Error> { //! fn main() -> Result<(), bdk::Error> {
//! let wallet = Wallet::new_offline( //! let wallet = Wallet::new(
//! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", //! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), //! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
//! bitcoin::Network::Testnet, //! bitcoin::Network::Testnet,
@ -272,6 +272,7 @@ pub use wallet::address_validator;
pub use wallet::signer; pub use wallet::signer;
pub use wallet::signer::SignOptions; pub use wallet::signer::SignOptions;
pub use wallet::tx_builder::TxBuilder; pub use wallet::tx_builder::TxBuilder;
pub use wallet::SyncOptions;
pub use wallet::Wallet; pub use wallet::Wallet;
/// Get the version of BDK at runtime /// Get the version of BDK at runtime

View File

@ -361,10 +361,10 @@ macro_rules! bdk_blockchain_tests {
mod bdk_blockchain_tests { mod bdk_blockchain_tests {
use $crate::bitcoin::{Transaction, Network}; use $crate::bitcoin::{Transaction, Network};
use $crate::testutils::blockchain_tests::TestClient; use $crate::testutils::blockchain_tests::TestClient;
use $crate::blockchain::noop_progress; use $crate::blockchain::Blockchain;
use $crate::database::MemoryDatabase; use $crate::database::MemoryDatabase;
use $crate::types::KeychainKind; use $crate::types::KeychainKind;
use $crate::{Wallet, FeeRate}; use $crate::{Wallet, FeeRate, SyncOptions};
use $crate::testutils; use $crate::testutils;
use super::*; use super::*;
@ -375,11 +375,11 @@ macro_rules! bdk_blockchain_tests {
$block $block
} }
fn get_wallet_from_descriptors(descriptors: &(String, Option<String>), test_client: &TestClient) -> Wallet<$blockchain, MemoryDatabase> { fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<MemoryDatabase> {
Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new(), get_blockchain(test_client)).unwrap() Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new()).unwrap()
} }
fn init_single_sig() -> (Wallet<$blockchain, MemoryDatabase>, (String, Option<String>), TestClient) { fn init_single_sig() -> (Wallet<MemoryDatabase>, $blockchain, (String, Option<String>), TestClient) {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let descriptors = testutils! { let descriptors = testutils! {
@ -387,13 +387,14 @@ macro_rules! bdk_blockchain_tests {
}; };
let test_client = TestClient::default(); 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 // rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool
#[cfg(feature = "test-rpc")] #[cfg(feature = "test-rpc")]
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
(wallet, descriptors, test_client) (wallet, blockchain, descriptors, test_client)
} }
#[test] #[test]
@ -401,7 +402,7 @@ macro_rules! bdk_blockchain_tests {
use std::ops::Deref; use std::ops::Deref;
use crate::database::Database; 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! { let tx = testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) @tx ( (@external descriptors, 0) => 50_000 )
@ -414,7 +415,7 @@ macro_rules! bdk_blockchain_tests {
#[cfg(not(feature = "test-rpc"))] #[cfg(not(feature = "test-rpc"))]
assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none"); assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none");
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated"); assert!(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"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
@ -429,7 +430,7 @@ macro_rules! bdk_blockchain_tests {
#[test] #[test]
fn test_sync_stop_gap_20() { 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! { test_client.receive(testutils! {
@tx ( (@external descriptors, 5) => 50_000 ) @tx ( (@external descriptors, 5) => 50_000 )
@ -438,7 +439,7 @@ macro_rules! bdk_blockchain_tests {
@tx ( (@external descriptors, 25) => 50_000 ) @tx ( (@external descriptors, 25) => 50_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 100_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 100_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
@ -446,16 +447,16 @@ macro_rules! bdk_blockchain_tests {
#[test] #[test]
fn test_sync_before_and_after_receive() { fn test_sync_before_and_after_receive() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0); assert_eq!(wallet.get_balance().unwrap(), 0);
test_client.receive(testutils! { test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) @tx ( (@external descriptors, 0) => 50_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
@ -463,13 +464,13 @@ macro_rules! bdk_blockchain_tests {
#[test] #[test]
fn test_sync_multiple_outputs_same_tx() { 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! { let txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 ) @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 105_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 105_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
@ -484,7 +485,7 @@ macro_rules! bdk_blockchain_tests {
#[test] #[test]
fn test_sync_receive_multi() { 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! { test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) @tx ( (@external descriptors, 0) => 50_000 )
@ -493,7 +494,7 @@ macro_rules! bdk_blockchain_tests {
@tx ( (@external descriptors, 5) => 25_000 ) @tx ( (@external descriptors, 5) => 25_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
@ -502,32 +503,32 @@ macro_rules! bdk_blockchain_tests {
#[test] #[test]
fn test_sync_address_reuse() { 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! { test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) @tx ( (@external descriptors, 0) => 50_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000); assert_eq!(wallet.get_balance().unwrap(), 50_000);
test_client.receive(testutils! { test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 25_000 ) @tx ( (@external descriptors, 0) => 25_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
} }
#[test] #[test]
fn test_sync_receive_rbf_replaced() { 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! { let txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true ) @tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs"); 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); let new_txid = test_client.bump_fee(&txid);
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after bump"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after bump");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs 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"))] #[cfg(not(feature = "esplora"))]
#[test] #[test]
fn test_sync_reorg_block() { 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! { let txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true ) @tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs"); 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 // Invalidate 1 block
test_client.invalidate(1); test_client.invalidate(1);
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after invalidate"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after invalidate");
@ -589,15 +590,15 @@ macro_rules! bdk_blockchain_tests {
#[test] #[test]
fn test_sync_after_send() { fn test_sync_after_send() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
println!("{}", descriptors.0); println!("{}", descriptors.0);
let node_addr = test_client.get_node_address(None); let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! { test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) @tx ( (@external descriptors, 0) => 50_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
@ -607,8 +608,8 @@ macro_rules! bdk_blockchain_tests {
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx(); let tx = psbt.extract_tx();
println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
wallet.broadcast(&tx).unwrap(); blockchain.broadcast(&tx).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send"); 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"); assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
@ -619,24 +620,24 @@ macro_rules! bdk_blockchain_tests {
/// The coins should only be received once! /// The coins should only be received once!
#[test] #[test]
fn test_sync_double_receive() { fn test_sync_double_receive() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None), &test_client); let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None));
// need to sync so rpc can start watching // need to sync so rpc can start watching
receiver_wallet.sync(noop_progress(), None).unwrap(); receiver_wallet.sync(&blockchain, SyncOptions::default()).unwrap();
test_client.receive(testutils! { test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).expect("sync");
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address; let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
let tx1 = { let tx1 = {
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf(); builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf();
let (mut psbt, _details) = builder.finish().unwrap(); let (mut psbt, _details) = builder.finish().expect("building first tx");
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing first tx");
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
psbt.extract_tx() psbt.extract_tx()
}; };
@ -644,22 +645,22 @@ macro_rules! bdk_blockchain_tests {
let tx2 = { let tx2 = {
let mut builder = wallet.build_tx(); 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)); 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 (mut psbt, _details) = builder.finish().expect("building replacement tx");
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing replacement tx");
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
psbt.extract_tx() psbt.extract_tx()
}; };
wallet.broadcast(&tx1).unwrap(); blockchain.broadcast(&tx1).expect("broadcasting first");
wallet.broadcast(&tx2).unwrap(); blockchain.broadcast(&tx2).expect("broadcasting replacement");
receiver_wallet.sync(noop_progress(), None).unwrap(); receiver_wallet.sync(&blockchain, SyncOptions::default()).expect("syncing receiver");
assert_eq!(receiver_wallet.get_balance().unwrap(), 49_000, "should have received coins once and only once"); assert_eq!(receiver_wallet.get_balance().expect("balance"), 49_000, "should have received coins once and only once");
} }
#[test] #[test]
fn test_sync_many_sends_to_a_single_address() { 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 { for _ in 0..4 {
// split this up into multiple blocks so rpc doesn't get angry // split this up into multiple blocks so rpc doesn't get angry
@ -678,22 +679,22 @@ macro_rules! bdk_blockchain_tests {
}); });
} }
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 100_000); assert_eq!(wallet.get_balance().unwrap(), 100_000);
} }
#[test] #[test]
fn test_update_confirmation_time_after_generate() { fn test_update_confirmation_time_after_generate() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
println!("{}", descriptors.0); println!("{}", descriptors.0);
let node_addr = test_client.get_node_address(None); let node_addr = test_client.get_node_address(None);
let received_txid = test_client.receive(testutils! { let received_txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) @tx ( (@external descriptors, 0) => 50_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>(); let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
@ -701,7 +702,7 @@ macro_rules! bdk_blockchain_tests {
assert!(details.confirmation_time.is_none()); assert!(details.confirmation_time.is_none());
test_client.generate(1, Some(node_addr)); test_client.generate(1, Some(node_addr));
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>(); let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
let details = tx_map.get(&received_txid).unwrap(); let details = tx_map.get(&received_txid).unwrap();
@ -711,13 +712,13 @@ macro_rules! bdk_blockchain_tests {
#[test] #[test]
fn test_sync_outgoing_from_scratch() { fn test_sync_outgoing_from_scratch() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None); let node_addr = test_client.get_node_address(None);
let received_txid = test_client.receive(testutils! { let received_txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) @tx ( (@external descriptors, 0) => 50_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
@ -726,25 +727,26 @@ macro_rules! bdk_blockchain_tests {
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
let sent_txid = wallet.broadcast(&psbt.extract_tx()).unwrap(); let sent_tx = psbt.extract_tx();
blockchain.broadcast(&sent_tx).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive"); assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive");
// empty wallet // 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 #[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
test_client.generate(1, Some(node_addr)); test_client.generate(1, Some(node_addr));
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>(); let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
let received = tx_map.get(&received_txid).unwrap(); let received = tx_map.get(&received_txid).unwrap();
assert_eq!(received.received, 50_000, "incorrect received from receiver"); assert_eq!(received.received, 50_000, "incorrect received from receiver");
assert_eq!(received.sent, 0, "incorrect sent 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.received, details.received, "incorrect received from sender");
assert_eq!(sent.sent, details.sent, "incorrect sent 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"); 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] #[test]
fn test_sync_long_change_chain() { fn test_sync_long_change_chain() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None); let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! { test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) @tx ( (@external descriptors, 0) => 50_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut total_sent = 0; let mut total_sent = 0;
@ -769,38 +771,38 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&psbt.extract_tx()).unwrap(); blockchain.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
total_sent += 5_000 + details.fee.unwrap_or(0); total_sent += 5_000 + details.fee.unwrap_or(0);
} }
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance after chain"); assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance after chain");
// empty wallet // 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 #[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
test_client.generate(1, Some(node_addr)); test_client.generate(1, Some(node_addr));
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance empty wallet"); assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance empty wallet");
} }
#[test] #[test]
fn test_sync_bump_fee_basic() { fn test_sync_bump_fee_basic() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None); let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! { test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
@ -808,8 +810,8 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&psbt.extract_tx()).unwrap(); blockchain.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
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(), 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"); assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance from received");
@ -818,8 +820,8 @@ macro_rules! bdk_blockchain_tests {
let (mut new_psbt, new_details) = builder.finish().expect("fee bump tx"); let (mut new_psbt, new_details) = builder.finish().expect("fee bump tx");
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&new_psbt.extract_tx()).unwrap(); blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(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(), 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"); 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] #[test]
fn test_sync_bump_fee_remove_change() { fn test_sync_bump_fee_remove_change() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None); let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! { test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
@ -843,8 +845,8 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&psbt.extract_tx()).unwrap(); blockchain.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send"); 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"); 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 (mut new_psbt, new_details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&new_psbt.extract_tx()).unwrap(); blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after change removal"); assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after change removal");
assert_eq!(new_details.received, 0, "incorrect received after change removal"); assert_eq!(new_details.received, 0, "incorrect received after change removal");
@ -863,14 +865,14 @@ macro_rules! bdk_blockchain_tests {
#[test] #[test]
fn test_sync_bump_fee_add_input_simple() { fn test_sync_bump_fee_add_input_simple() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None); let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! { test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
@ -878,8 +880,8 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&psbt.extract_tx()).unwrap(); blockchain.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send"); 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"); 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 (mut new_psbt, new_details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&new_psbt.extract_tx()).unwrap(); blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(new_details.sent, 75_000, "incorrect sent"); assert_eq!(new_details.sent, 75_000, "incorrect sent");
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance after add input"); assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance after add input");
} }
#[test] #[test]
fn test_sync_bump_fee_add_input_no_change() { fn test_sync_bump_fee_add_input_no_change() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None); let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! { test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
@ -911,8 +913,8 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&psbt.extract_tx()).unwrap(); blockchain.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send"); 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"); 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(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&new_psbt.extract_tx()).unwrap(); blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(new_details.sent, 75_000, "incorrect sent"); assert_eq!(new_details.sent, 75_000, "incorrect sent");
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after add input"); assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after add input");
assert_eq!(new_details.received, 0, "incorrect received after add input"); assert_eq!(new_details.received, 0, "incorrect received after add input");
@ -933,13 +935,13 @@ macro_rules! bdk_blockchain_tests {
#[test] #[test]
fn test_add_data() { fn test_add_data() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None); let node_addr = test_client.get_node_address(None);
let _ = test_client.receive(testutils! { let _ = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) @tx ( (@external descriptors, 0) => 50_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
@ -952,22 +954,22 @@ macro_rules! bdk_blockchain_tests {
let tx = psbt.extract_tx(); let tx = psbt.extract_tx();
let serialized_tx = bitcoin::consensus::encode::serialize(&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"); 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)); test_client.generate(1, Some(node_addr));
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send"); 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::<std::collections::HashMap<_, _>>(); let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
let _ = tx_map.get(&sent_txid).unwrap(); let _ = tx_map.get(&tx.txid()).unwrap();
} }
#[test] #[test]
fn test_sync_receive_coinbase() { 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; let wallet_addr = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance");
test_client.generate(1, Some(wallet_addr)); test_client.generate(1, Some(wallet_addr));
@ -980,7 +982,7 @@ macro_rules! bdk_blockchain_tests {
} }
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase"); 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::jsonrpc::serde_json::Value;
use bitcoincore_rpc::{Auth, Client, RpcApi}; 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 // TODO remove once rust-bitcoincore-rpc with PR 199 released
// https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/199 // 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 ) @tx ( (@external descriptors, 0) => 50_000 )
}); });
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "wallet has incorrect balance"); 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 // 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(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "wallet cannot finalize transaction"); assert!(finalized, "wallet cannot finalize transaction");
let tx = psbt.extract_tx(); let tx = psbt.extract_tx();
wallet.broadcast(&tx).unwrap(); blockchain.broadcast(&tx).unwrap();
wallet.sync(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.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_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"); assert_eq!(wallet.list_unspent().unwrap().len(), 1, "wallet has incorrect number of unspents");
@ -1095,7 +1097,7 @@ macro_rules! bdk_blockchain_tests {
// 2. // 2.
// Core (#2) -> Us (#4) // 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 bdk_address = wallet.get_address(AddressIndex::New).unwrap().address;
let core_address = test_client.get_new_address(None, None).unwrap(); let core_address = test_client.get_new_address(None, None).unwrap();
let tx = testutils! { let tx = testutils! {
@ -1106,7 +1108,7 @@ macro_rules! bdk_blockchain_tests {
let txid_1 = test_client.receive(tx); let txid_1 = test_client.receive(tx);
let tx_1: Transaction = deserialize(&test_client.get_transaction(&txid_1, None).unwrap().hex).unwrap(); 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; 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(); 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.received, 50_000);
assert_eq!(tx_1.sent, 0); assert_eq!(tx_1.sent, 0);
@ -1117,7 +1119,7 @@ macro_rules! bdk_blockchain_tests {
}; };
let txid_2 = test_client.receive(tx); 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(); 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.received, 10_000);
assert_eq!(tx_2.sent, 0); assert_eq!(tx_2.sent, 0);

View File

@ -55,7 +55,7 @@
//! } //! }
//! //!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; //! 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)); //! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
//! //!
//! let address = wallet.get_address(New)?; //! let address = wallet.get_address(New)?;

View File

@ -30,7 +30,7 @@
//! }"#; //! }"#;
//! //!
//! let import = WalletExport::from_str(import)?; //! let import = WalletExport::from_str(import)?;
//! let wallet = Wallet::new_offline( //! let wallet = Wallet::new(
//! &import.descriptor(), //! &import.descriptor(),
//! import.change_descriptor().as_ref(), //! import.change_descriptor().as_ref(),
//! Network::Testnet, //! Network::Testnet,
@ -45,7 +45,7 @@
//! # use bdk::database::*; //! # use bdk::database::*;
//! # use bdk::wallet::export::*; //! # use bdk::wallet::export::*;
//! # use bdk::*; //! # use bdk::*;
//! let wallet = Wallet::new_offline( //! let wallet = Wallet::new(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)", //! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"), //! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
//! Network::Testnet, //! Network::Testnet,
@ -111,8 +111,8 @@ impl WalletExport {
/// ///
/// If the database is empty or `include_blockheight` is false, the `blockheight` field /// If the database is empty or `include_blockheight` is false, the `blockheight` field
/// returned will be `0`. /// returned will be `0`.
pub fn export_wallet<B, D: BatchDatabase>( pub fn export_wallet<D: BatchDatabase>(
wallet: &Wallet<B, D>, wallet: &Wallet<D>,
label: &str, label: &str,
include_blockheight: bool, include_blockheight: bool,
) -> Result<Self, &'static str> { ) -> Result<Self, &'static str> {
@ -241,7 +241,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
let wallet = Wallet::new_offline( let wallet = Wallet::new(
descriptor, descriptor,
Some(change_descriptor), Some(change_descriptor),
Network::Bitcoin, Network::Bitcoin,
@ -265,8 +265,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let wallet = let wallet = Wallet::new(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap(); WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
} }
@ -279,7 +278,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
let wallet = Wallet::new_offline( let wallet = Wallet::new(
descriptor, descriptor,
Some(change_descriptor), Some(change_descriptor),
Network::Bitcoin, Network::Bitcoin,
@ -302,7 +301,7 @@ mod test {
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\ [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
))"; ))";
let wallet = Wallet::new_offline( let wallet = Wallet::new(
descriptor, descriptor,
Some(change_descriptor), Some(change_descriptor),
Network::Testnet, Network::Testnet,
@ -322,7 +321,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
let wallet = Wallet::new_offline( let wallet = Wallet::new(
descriptor, descriptor,
Some(change_descriptor), Some(change_descriptor),
Network::Bitcoin, Network::Bitcoin,

View File

@ -55,7 +55,7 @@ use signer::{SignOptions, Signer, SignerOrdering, SignersContainer};
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams}; use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx}; use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx};
use crate::blockchain::{Blockchain, Progress}; use crate::blockchain::{GetHeight, NoopProgress, Progress, WalletSync};
use crate::database::memory::MemoryDatabase; use crate::database::memory::MemoryDatabase;
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime};
use crate::descriptor::derived::AsDerived; use crate::descriptor::derived::AsDerived;
@ -75,16 +75,17 @@ const CACHE_ADDR_BATCH_SIZE: u32 = 100;
/// A Bitcoin wallet /// A Bitcoin wallet
/// ///
/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a /// The `Wallet` struct acts as a way of coherently interfacing with output descriptors and related transactions.
/// [`blockchain`](trait@crate::blockchain::Blockchain) and implements the basic functions that a Bitcoin wallets /// Its main components are:
/// needs to operate, like [generating addresses](Wallet::get_address), [returning the balance](Wallet::get_balance),
/// [creating transactions](Wallet::build_tx), etc.
/// ///
/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided /// 1. output *descriptors* from which it can derive addresses.
/// implements [`Blockchain`], or "offline" if it is the unit type `()`. Offline wallets only expose /// 2. A [`Database`] where it tracks transactions and utxos related to the descriptors.
/// methods that don't need any interaction with the blockchain to work. /// 3. [`Signer`]s that can contribute signatures to addresses instantiated from the descriptors.
///
/// [`Database`]: crate::database::Database
/// [`Signer`]: crate::signer::Signer
#[derive(Debug)] #[derive(Debug)]
pub struct Wallet<B, D> { pub struct Wallet<D> {
descriptor: ExtendedDescriptor, descriptor: ExtendedDescriptor,
change_descriptor: Option<ExtendedDescriptor>, change_descriptor: Option<ExtendedDescriptor>,
@ -95,88 +96,11 @@ pub struct Wallet<B, D> {
network: Network, network: Network,
current_height: Option<u32>,
client: B,
database: RefCell<D>, database: RefCell<D>,
secp: SecpCtx, secp: SecpCtx,
} }
impl<D> Wallet<(), D>
where
D: BatchDatabase,
{
/// Create a new "offline" wallet
pub fn new_offline<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
database: D,
) -> Result<Self, Error> {
Self::_new(descriptor, change_descriptor, network, database, (), None)
}
}
impl<B, D> Wallet<B, D>
where
D: BatchDatabase,
{
fn _new<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
mut database: D,
client: B,
current_height: Option<u32>,
) -> Result<Self, Error> {
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 /// 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`. /// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
#[derive(Debug)] #[derive(Debug)]
@ -232,11 +156,83 @@ impl fmt::Display for AddressInfo {
} }
} }
// offline actions, always available #[derive(Debug, Default)]
impl<B, D> Wallet<B, D> /// Options to a [`sync`].
///
/// [`sync`]: Wallet::sync
pub struct SyncOptions {
/// The progress tracker which may be informed when progress is made.
pub progress: Option<Box<dyn Progress>>,
}
impl<D> Wallet<D>
where where
D: BatchDatabase, D: BatchDatabase,
{ {
#[deprecated = "Just use Wallet::new -- all wallets are offline now!"]
/// Create a new "offline" wallet
pub fn new_offline<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
database: D,
) -> Result<Self, Error> {
Self::new(descriptor, change_descriptor, network, database)
}
/// Create a wallet.
///
/// The only way this can fail is if the descriptors passed in do not match the checksums in `database`.
pub fn new<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
mut database: D,
) -> Result<Self, Error> {
let secp = Secp256k1::new();
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?;
database.check_descriptor_checksum(
KeychainKind::External,
get_checksum(&descriptor.to_string())?.as_bytes(),
)?;
let signers = Arc::new(SignersContainer::from(keymap));
let (change_descriptor, change_signers) = match change_descriptor {
Some(desc) => {
let (change_descriptor, change_keymap) =
into_wallet_descriptor_checked(desc, &secp, network)?;
database.check_descriptor_checksum(
KeychainKind::Internal,
get_checksum(&change_descriptor.to_string())?.as_bytes(),
)?;
let change_signers = Arc::new(SignersContainer::from(change_keymap));
// if !parsed.same_structure(descriptor.as_ref()) {
// return Err(Error::DifferentDescriptorStructure);
// }
(Some(change_descriptor), change_signers)
}
None => (None, Arc::new(SignersContainer::new())),
};
Ok(Wallet {
descriptor,
change_descriptor,
signers,
change_signers,
address_validators: Vec::new(),
network,
database: RefCell::new(database),
secp,
})
}
/// Get the Bitcoin network the wallet is using.
pub fn network(&self) -> Network {
self.network
}
// Return a newly derived address for the specified `keychain`. // Return a newly derived address for the specified `keychain`.
fn get_new_address(&self, keychain: KeychainKind) -> Result<AddressInfo, Error> { fn get_new_address(&self, keychain: KeychainKind) -> Result<AddressInfo, Error> {
let incremented_index = self.fetch_and_increment_index(keychain)?; let incremented_index = self.fetch_and_increment_index(keychain)?;
@ -487,7 +483,7 @@ where
/// ``` /// ```
/// ///
/// [`TxBuilder`]: crate::TxBuilder /// [`TxBuilder`]: crate::TxBuilder
pub fn build_tx(&self) -> TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, CreateTx> { pub fn build_tx(&self) -> TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, CreateTx> {
TxBuilder { TxBuilder {
wallet: self, wallet: self,
params: TxParams::default(), params: TxParams::default(),
@ -830,7 +826,7 @@ where
pub fn build_fee_bump( pub fn build_fee_bump(
&self, &self,
txid: Txid, txid: Txid,
) -> Result<TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> { ) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
let mut details = match self.database.borrow().get_tx(&txid, true)? { let mut details = match self.database.borrow().get_tx(&txid, true)? {
None => return Err(Error::TransactionNotFound), None => return Err(Error::TransactionNotFound),
Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound), Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound),
@ -1061,7 +1057,11 @@ where
.borrow() .borrow()
.get_tx(&input.previous_output.txid, false)? .get_tx(&input.previous_output.txid, false)?
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(u32::MAX)); .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!( debug!(
"Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}", "Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
@ -1512,61 +1512,35 @@ where
pub fn database(&self) -> impl std::ops::Deref<Target = D> + '_ { pub fn database(&self) -> impl std::ops::Deref<Target = D> + '_ {
self.database.borrow() self.database.borrow()
} }
}
impl<B, D> Wallet<B, D>
where
B: Blockchain,
D: BatchDatabase,
{
/// Create a new "online" wallet
#[maybe_async]
pub fn new<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
database: D,
client: B,
) -> Result<Self, Error> {
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 /// Sync the internal database with the blockchain
#[maybe_async] #[maybe_async]
pub fn sync<P: 'static + Progress>( pub fn sync<B: WalletSync + GetHeight>(
&self, &self,
progress_update: P, blockchain: &B,
max_address_param: Option<u32>, sync_opts: SyncOptions,
) -> Result<(), Error> { ) -> Result<(), Error> {
debug!("Begin sync..."); debug!("Begin sync...");
let run_setup = let SyncOptions { progress } = sync_opts;
self.ensure_addresses_cached(max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE))?; let progress = progress.unwrap_or_else(|| Box::new(NoopProgress));
let run_setup = self.ensure_addresses_cached(CACHE_ADDR_BATCH_SIZE)?;
debug!("run_setup: {}", run_setup); debug!("run_setup: {}", run_setup);
// TODO: what if i generate an address first and cache some addresses? // 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 // TODO: we should sync if generating an address triggers a new batch to be stored
if run_setup { if run_setup {
maybe_await!(self maybe_await!(
.client blockchain.wallet_setup(self.database.borrow_mut().deref_mut(), progress,)
.setup(self.database.borrow_mut().deref_mut(), progress_update,))?; )?;
} else { } else {
maybe_await!(self maybe_await!(blockchain.wallet_sync(self.database.borrow_mut().deref_mut(), progress,))?;
.client
.sync(self.database.borrow_mut().deref_mut(), progress_update,))?;
} }
let sync_time = SyncTime { let sync_time = SyncTime {
block_time: BlockTime { block_time: BlockTime {
height: maybe_await!(self.client.get_height())?, height: maybe_await!(blockchain.get_height())?,
timestamp: time::get_timestamp(), timestamp: time::get_timestamp(),
}, },
}; };
@ -1575,31 +1549,18 @@ where
Ok(()) 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<Txid, Error> {
maybe_await!(self.client.broadcast(tx))?;
Ok(tx.txid())
}
} }
/// Return a fake wallet that appears to be funded for testing. /// Return a fake wallet that appears to be funded for testing.
pub fn get_funded_wallet( pub fn get_funded_wallet(
descriptor: &str, descriptor: &str,
) -> ( ) -> (
Wallet<(), MemoryDatabase>, Wallet<MemoryDatabase>,
(String, Option<String>), (String, Option<String>),
bitcoin::Txid, bitcoin::Txid,
) { ) {
let descriptors = testutils!(@descriptors (descriptor)); let descriptors = testutils!(@descriptors (descriptor));
let wallet = Wallet::new_offline( let wallet = Wallet::new(
&descriptors.0, &descriptors.0,
None, None,
Network::Regtest, Network::Regtest,
@ -1649,7 +1610,7 @@ pub(crate) mod test {
#[test] #[test]
fn test_cache_addresses_fixed() { fn test_cache_addresses_fixed() {
let db = MemoryDatabase::new(); let db = MemoryDatabase::new();
let wallet = Wallet::new_offline( let wallet = Wallet::new(
"wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)", "wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
None, None,
Network::Testnet, Network::Testnet,
@ -1683,7 +1644,7 @@ pub(crate) mod test {
#[test] #[test]
fn test_cache_addresses() { fn test_cache_addresses() {
let db = MemoryDatabase::new(); 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!( assert_eq!(
wallet.get_address(New).unwrap().to_string(), wallet.get_address(New).unwrap().to_string(),
@ -1711,7 +1672,7 @@ pub(crate) mod test {
#[test] #[test]
fn test_cache_addresses_refill() { fn test_cache_addresses_refill() {
let db = MemoryDatabase::new(); 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!( assert_eq!(
wallet.get_address(New).unwrap().to_string(), wallet.get_address(New).unwrap().to_string(),
@ -3809,7 +3770,7 @@ pub(crate) mod test {
#[test] #[test]
fn test_unused_address() { fn test_unused_address() {
let db = MemoryDatabase::new(); let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap(); None, Network::Testnet, db).unwrap();
assert_eq!( assert_eq!(
@ -3826,7 +3787,7 @@ pub(crate) mod test {
fn test_next_unused_address() { fn test_next_unused_address() {
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
let descriptors = testutils!(@descriptors (descriptor)); let descriptors = testutils!(@descriptors (descriptor));
let wallet = Wallet::new_offline( let wallet = Wallet::new(
&descriptors.0, &descriptors.0,
None, None,
Network::Testnet, Network::Testnet,
@ -3855,7 +3816,7 @@ pub(crate) mod test {
#[test] #[test]
fn test_peek_address_at_index() { fn test_peek_address_at_index() {
let db = MemoryDatabase::new(); let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap(); None, Network::Testnet, db).unwrap();
assert_eq!( assert_eq!(
@ -3888,7 +3849,7 @@ pub(crate) mod test {
#[test] #[test]
fn test_peek_address_at_index_not_derivable() { fn test_peek_address_at_index_not_derivable() {
let db = MemoryDatabase::new(); let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)", let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
None, Network::Testnet, db).unwrap(); None, Network::Testnet, db).unwrap();
assert_eq!( assert_eq!(
@ -3910,7 +3871,7 @@ pub(crate) mod test {
#[test] #[test]
fn test_reset_address_index() { fn test_reset_address_index() {
let db = MemoryDatabase::new(); let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap(); None, Network::Testnet, db).unwrap();
// new index 0 // new index 0
@ -3947,7 +3908,7 @@ pub(crate) mod test {
#[test] #[test]
fn test_returns_index_and_address() { fn test_returns_index_and_address() {
let db = MemoryDatabase::new(); let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap(); None, Network::Testnet, db).unwrap();
// new index 0 // new index 0
@ -4020,7 +3981,7 @@ pub(crate) mod test {
fn test_get_address() { fn test_get_address() {
use crate::descriptor::template::Bip84; use crate::descriptor::template::Bip84;
let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let wallet = Wallet::new_offline( let wallet = Wallet::new(
Bip84(key, KeychainKind::External), Bip84(key, KeychainKind::External),
Some(Bip84(key, KeychainKind::Internal)), Some(Bip84(key, KeychainKind::Internal)),
Network::Regtest, Network::Regtest,
@ -4040,7 +4001,7 @@ pub(crate) mod test {
Address::from_str("bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa").unwrap() Address::from_str("bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa").unwrap()
); );
let wallet = Wallet::new_offline( let wallet = Wallet::new(
Bip84(key, KeychainKind::External), Bip84(key, KeychainKind::External),
None, None,
Network::Regtest, Network::Regtest,

View File

@ -72,7 +72,7 @@
//! let custom_signer = CustomSigner::connect(); //! let custom_signer = CustomSigner::connect();
//! //!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; //! 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( //! wallet.add_signer(
//! KeychainKind::External, //! KeychainKind::External,
//! SignerOrdering(200), //! SignerOrdering(200),

View File

@ -120,8 +120,8 @@ impl TxBuilderContext for BumpFee {}
/// [`finish`]: Self::finish /// [`finish`]: Self::finish
/// [`coin_selection`]: Self::coin_selection /// [`coin_selection`]: Self::coin_selection
#[derive(Debug)] #[derive(Debug)]
pub struct TxBuilder<'a, B, D, Cs, Ctx> { pub struct TxBuilder<'a, D, Cs, Ctx> {
pub(crate) wallet: &'a Wallet<B, D>, pub(crate) wallet: &'a Wallet<D>,
pub(crate) params: TxParams, pub(crate) params: TxParams,
pub(crate) coin_selection: Cs, pub(crate) coin_selection: Cs,
pub(crate) phantom: PhantomData<Ctx>, pub(crate) phantom: PhantomData<Ctx>,
@ -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 { fn clone(&self) -> Self {
TxBuilder { TxBuilder {
wallet: self.wallet, 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 // methods supported by both contexts, for any CoinSelectionAlgorithm
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext> impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
TxBuilder<'a, B, D, Cs, Ctx> TxBuilder<'a, D, Cs, Ctx>
{ {
/// Set a custom fee rate /// Set a custom fee rate
pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self { pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
@ -508,7 +508,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
pub fn coin_selection<P: CoinSelectionAlgorithm<D>>( pub fn coin_selection<P: CoinSelectionAlgorithm<D>>(
self, self,
coin_selection: P, coin_selection: P,
) -> TxBuilder<'a, B, D, P, Ctx> { ) -> TxBuilder<'a, D, P, Ctx> {
TxBuilder { TxBuilder {
wallet: self.wallet, wallet: self.wallet,
params: self.params, params: self.params,
@ -547,7 +547,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
} }
} }
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D, Cs, CreateTx> { impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, CreateTx> {
/// Replace the recipients already added with a new list /// Replace the recipients already added with a new list
pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self { pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self {
self.params.recipients = recipients; self.params.recipients = recipients;
@ -614,7 +614,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
} }
// methods supported only by bump_fee // 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 /// 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 /// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
/// will attempt to find a change output to shrink instead. /// will attempt to find a change output to shrink instead.

View File

@ -17,7 +17,7 @@ use std::fmt;
use bitcoin::consensus::serialize; use bitcoin::consensus::serialize;
use bitcoin::{OutPoint, Transaction, Txid}; use bitcoin::{OutPoint, Transaction, Txid};
use crate::blockchain::Blockchain; use crate::blockchain::GetTx;
use crate::database::Database; use crate::database::Database;
use crate::error::Error; use crate::error::Error;
@ -29,7 +29,7 @@ use crate::error::Error;
/// Depending on the [capabilities](crate::blockchain::Blockchain::get_capabilities) of the /// Depending on the [capabilities](crate::blockchain::Blockchain::get_capabilities) of the
/// [`Blockchain`] backend, the method could fail when called with old "historical" transactions or /// [`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. /// with unconfirmed transactions that have been evicted from the backend's memory.
pub fn verify_tx<D: Database, B: Blockchain>( pub fn verify_tx<D: Database, B: GetTx>(
tx: &Transaction, tx: &Transaction,
database: &D, database: &D,
blockchain: &B, blockchain: &B,
@ -104,43 +104,18 @@ impl_error!(bitcoinconsensus::Error, Consensus, VerifyError);
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::collections::HashSet; use super::*;
use crate::database::{BatchOperations, MemoryDatabase};
use bitcoin::consensus::encode::deserialize; use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::hex::FromHex;
use bitcoin::{Transaction, Txid}; use bitcoin::{Transaction, Txid};
use crate::blockchain::{Blockchain, Capability, Progress};
use crate::database::{BatchDatabase, BatchOperations, MemoryDatabase};
use crate::FeeRate;
use super::*;
struct DummyBlockchain; struct DummyBlockchain;
impl Blockchain for DummyBlockchain { impl GetTx for DummyBlockchain {
fn get_capabilities(&self) -> HashSet<Capability> {
Default::default()
}
fn setup<D: BatchDatabase, P: 'static + Progress>(
&self,
_database: &mut D,
_progress_update: P,
) -> Result<(), Error> {
Ok(())
}
fn get_tx(&self, _txid: &Txid) -> Result<Option<Transaction>, Error> { fn get_tx(&self, _txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(None) Ok(None)
} }
fn broadcast(&self, _tx: &Transaction) -> Result<(), Error> {
Ok(())
}
fn get_height(&self) -> Result<u32, Error> {
Ok(42)
}
fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
Ok(FeeRate::default_min_relay_fee())
}
} }
#[test] #[test]