From c23d2bcaff3c24aafe73e0c4ca11f69053cebb9a Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 1 Jun 2024 00:06:20 -0500 Subject: [PATCH] refactor(persist): update file_store, sqlite, wallet to use bdk_chain::persist Also update examples and remove bdk_persist crate. --- Cargo.toml | 1 - crates/chain/src/keychain/txout_index.rs | 2 +- crates/chain/src/persist.rs | 64 +-- crates/file_store/Cargo.toml | 4 +- crates/file_store/README.md | 2 +- crates/file_store/src/store.rs | 13 +- crates/hwi/src/lib.rs | 2 +- crates/persist/Cargo.toml | 22 - crates/persist/README.md | 5 - crates/persist/src/changeset.rs | 73 --- crates/persist/src/lib.rs | 8 - crates/persist/src/persist.rs | 106 ---- crates/sqlite/Cargo.toml | 4 +- crates/sqlite/README.md | 6 +- crates/sqlite/src/store.rs | 39 +- crates/wallet/Cargo.toml | 4 +- crates/wallet/README.md | 20 +- crates/wallet/examples/compiler.rs | 4 +- crates/wallet/src/descriptor/template.rs | 51 +- crates/wallet/src/wallet/coin_selection.rs | 1 - crates/wallet/src/wallet/error.rs | 9 - crates/wallet/src/wallet/export.rs | 6 +- crates/wallet/src/wallet/hardwaresigner.rs | 2 +- crates/wallet/src/wallet/mod.rs | 502 ++++++++---------- crates/wallet/src/wallet/signer.rs | 2 +- crates/wallet/src/wallet/tx_builder.rs | 6 +- crates/wallet/tests/common.rs | 2 +- crates/wallet/tests/wallet.rs | 320 +++++------ example-crates/example_cli/Cargo.toml | 1 - example-crates/example_cli/src/lib.rs | 26 +- example-crates/wallet_electrum/src/main.rs | 15 +- .../wallet_esplora_async/src/main.rs | 11 +- .../wallet_esplora_blocking/src/main.rs | 11 +- example-crates/wallet_rpc/src/main.rs | 16 +- 34 files changed, 556 insertions(+), 804 deletions(-) delete mode 100644 crates/persist/Cargo.toml delete mode 100644 crates/persist/README.md delete mode 100644 crates/persist/src/changeset.rs delete mode 100644 crates/persist/src/lib.rs delete mode 100644 crates/persist/src/persist.rs diff --git a/Cargo.toml b/Cargo.toml index ea1b8f5048..201bb21f3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ members = [ "crates/esplora", "crates/bitcoind_rpc", "crates/hwi", - "crates/persist", "crates/testenv", "example-crates/example_cli", "example-crates/example_electrum", diff --git a/crates/chain/src/keychain/txout_index.rs b/crates/chain/src/keychain/txout_index.rs index 994553b3f5..f538913d9b 100644 --- a/crates/chain/src/keychain/txout_index.rs +++ b/crates/chain/src/keychain/txout_index.rs @@ -308,7 +308,7 @@ impl KeychainTxOutIndex { /// Return a reference to the internal [`SpkTxOutIndex`]. /// - /// **WARNING:** The internal index will contain lookahead spks. Refer to + /// **WARNING**: The internal index will contain lookahead spks. Refer to /// [struct-level docs](KeychainTxOutIndex) for more about `lookahead`. pub fn inner(&self) -> &SpkTxOutIndex<(DescriptorId, u32)> { &self.inner diff --git a/crates/chain/src/persist.rs b/crates/chain/src/persist.rs index a953e724f5..5d4b39e012 100644 --- a/crates/chain/src/persist.rs +++ b/crates/chain/src/persist.rs @@ -1,7 +1,7 @@ -//! This module is home to the [`Persist`] trait which defines the behavior of a data store +//! This module is home to the [`PersistBackend`] trait which defines the behavior of a data store //! required to persist changes made to BDK data structures. //! -//! The [`StagedPersist`] type provides a convenient wrapper around implementations of [`Persist`] that +//! The [`StagedPersistBackend`] type provides a convenient wrapper around implementations of [`PersistBackend`] that //! allows changes to be staged before committing them. //! //! The [`CombinedChangeSet`] type encapsulates a combination of [`crate`] structures that are @@ -92,7 +92,7 @@ impl From>> /// /// `C` represents the changeset; a datatype that records changes made to in-memory data structures /// that are to be persisted, or retrieved from persistence. -pub trait Persist { +pub trait PersistBackend { /// The error the backend returns when it fails to write. type WriteError: Debug + Display; @@ -113,7 +113,7 @@ pub trait Persist { fn load_changes(&mut self) -> Result, Self::LoadError>; } -impl Persist for () { +impl PersistBackend for () { type WriteError = Infallible; type LoadError = Infallible; @@ -132,7 +132,7 @@ impl Persist for () { /// `C` represents the changeset; a datatype that records changes made to in-memory data structures /// that are to be persisted, or retrieved from persistence. #[async_trait] -pub trait PersistAsync { +pub trait PersistBackendAsync { /// The error the backend returns when it fails to write. type WriteError: Debug + Display; @@ -155,7 +155,7 @@ pub trait PersistAsync { #[cfg(feature = "async")] #[async_trait] -impl PersistAsync for () { +impl PersistBackendAsync for () { type WriteError = Infallible; type LoadError = Infallible; @@ -168,17 +168,17 @@ impl PersistAsync for () { } } -/// `StagedPersist` adds a convenient staging area for changesets before they are persisted. +/// `StagedPersistBackend` adds a convenient staging area for changesets before they are persisted. /// /// Not all changes to the in-memory representation needs to be written to disk right away, so -/// [`crate::persist::StagedPersist::stage`] can be used to *stage* changes first and then -/// [`crate::persist::StagedPersist::commit`] can be used to write changes to disk. -pub struct StagedPersist> { +/// [`crate::persist::StagedPersistBackend::stage`] can be used to *stage* changes first and then +/// [`crate::persist::StagedPersistBackend::commit`] can be used to write changes to disk. +pub struct StagedPersistBackend> { inner: P, stage: C, } -impl> Persist for StagedPersist { +impl> PersistBackend for StagedPersistBackend { type WriteError = P::WriteError; type LoadError = P::LoadError; @@ -191,16 +191,16 @@ impl> Persist for StagedPersist { } } -impl StagedPersist +impl StagedPersistBackend where C: Default + Append, - P: Persist, + P: PersistBackend, { - /// Create a new [`StagedPersist`] adding staging to an inner data store that implements - /// [`Persist`]. - pub fn new(persist: P) -> Self { + /// Create a new [`StagedPersistBackend`] adding staging to an inner data store that implements + /// [`PersistBackend`]. + pub fn new(persist_backend: P) -> Self { Self { - inner: persist, + inner: persist_backend, stage: Default::default(), } } @@ -258,16 +258,18 @@ where /// `StagedPersistAsync` adds a convenient async staging area for changesets before they are persisted. /// /// Not all changes to the in-memory representation needs to be written to disk right away, so -/// [`StagedPersistAsync::stage`] can be used to *stage* changes first and then -/// [`StagedPersistAsync::commit`] can be used to write changes to disk. -pub struct StagedPersistAsync> { +/// [`StagedPersistBackendAsync::stage`] can be used to *stage* changes first and then +/// [`StagedPersistBackendAsync::commit`] can be used to write changes to disk. +pub struct StagedPersistBackendAsync> { inner: P, staged: C, } #[cfg(feature = "async")] #[async_trait] -impl + Send> PersistAsync for StagedPersistAsync { +impl + Send> PersistBackendAsync + for StagedPersistBackendAsync +{ type WriteError = P::WriteError; type LoadError = P::LoadError; @@ -281,16 +283,16 @@ impl + Send> PersistAsync for StagedPersis } #[cfg(feature = "async")] -impl StagedPersistAsync +impl StagedPersistBackendAsync where C: Default + Append + Send + Sync, - P: PersistAsync + Send, + P: PersistBackendAsync + Send, { - /// Create a new [`StagedPersistAsync`] adding staging to an inner data store that implements - /// [`PersistAsync`]. - pub fn new(persist: P) -> Self { + /// Create a new [`StagedPersistBackendAsync`] adding staging to an inner data store that implements + /// [`PersistBackendAsync`]. + pub fn new(persist_backend: P) -> Self { Self { - inner: persist, + inner: persist_backend, staged: Default::default(), } } @@ -349,7 +351,7 @@ where mod test { extern crate core; - use crate::persist::{Persist, StagedPersist}; + use crate::persist::{PersistBackend, StagedPersistBackend}; use crate::Append; use std::error::Error; use std::fmt::{self, Display, Formatter}; @@ -395,7 +397,7 @@ mod test { } } - impl Persist for TestBackend + impl PersistBackend for TestBackend where C: Default + Append + Clone + ToString, { @@ -426,7 +428,7 @@ mod test { changeset: TestChangeSet(None), }; - let mut staged_backend = StagedPersist::new(backend); + let mut staged_backend = StagedPersistBackend::new(backend); staged_backend.stage(TestChangeSet(Some("ONE".to_string()))); staged_backend.stage(TestChangeSet(None)); staged_backend.stage(TestChangeSet(Some("TWO".to_string()))); @@ -446,7 +448,7 @@ mod test { let backend = TestBackend { changeset: TestChangeSet(None), }; - let mut staged_backend = StagedPersist::new(backend); + let mut staged_backend = StagedPersistBackend::new(backend); staged_backend.stage(TestChangeSet(Some("ERROR".to_string()))); let result = staged_backend.commit(); assert!(matches!(result, Err(e) if e == FailedWrite)); diff --git a/crates/file_store/Cargo.toml b/crates/file_store/Cargo.toml index 09b16c140b..392424cb38 100644 --- a/crates/file_store/Cargo.toml +++ b/crates/file_store/Cargo.toml @@ -5,15 +5,13 @@ edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/bitcoindevkit/bdk" documentation = "https://docs.rs/bdk_file_store" -description = "A simple append-only flat file implementation of Persist for Bitcoin Dev Kit." +description = "A simple append-only flat file implementation of PersistBackend for Bitcoin Dev Kit." keywords = ["bitcoin", "persist", "persistence", "bdk", "file"] authors = ["Bitcoin Dev Kit Developers"] readme = "README.md" [dependencies] -anyhow = { version = "1", default-features = false } bdk_chain = { path = "../chain", version = "0.15.0", features = [ "serde", "miniscript" ] } -bdk_persist = { path = "../persist", version = "0.3.0"} bincode = { version = "1" } serde = { version = "1", features = ["derive"] } diff --git a/crates/file_store/README.md b/crates/file_store/README.md index 58b572ce00..cad196b2dd 100644 --- a/crates/file_store/README.md +++ b/crates/file_store/README.md @@ -1,6 +1,6 @@ # BDK File Store -This is a simple append-only flat file implementation of [`PersistBackend`](bdk_persist::PersistBackend). +This is a simple append-only flat file implementation of [`PersistBackend`](bdk_chain::persist::PersistBackend). The main structure is [`Store`] which works with any [`bdk_chain`] based changesets to persist data into a flat file. diff --git a/crates/file_store/src/store.rs b/crates/file_store/src/store.rs index 6cea927657..2f451db722 100644 --- a/crates/file_store/src/store.rs +++ b/crates/file_store/src/store.rs @@ -1,7 +1,6 @@ use crate::{bincode_options, EntryIter, FileError, IterError}; -use anyhow::anyhow; +use bdk_chain::persist::PersistBackend; use bdk_chain::Append; -use bdk_persist::PersistBackend; use bincode::Options; use std::{ fmt::{self, Debug}, @@ -25,19 +24,21 @@ where impl PersistBackend for Store where C: Append + + Debug + serde::Serialize + serde::de::DeserializeOwned + core::marker::Send + core::marker::Sync, { - fn write_changes(&mut self, changeset: &C) -> anyhow::Result<()> { + type WriteError = io::Error; + type LoadError = AggregateChangesetsError; + + fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> { self.append_changeset(changeset) - .map_err(|e| anyhow!(e).context("failed to write changes to persistence backend")) } - fn load_from_persistence(&mut self) -> anyhow::Result> { + fn load_changes(&mut self) -> Result, Self::LoadError> { self.aggregate_changesets() - .map_err(|e| anyhow!(e.iter_error).context("error loading from persistence backend")) } } diff --git a/crates/hwi/src/lib.rs b/crates/hwi/src/lib.rs index 762af8ff11..73a8fc5396 100644 --- a/crates/hwi/src/lib.rs +++ b/crates/hwi/src/lib.rs @@ -18,7 +18,7 @@ //! let first_device = devices.remove(0)?; //! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?; //! -//! # let mut wallet = Wallet::new_no_persist( +//! # let mut wallet = Wallet::new( //! # "", //! # "", //! # Network::Testnet, diff --git a/crates/persist/Cargo.toml b/crates/persist/Cargo.toml deleted file mode 100644 index 446eea4afd..0000000000 --- a/crates/persist/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "bdk_persist" -homepage = "https://bitcoindevkit.org" -version = "0.3.0" -repository = "https://github.com/bitcoindevkit/bdk" -documentation = "https://docs.rs/bdk_persist" -description = "Types that define data persistence of a BDK wallet" -keywords = ["bitcoin", "wallet", "persistence", "database"] -readme = "README.md" -license = "MIT OR Apache-2.0" -authors = ["Bitcoin Dev Kit Developers"] -edition = "2021" -rust-version = "1.63" - -[dependencies] -anyhow = { version = "1", default-features = false } -bdk_chain = { path = "../chain", version = "0.15.0", default-features = false } - -[features] -default = ["bdk_chain/std", "miniscript"] -serde = ["bdk_chain/serde"] -miniscript = ["bdk_chain/miniscript"] diff --git a/crates/persist/README.md b/crates/persist/README.md deleted file mode 100644 index c82354860a..0000000000 --- a/crates/persist/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# BDK Persist - -This crate is home to the [`PersistBackend`] trait which defines the behavior of a database to perform the task of persisting changes made to BDK data structures. - -The [`Persist`] type provides a convenient wrapper around a [`PersistBackend`] that allows staging changes before committing them. diff --git a/crates/persist/src/changeset.rs b/crates/persist/src/changeset.rs deleted file mode 100644 index b796b07f0e..0000000000 --- a/crates/persist/src/changeset.rs +++ /dev/null @@ -1,73 +0,0 @@ -#![cfg(feature = "miniscript")] - -use bdk_chain::{bitcoin::Network, indexed_tx_graph, keychain, local_chain, Anchor, Append}; - -/// Changes from a combination of [`bdk_chain`] structures. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr( - feature = "serde", - derive(bdk_chain::serde::Deserialize, bdk_chain::serde::Serialize), - serde( - crate = "bdk_chain::serde", - bound( - deserialize = "A: Ord + bdk_chain::serde::Deserialize<'de>, K: Ord + bdk_chain::serde::Deserialize<'de>", - serialize = "A: Ord + bdk_chain::serde::Serialize, K: Ord + bdk_chain::serde::Serialize", - ), - ) -)] -pub struct CombinedChangeSet { - /// Changes to the [`LocalChain`](local_chain::LocalChain). - pub chain: local_chain::ChangeSet, - /// Changes to [`IndexedTxGraph`](indexed_tx_graph::IndexedTxGraph). - pub indexed_tx_graph: indexed_tx_graph::ChangeSet>, - /// Stores the network type of the transaction data. - pub network: Option, -} - -impl Default for CombinedChangeSet { - fn default() -> Self { - Self { - chain: Default::default(), - indexed_tx_graph: Default::default(), - network: None, - } - } -} - -impl Append for CombinedChangeSet { - fn append(&mut self, other: Self) { - Append::append(&mut self.chain, other.chain); - Append::append(&mut self.indexed_tx_graph, other.indexed_tx_graph); - if other.network.is_some() { - debug_assert!( - self.network.is_none() || self.network == other.network, - "network type must either be just introduced or remain the same" - ); - self.network = other.network; - } - } - - fn is_empty(&self) -> bool { - self.chain.is_empty() && self.indexed_tx_graph.is_empty() && self.network.is_none() - } -} - -impl From for CombinedChangeSet { - fn from(chain: local_chain::ChangeSet) -> Self { - Self { - chain, - ..Default::default() - } - } -} - -impl From>> - for CombinedChangeSet -{ - fn from(indexed_tx_graph: indexed_tx_graph::ChangeSet>) -> Self { - Self { - indexed_tx_graph, - ..Default::default() - } - } -} diff --git a/crates/persist/src/lib.rs b/crates/persist/src/lib.rs deleted file mode 100644 index e3824e1adc..0000000000 --- a/crates/persist/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![doc = include_str!("../README.md")] -#![no_std] -#![warn(missing_docs)] - -mod changeset; -mod persist; -pub use changeset::*; -pub use persist::*; diff --git a/crates/persist/src/persist.rs b/crates/persist/src/persist.rs deleted file mode 100644 index 5d9df3bf6d..0000000000 --- a/crates/persist/src/persist.rs +++ /dev/null @@ -1,106 +0,0 @@ -extern crate alloc; -use alloc::boxed::Box; -use bdk_chain::Append; -use core::fmt; - -/// `Persist` wraps a [`PersistBackend`] to create a convenient staging area for changes (`C`) -/// before they are persisted. -/// -/// Not all changes to the in-memory representation needs to be written to disk right away, so -/// [`Persist::stage`] can be used to *stage* changes first and then [`Persist::commit`] can be used -/// to write changes to disk. -pub struct Persist { - backend: Box + Send + Sync>, - stage: C, -} - -impl fmt::Debug for Persist { - fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { - write!(fmt, "{:?}", self.stage)?; - Ok(()) - } -} - -impl Persist -where - C: Default + Append, -{ - /// Create a new [`Persist`] from [`PersistBackend`]. - pub fn new(backend: impl PersistBackend + Send + Sync + 'static) -> Self { - let backend = Box::new(backend); - Self { - backend, - stage: Default::default(), - } - } - - /// Stage a `changeset` to be committed later with [`commit`]. - /// - /// [`commit`]: Self::commit - pub fn stage(&mut self, changeset: C) { - self.stage.append(changeset) - } - - /// Get the changes that have not been committed yet. - pub fn staged(&self) -> &C { - &self.stage - } - - /// Commit the staged changes to the underlying persistence backend. - /// - /// Changes that are committed (if any) are returned. - /// - /// # Error - /// - /// Returns a backend-defined error if this fails. - pub fn commit(&mut self) -> anyhow::Result> { - if self.stage.is_empty() { - return Ok(None); - } - self.backend - .write_changes(&self.stage) - // if written successfully, take and return `self.stage` - .map(|_| Some(core::mem::take(&mut self.stage))) - } - - /// Stages a new changeset and commits it (along with any other previously staged changes) to - /// the persistence backend - /// - /// Convenience method for calling [`stage`] and then [`commit`]. - /// - /// [`stage`]: Self::stage - /// [`commit`]: Self::commit - pub fn stage_and_commit(&mut self, changeset: C) -> anyhow::Result> { - self.stage(changeset); - self.commit() - } -} - -/// A persistence backend for [`Persist`]. -/// -/// `C` represents the changeset; a datatype that records changes made to in-memory data structures -/// that are to be persisted, or retrieved from persistence. -pub trait PersistBackend { - /// Writes a changeset to the persistence backend. - /// - /// It is up to the backend what it does with this. It could store every changeset in a list or - /// it inserts the actual changes into a more structured database. All it needs to guarantee is - /// that [`load_from_persistence`] restores a keychain tracker to what it should be if all - /// changesets had been applied sequentially. - /// - /// [`load_from_persistence`]: Self::load_from_persistence - fn write_changes(&mut self, changeset: &C) -> anyhow::Result<()>; - - /// Return the aggregate changeset `C` from persistence. - fn load_from_persistence(&mut self) -> anyhow::Result>; -} - -impl PersistBackend for () { - fn write_changes(&mut self, _changeset: &C) -> anyhow::Result<()> { - Ok(()) - } - - fn load_from_persistence(&mut self) -> anyhow::Result> { - Ok(None) - } -} diff --git a/crates/sqlite/Cargo.toml b/crates/sqlite/Cargo.toml index fbbfc374f6..238c818f73 100644 --- a/crates/sqlite/Cargo.toml +++ b/crates/sqlite/Cargo.toml @@ -5,15 +5,13 @@ edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/bitcoindevkit/bdk" documentation = "https://docs.rs/bdk_sqlite" -description = "A simple SQLite based implementation of Persist for Bitcoin Dev Kit." +description = "A simple SQLite based implementation of PersistBackend for Bitcoin Dev Kit." keywords = ["bitcoin", "persist", "persistence", "bdk", "sqlite"] authors = ["Bitcoin Dev Kit Developers"] readme = "README.md" [dependencies] -anyhow = { version = "1", default-features = false } bdk_chain = { path = "../chain", version = "0.15.0", features = ["serde", "miniscript"] } -bdk_persist = { path = "../persist", version = "0.3.0", features = ["serde"] } rusqlite = { version = "0.31.0", features = ["bundled"] } serde = { version = "1", features = ["derive"] } serde_json = "1" \ No newline at end of file diff --git a/crates/sqlite/README.md b/crates/sqlite/README.md index 4136656f9c..26f4650bc3 100644 --- a/crates/sqlite/README.md +++ b/crates/sqlite/README.md @@ -1,8 +1,8 @@ # BDK SQLite -This is a simple [SQLite] relational database schema backed implementation of [`PersistBackend`](bdk_persist::PersistBackend). +This is a simple [SQLite] relational database schema backed implementation of `PersistBackend`. -The main structure is `Store` which persists [`bdk_persist`] `CombinedChangeSet` data into a SQLite database file. +The main structure is `Store` which persists `CombinedChangeSet` data into a SQLite database file. -[`bdk_persist`]:https://docs.rs/bdk_persist/latest/bdk_persist/ + [SQLite]: https://www.sqlite.org/index.html diff --git a/crates/sqlite/src/store.rs b/crates/sqlite/src/store.rs index beeb9e0aa9..a217d5d8cb 100644 --- a/crates/sqlite/src/store.rs +++ b/crates/sqlite/src/store.rs @@ -12,10 +12,10 @@ use std::str::FromStr; use std::sync::{Arc, Mutex}; use crate::Error; +use bdk_chain::persist::{CombinedChangeSet, PersistBackend}; use bdk_chain::{ indexed_tx_graph, keychain, local_chain, tx_graph, Anchor, Append, DescriptorExt, DescriptorId, }; -use bdk_persist::CombinedChangeSet; /// Persists data in to a relational schema based [SQLite] database file. /// @@ -57,21 +57,23 @@ where } } -impl bdk_persist::PersistBackend for Store +impl PersistBackend> for Store where K: Ord + for<'de> Deserialize<'de> + Serialize + Send, A: Anchor + for<'de> Deserialize<'de> + Serialize + Send, - C: Clone + From> + Into>, { - fn write_changes(&mut self, changeset: &C) -> anyhow::Result<()> { - self.write(&changeset.clone().into()) - .map_err(|e| anyhow::anyhow!(e).context("unable to write changes to sqlite database")) + type WriteError = Error; + type LoadError = Error; + + fn write_changes( + &mut self, + changeset: &CombinedChangeSet, + ) -> Result<(), Self::WriteError> { + self.write(changeset) } - fn load_from_persistence(&mut self) -> anyhow::Result> { + fn load_changes(&mut self) -> Result>, Self::LoadError> { self.read() - .map(|c| c.map(Into::into)) - .map_err(|e| anyhow::anyhow!(e).context("unable to read changes from sqlite database")) } } @@ -561,11 +563,11 @@ mod test { use bdk_chain::bitcoin::Network::Testnet; use bdk_chain::bitcoin::{secp256k1, BlockHash, OutPoint}; use bdk_chain::miniscript::Descriptor; + use bdk_chain::persist::{CombinedChangeSet, PersistBackend}; use bdk_chain::{ indexed_tx_graph, keychain, tx_graph, BlockId, ConfirmationHeightAnchor, ConfirmationTimeHeightAnchor, DescriptorExt, }; - use bdk_persist::PersistBackend; use std::str::FromStr; use std::sync::Arc; @@ -576,8 +578,7 @@ mod test { } #[test] - fn insert_and_load_aggregate_changesets_with_confirmation_time_height_anchor( - ) -> anyhow::Result<()> { + fn insert_and_load_aggregate_changesets_with_confirmation_time_height_anchor() { let (test_changesets, agg_test_changesets) = create_test_changesets(&|height, time, hash| ConfirmationTimeHeightAnchor { confirmation_height: height, @@ -593,15 +594,13 @@ mod test { store.write_changes(changeset).expect("write changeset"); }); - let agg_changeset = store.load_from_persistence().expect("aggregated changeset"); + let agg_changeset = store.load_changes().expect("aggregated changeset"); assert_eq!(agg_changeset, Some(agg_test_changesets)); - Ok(()) } #[test] - fn insert_and_load_aggregate_changesets_with_confirmation_height_anchor() -> anyhow::Result<()> - { + fn insert_and_load_aggregate_changesets_with_confirmation_height_anchor() { let (test_changesets, agg_test_changesets) = create_test_changesets(&|height, _time, hash| ConfirmationHeightAnchor { confirmation_height: height, @@ -616,14 +615,13 @@ mod test { store.write_changes(changeset).expect("write changeset"); }); - let agg_changeset = store.load_from_persistence().expect("aggregated changeset"); + let agg_changeset = store.load_changes().expect("aggregated changeset"); assert_eq!(agg_changeset, Some(agg_test_changesets)); - Ok(()) } #[test] - fn insert_and_load_aggregate_changesets_with_blockid_anchor() -> anyhow::Result<()> { + fn insert_and_load_aggregate_changesets_with_blockid_anchor() { let (test_changesets, agg_test_changesets) = create_test_changesets(&|height, _time, hash| BlockId { height, hash }); @@ -634,10 +632,9 @@ mod test { store.write_changes(changeset).expect("write changeset"); }); - let agg_changeset = store.load_from_persistence().expect("aggregated changeset"); + let agg_changeset = store.load_changes().expect("aggregated changeset"); assert_eq!(agg_changeset, Some(agg_test_changesets)); - Ok(()) } fn create_test_changesets( diff --git a/crates/wallet/Cargo.toml b/crates/wallet/Cargo.toml index 2a129e9595..fa9b118e1c 100644 --- a/crates/wallet/Cargo.toml +++ b/crates/wallet/Cargo.toml @@ -13,14 +13,12 @@ edition = "2021" rust-version = "1.63" [dependencies] -anyhow = { version = "1", default-features = false } rand = "^0.8" miniscript = { version = "11.0.0", features = ["serde"], default-features = false } bitcoin = { version = "0.31.0", features = ["serde", "base64", "rand-std"], default-features = false } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1.0" } -bdk_chain = { path = "../chain", version = "0.15.0", features = ["miniscript", "serde"], default-features = false } -bdk_persist = { path = "../persist", version = "0.3.0", features = ["miniscript", "serde"], default-features = false } +bdk_chain = { path = "../chain", version = "0.15.0", features = ["miniscript", "serde", "persist"], default-features = false } # Optional dependencies bip39 = { version = "2.0", optional = true } diff --git a/crates/wallet/README.md b/crates/wallet/README.md index 73ff4b58da..6816f6ab48 100644 --- a/crates/wallet/README.md +++ b/crates/wallet/README.md @@ -57,11 +57,12 @@ that the `Wallet` can use to update its view of the chain. ## Persistence -To persist the `Wallet` on disk, it must be constructed with a [`PersistBackend`] implementation. +To persist `Wallet` state data on disk use an implementation of the [`PersistBackend`] trait. See also the [`StagedPersistBackend`] type. **Implementations** * [`bdk_file_store`]: A simple flat-file implementation of [`PersistBackend`]. +* [`bdk_sqlite`]: A simple sqlite implementation of [`PersistBackend`]. **Example** @@ -74,12 +75,13 @@ fn main() { let db = bdk_file_store::Store::::open_or_create_new(b"magic_bytes", "path/to/my_wallet.db").expect("create store"); let descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)"; - let change_descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/1/*)"; - let mut wallet = Wallet::new_or_load(descriptor, change_descriptor, db, Network::Testnet).expect("create or load wallet"); + let change_descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/1/*)"; + let changeset = db.load_changes().expect("changeset loaded"); + let mut wallet = Wallet::new_or_load(descriptor, change_descriptor, changeset, Network::Testnet).expect("create or load wallet"); // Insert a single `TxOut` at `OutPoint` into the wallet. let _ = wallet.insert_txout(outpoint, txout); - wallet.commit().expect("must write to database"); + db.write_changes(&wallet.take_staged()).expect("must write to database"); } ``` @@ -115,7 +117,7 @@ fn main() { - + @@ -144,7 +146,7 @@ fn main() { - + @@ -180,7 +182,7 @@ fn main() { - + @@ -220,9 +222,11 @@ license, shall be dual licensed as above, without any additional terms or conditions. [`Wallet`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/wallet/struct.Wallet.html -[`PersistBackend`]: https://docs.rs/bdk_chain/latest/bdk_chain/trait.PersistBackend.html +[`PersistBackend`]: https://docs.rs/bdk_chain/latest/bdk_chain/persist/trait.PersistBackend.html +[`StagedPersistBackend`]: https://docs.rs/bdk_chain/latest/bdk_chain/persist/struct.StagedPersistBackend.html [`bdk_chain`]: https://docs.rs/bdk_chain/latest [`bdk_file_store`]: https://docs.rs/bdk_file_store/latest +[`bdk_sqlite`]: https://docs.rs/bdk_sqlite/latest [`bdk_electrum`]: https://docs.rs/bdk_electrum/latest [`bdk_esplora`]: https://docs.rs/bdk_esplora/latest [`bdk_bitcoind_rpc`]: https://docs.rs/bdk_bitcoind_rpc/latest diff --git a/crates/wallet/examples/compiler.rs b/crates/wallet/examples/compiler.rs index a11899f475..13b905ad9c 100644 --- a/crates/wallet/examples/compiler.rs +++ b/crates/wallet/examples/compiler.rs @@ -77,11 +77,11 @@ fn main() -> Result<(), Box> { ); // Create a new wallet from descriptors - let mut wallet = Wallet::new_no_persist(&descriptor, &internal_descriptor, Network::Regtest)?; + let mut wallet = Wallet::new(&descriptor, &internal_descriptor, Network::Regtest)?; println!( "First derived address from the descriptor: \n{}", - wallet.next_unused_address(KeychainKind::External)?, + wallet.next_unused_address(KeychainKind::External), ); // BDK also has it's own `Policy` structure to represent the spending condition in a more diff --git a/crates/wallet/src/descriptor/template.rs b/crates/wallet/src/descriptor/template.rs index 61d6165b94..aaa7f247a9 100644 --- a/crates/wallet/src/descriptor/template.rs +++ b/crates/wallet/src/descriptor/template.rs @@ -81,12 +81,11 @@ impl IntoWalletDescriptor for T { /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// let key_internal = /// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; -/// let mut wallet = -/// Wallet::new_no_persist(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?; +/// let mut wallet = Wallet::new(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?; /// /// assert_eq!( /// wallet -/// .next_unused_address(KeychainKind::External)? +/// .next_unused_address(KeychainKind::External) /// .to_string(), /// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT" /// ); @@ -114,7 +113,7 @@ impl> DescriptorTemplate for P2Pkh { /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// let key_internal = /// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; -/// let mut wallet = Wallet::new_no_persist( +/// let mut wallet = Wallet::new( /// P2Wpkh_P2Sh(key_external), /// P2Wpkh_P2Sh(key_internal), /// Network::Testnet, @@ -122,7 +121,7 @@ impl> DescriptorTemplate for P2Pkh { /// /// assert_eq!( /// wallet -/// .next_unused_address(KeychainKind::External)? +/// .next_unused_address(KeychainKind::External) /// .to_string(), /// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5" /// ); @@ -151,12 +150,11 @@ impl> DescriptorTemplate for P2Wpkh_P2Sh { /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// let key_internal = /// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; -/// let mut wallet = -/// Wallet::new_no_persist(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?; +/// let mut wallet = Wallet::new(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?; /// /// assert_eq!( /// wallet -/// .next_unused_address(KeychainKind::External)? +/// .next_unused_address(KeychainKind::External) /// .to_string(), /// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd" /// ); @@ -184,12 +182,11 @@ impl> DescriptorTemplate for P2Wpkh { /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// let key_internal = /// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; -/// let mut wallet = -/// Wallet::new_no_persist(P2TR(key_external), P2TR(key_internal), Network::Testnet)?; +/// let mut wallet = Wallet::new(P2TR(key_external), P2TR(key_internal), Network::Testnet)?; /// /// assert_eq!( /// wallet -/// .next_unused_address(KeychainKind::External)? +/// .next_unused_address(KeychainKind::External) /// .to_string(), /// "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46" /// ); @@ -218,13 +215,13 @@ impl> DescriptorTemplate for P2TR { /// use bdk_wallet::template::Bip44; /// /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::new_no_persist( +/// let mut wallet = Wallet::new( /// Bip44(key.clone(), KeychainKind::External), /// Bip44(key, KeychainKind::Internal), /// Network::Testnet, /// )?; /// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89"); +/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt"); /// # Ok::<_, Box>(()) /// ``` @@ -255,13 +252,13 @@ impl> DescriptorTemplate for Bip44 { /// /// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::new_no_persist( +/// let mut wallet = Wallet::new( /// Bip44Public(key.clone(), fingerprint, KeychainKind::External), /// Bip44Public(key, fingerprint, KeychainKind::Internal), /// Network::Testnet, /// )?; /// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); +/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz"); /// # Ok::<_, Box>(()) /// ``` @@ -291,13 +288,13 @@ impl> DescriptorTemplate for Bip44Public { /// use bdk_wallet::template::Bip49; /// /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::new_no_persist( +/// let mut wallet = Wallet::new( /// Bip49(key.clone(), KeychainKind::External), /// Bip49(key, KeychainKind::Internal), /// Network::Testnet, /// )?; /// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB"); +/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e"); /// # Ok::<_, Box>(()) /// ``` @@ -328,13 +325,13 @@ impl> DescriptorTemplate for Bip49 { /// /// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::new_no_persist( +/// let mut wallet = Wallet::new( /// Bip49Public(key.clone(), fingerprint, KeychainKind::External), /// Bip49Public(key, fingerprint, KeychainKind::Internal), /// Network::Testnet, /// )?; /// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); +/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q"); /// # Ok::<_, Box>(()) /// ``` @@ -364,13 +361,13 @@ impl> DescriptorTemplate for Bip49Public { /// use bdk_wallet::template::Bip84; /// /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::new_no_persist( +/// let mut wallet = Wallet::new( /// Bip84(key.clone(), KeychainKind::External), /// Bip84(key, KeychainKind::Internal), /// Network::Testnet, /// )?; /// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n"); +/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr"); /// # Ok::<_, Box>(()) /// ``` @@ -401,13 +398,13 @@ impl> DescriptorTemplate for Bip84 { /// /// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::new_no_persist( +/// let mut wallet = Wallet::new( /// Bip84Public(key.clone(), fingerprint, KeychainKind::External), /// Bip84Public(key, fingerprint, KeychainKind::Internal), /// Network::Testnet, /// )?; /// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); +/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv"); /// # Ok::<_, Box>(()) /// ``` @@ -437,13 +434,13 @@ impl> DescriptorTemplate for Bip84Public { /// use bdk_wallet::template::Bip86; /// /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::new_no_persist( +/// let mut wallet = Wallet::new( /// Bip86(key.clone(), KeychainKind::External), /// Bip86(key, KeychainKind::Internal), /// Network::Testnet, /// )?; /// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu"); +/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm"); /// # Ok::<_, Box>(()) /// ``` @@ -474,13 +471,13 @@ impl> DescriptorTemplate for Bip86 { /// /// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::new_no_persist( +/// let mut wallet = Wallet::new( /// Bip86Public(key.clone(), fingerprint, KeychainKind::External), /// Bip86Public(key, fingerprint, KeychainKind::Internal), /// Network::Testnet, /// )?; /// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37"); +/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku"); /// # Ok::<_, Box>(()) /// ``` diff --git a/crates/wallet/src/wallet/coin_selection.rs b/crates/wallet/src/wallet/coin_selection.rs index 5df64796a5..a95636d992 100644 --- a/crates/wallet/src/wallet/coin_selection.rs +++ b/crates/wallet/src/wallet/coin_selection.rs @@ -28,7 +28,6 @@ //! # use bitcoin::*; //! # use bdk_wallet::wallet::{self, ChangeSet, coin_selection::*, coin_selection}; //! # use bdk_wallet::wallet::error::CreateTxError; -//! # use bdk_persist::PersistBackend; //! # use bdk_wallet::*; //! # use bdk_wallet::wallet::coin_selection::decide_change; //! # use anyhow::Error; diff --git a/crates/wallet/src/wallet/error.rs b/crates/wallet/src/wallet/error.rs index 7b19a2ec5b..b6c9375a43 100644 --- a/crates/wallet/src/wallet/error.rs +++ b/crates/wallet/src/wallet/error.rs @@ -50,8 +50,6 @@ impl std::error::Error for MiniscriptPsbtError {} pub enum CreateTxError { /// There was a problem with the descriptors passed in Descriptor(DescriptorError), - /// We were unable to load wallet data from or write wallet data to the persistence backend - Persist(anyhow::Error), /// There was a problem while extracting and manipulating policies Policy(PolicyError), /// Spending policy is not compatible with this [`KeychainKind`] @@ -114,13 +112,6 @@ impl fmt::Display for CreateTxError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Descriptor(e) => e.fmt(f), - Self::Persist(e) => { - write!( - f, - "failed to load wallet data from or write wallet data to persistence backend: {}", - e - ) - } Self::Policy(e) => e.fmt(f), CreateTxError::SpendingPolicyRequired(keychain_kind) => { write!(f, "Spending policy required: {:?}", keychain_kind) diff --git a/crates/wallet/src/wallet/export.rs b/crates/wallet/src/wallet/export.rs index 3697f91ddd..91c0ff505f 100644 --- a/crates/wallet/src/wallet/export.rs +++ b/crates/wallet/src/wallet/export.rs @@ -29,7 +29,7 @@ //! }"#; //! //! let import = FullyNodedExport::from_str(import)?; -//! let wallet = Wallet::new_no_persist( +//! let wallet = Wallet::new( //! &import.descriptor(), //! &import.change_descriptor().expect("change descriptor"), //! Network::Testnet, @@ -42,7 +42,7 @@ //! # use bitcoin::*; //! # use bdk_wallet::wallet::export::*; //! # use bdk_wallet::*; -//! let wallet = Wallet::new_no_persist( +//! let wallet = Wallet::new( //! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)", //! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)", //! Network::Testnet, @@ -222,7 +222,7 @@ mod test { use crate::wallet::Wallet; fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet { - let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap(); + let mut wallet = Wallet::new(descriptor, change_descriptor, network).unwrap(); let transaction = Transaction { input: vec![], output: vec![], diff --git a/crates/wallet/src/wallet/hardwaresigner.rs b/crates/wallet/src/wallet/hardwaresigner.rs index b9bff5ad50..b79cd5cf66 100644 --- a/crates/wallet/src/wallet/hardwaresigner.rs +++ b/crates/wallet/src/wallet/hardwaresigner.rs @@ -30,7 +30,7 @@ //! let first_device = devices.remove(0)?; //! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?; //! -//! # let mut wallet = Wallet::new_no_persist( +//! # let mut wallet = Wallet::new( //! # "", //! # None, //! # Network::Testnet, diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 0d661727dd..045d83d175 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -31,7 +31,6 @@ use bdk_chain::{ Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut, IndexedTxGraph, }; -use bdk_persist::{Persist, PersistBackend}; use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; use bitcoin::{ @@ -41,6 +40,7 @@ use bitcoin::{ use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt}; use bitcoin::{constants::genesis_block, Amount}; use core::fmt; +use core::mem; use core::ops::Deref; use descriptor::error::Error as DescriptorError; use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier}; @@ -85,6 +85,11 @@ const COINBASE_MATURITY: u32 = 100; /// 1. output *descriptors* from which it can derive addresses. /// 2. [`signer`]s that can contribute signatures to addresses instantiated from the descriptors. /// +/// The user is responsible for loading and writing wallet changes using an implementation of +/// [`PersistBackend`]. See individual functions and example for instructions on when [`Wallet`] +/// state needs to be persisted. +/// +/// [`PersistBackend`]: bdk_chain::persist::PersistBackend /// [`signer`]: crate::signer #[derive(Debug)] pub struct Wallet { @@ -92,7 +97,7 @@ pub struct Wallet { change_signers: Arc, chain: LocalChain, indexed_graph: IndexedTxGraph>, - persist: Persist, + stage: ChangeSet, network: Network, secp: SecpCtx, } @@ -136,7 +141,8 @@ impl From for Update { } /// The changes made to a wallet by applying an [`Update`]. -pub type ChangeSet = bdk_persist::CombinedChangeSet; +pub type ChangeSet = + bdk_chain::persist::CombinedChangeSet; /// A derived address and the index it was found at. /// For convenience this automatically derefs to `Address` @@ -164,36 +170,6 @@ impl fmt::Display for AddressInfo { } } -impl Wallet { - /// Creates a wallet that does not persist data. - pub fn new_no_persist( - descriptor: E, - change_descriptor: E, - network: Network, - ) -> Result { - Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e { - NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"), - NewError::Descriptor(e) => e, - NewError::Persist(_) => unreachable!("mock-write must always succeed"), - }) - } - - /// Creates a wallet that does not persist data, with a custom genesis hash. - pub fn new_no_persist_with_genesis_hash( - descriptor: E, - change_descriptor: E, - network: Network, - genesis_hash: BlockHash, - ) -> Result { - Self::new_with_genesis_hash(descriptor, change_descriptor, (), network, genesis_hash) - .map_err(|e| match e { - NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"), - NewError::Descriptor(e) => e, - NewError::Persist(_) => unreachable!("mock-write must always succeed"), - }) - } -} - /// The error type when constructing a fresh [`Wallet`]. /// /// Methods [`new`] and [`new_with_genesis_hash`] may return this error. @@ -202,23 +178,14 @@ impl Wallet { /// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash #[derive(Debug)] pub enum NewError { - /// Database already has data. - NonEmptyDatabase, /// There was problem with the passed-in descriptor(s). Descriptor(crate::descriptor::DescriptorError), - /// We were unable to write the wallet's data to the persistence backend. - Persist(anyhow::Error), } impl fmt::Display for NewError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - NewError::NonEmptyDatabase => write!( - f, - "database already has data - use `load` or `new_or_load` methods instead" - ), NewError::Descriptor(e) => e.fmt(f), - NewError::Persist(e) => e.fmt(f), } } } @@ -226,19 +193,15 @@ impl fmt::Display for NewError { #[cfg(feature = "std")] impl std::error::Error for NewError {} -/// The error type when loading a [`Wallet`] from persistence. +/// The error type when loading a [`Wallet`] from a [`ChangeSet`]. /// -/// Method [`load`] may return this error. +/// Method [`load_from_changeset`] may return this error. /// -/// [`load`]: Wallet::load +/// [`load_from_changeset`]: Wallet::load_from_changeset #[derive(Debug)] pub enum LoadError { /// There was a problem with the passed-in descriptor(s). Descriptor(crate::descriptor::DescriptorError), - /// Loading data from the persistence backend failed. - Persist(anyhow::Error), - /// Wallet not initialized, persistence backend is empty. - NotInitialized, /// Data loaded from persistence is missing network type. MissingNetwork, /// Data loaded from persistence is missing genesis hash. @@ -251,10 +214,6 @@ impl fmt::Display for LoadError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { LoadError::Descriptor(e) => e.fmt(f), - LoadError::Persist(e) => e.fmt(f), - LoadError::NotInitialized => { - write!(f, "wallet is not initialized, persistence backend is empty") - } LoadError::MissingNetwork => write!(f, "loaded data is missing network type"), LoadError::MissingGenesis => write!(f, "loaded data is missing genesis hash"), LoadError::MissingDescriptor(k) => { @@ -277,10 +236,6 @@ impl std::error::Error for LoadError {} pub enum NewOrLoadError { /// There is a problem with the passed-in descriptor. Descriptor(crate::descriptor::DescriptorError), - /// Either writing to or loading from the persistence backend failed. - Persist(anyhow::Error), - /// Wallet is not initialized, persistence backend is empty. - NotInitialized, /// The loaded genesis hash does not match what was provided. LoadedGenesisDoesNotMatch { /// The expected genesis block hash. @@ -308,14 +263,6 @@ impl fmt::Display for NewOrLoadError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { NewOrLoadError::Descriptor(e) => e.fmt(f), - NewOrLoadError::Persist(e) => write!( - f, - "failed to either write to or load from persistence, {}", - e - ), - NewOrLoadError::NotInitialized => { - write!(f, "wallet is not initialized, persistence backend is empty") - } NewOrLoadError::LoadedGenesisDoesNotMatch { expected, got } => { write!(f, "loaded genesis hash is not {}, got {:?}", expected, got) } @@ -403,11 +350,10 @@ impl Wallet { pub fn new( descriptor: E, change_descriptor: E, - db: impl PersistBackend + Send + Sync + 'static, network: Network, ) -> Result { let genesis_hash = genesis_block(network).block_hash(); - Self::new_with_genesis_hash(descriptor, change_descriptor, db, network, genesis_hash) + Self::new_with_genesis_hash(descriptor, change_descriptor, network, genesis_hash) } /// Initialize an empty [`Wallet`] with a custom genesis hash. @@ -417,15 +363,9 @@ impl Wallet { pub fn new_with_genesis_hash( descriptor: E, change_descriptor: E, - mut db: impl PersistBackend + Send + Sync + 'static, network: Network, genesis_hash: BlockHash, ) -> Result { - if let Ok(changeset) = db.load_from_persistence() { - if changeset.is_some() { - return Err(NewError::NonEmptyDatabase); - } - } let secp = Secp256k1::new(); let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash); let mut index = KeychainTxOutIndex::::default(); @@ -436,13 +376,11 @@ impl Wallet { let indexed_graph = IndexedTxGraph::new(index); - let mut persist = Persist::new(db); - persist.stage(ChangeSet { + let staged = ChangeSet { chain: chain_changeset, indexed_tx_graph: indexed_graph.initial_changeset(), network: Some(network), - }); - persist.commit().map_err(NewError::Persist)?; + }; Ok(Wallet { signers, @@ -450,12 +388,24 @@ impl Wallet { network, chain, indexed_graph, - persist, + stage: staged, secp, }) } - /// Load [`Wallet`] from the given persistence backend. + /// Stage a ['ChangeSet'] to be persisted later. + /// + /// [`commit`]: Self::commit + fn stage(&mut self, changeset: ChangeSet) { + self.stage.append(changeset) + } + + /// Take the staged [`ChangeSet`] to be persisted now. + pub fn take_staged(&mut self) -> ChangeSet { + mem::take(&mut self.stage) + } + + /// Load [`Wallet`] from the given previously persisted [`ChangeSet`]. /// /// Note that the descriptor secret keys are not persisted to the db; this means that after /// calling this method the [`Wallet`] **won't** know the secret keys, and as such, won't be @@ -473,10 +423,11 @@ impl Wallet { /// # use bdk_sqlite::{Store, rusqlite::Connection}; /// # /// # fn main() -> Result<(), anyhow::Error> { + /// # use bdk_chain::persist::PersistBackend; /// # let temp_dir = tempfile::tempdir().expect("must create tempdir"); /// # let file_path = temp_dir.path().join("store.db"); /// # let conn = Connection::open(file_path).expect("must open connection"); - /// # let db = Store::new(conn).expect("must create db"); + /// # let mut db = Store::new(conn).expect("must create db"); /// let secp = Secp256k1::new(); /// /// let (external_descriptor, external_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)").unwrap(); @@ -484,8 +435,8 @@ impl Wallet { /// /// let external_signer_container = SignersContainer::build(external_keymap, &external_descriptor, &secp); /// let internal_signer_container = SignersContainer::build(internal_keymap, &internal_descriptor, &secp); - /// - /// let mut wallet = Wallet::load(db)?; + /// let changeset = db.load_changes()?.expect("there must be an existing changeset"); + /// let mut wallet = Wallet::load_from_changeset(changeset)?; /// /// external_signer_container.signers().into_iter() /// .for_each(|s| wallet.add_signer(KeychainKind::External, SignerOrdering::default(), s.clone())); @@ -497,20 +448,7 @@ impl Wallet { /// /// Alternatively, you can call [`Wallet::new_or_load`], which will add the private keys of the /// passed-in descriptors to the [`Wallet`]. - pub fn load( - mut db: impl PersistBackend + Send + Sync + 'static, - ) -> Result { - let changeset = db - .load_from_persistence() - .map_err(LoadError::Persist)? - .ok_or(LoadError::NotInitialized)?; - Self::load_from_changeset(db, changeset) - } - - fn load_from_changeset( - db: impl PersistBackend + Send + Sync + 'static, - changeset: ChangeSet, - ) -> Result { + pub fn load_from_changeset(changeset: ChangeSet) -> Result { let secp = Secp256k1::new(); let network = changeset.network.ok_or(LoadError::MissingNetwork)?; let chain = @@ -538,157 +476,156 @@ impl Wallet { let mut indexed_graph = IndexedTxGraph::new(index); indexed_graph.apply_changeset(changeset.indexed_tx_graph); - let persist = Persist::new(db); + let stage = ChangeSet::default(); Ok(Wallet { signers, change_signers, chain, indexed_graph, - persist, + stage, network, secp, }) } - /// Either loads [`Wallet`] from persistence, or initializes it if it does not exist. + /// Either loads [`Wallet`] from the given [`ChangeSet`] or initializes it if one does not exist. + /// + /// This method will fail if the loaded [`ChangeSet`] has different parameters to those provided. + /// + /// ```rust,no_run + /// # use bdk_chain::persist::PersistBackend; + /// # use bdk_wallet::Wallet; + /// # use bdk_sqlite::{Store, rusqlite::Connection}; + /// # use bitcoin::Network::Testnet; + /// # let conn = Connection::open_in_memory().expect("must open connection"); + /// let mut db = Store::new(conn).expect("must create db"); + /// let changeset = db.load_changes()?; + /// + /// let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; + /// let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; /// - /// This method will fail if the loaded [`Wallet`] has different parameters to those provided. + /// let mut wallet = Wallet::new_or_load(external_descriptor, internal_descriptor, changeset, Testnet)?; + /// # Ok::<(), anyhow::Error>(()) + /// ``` pub fn new_or_load( descriptor: E, change_descriptor: E, - db: impl PersistBackend + Send + Sync + 'static, + changeset: Option, network: Network, ) -> Result { let genesis_hash = genesis_block(network).block_hash(); Self::new_or_load_with_genesis_hash( descriptor, change_descriptor, - db, + changeset, network, genesis_hash, ) } - /// Either loads [`Wallet`] from persistence, or initializes it if it does not exist, using the + /// Either loads [`Wallet`] from a [`ChangeSet`] or initializes it if one does not exist, using the /// provided descriptor, change descriptor, network, and custom genesis hash. /// - /// This method will fail if the loaded [`Wallet`] has different parameters to those provided. + /// This method will fail if the loaded [`ChangeSet`] has different parameters to those provided. /// This is like [`Wallet::new_or_load`] with an additional `genesis_hash` parameter. This is /// useful for syncing from alternative networks. pub fn new_or_load_with_genesis_hash( descriptor: E, change_descriptor: E, - mut db: impl PersistBackend + Send + Sync + 'static, + changeset: Option, network: Network, genesis_hash: BlockHash, ) -> Result { - let changeset = db - .load_from_persistence() - .map_err(NewOrLoadError::Persist)?; - match changeset { - Some(changeset) => { - let mut wallet = Self::load_from_changeset(db, changeset).map_err(|e| match e { - LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e), - LoadError::Persist(e) => NewOrLoadError::Persist(e), - LoadError::NotInitialized => NewOrLoadError::NotInitialized, - LoadError::MissingNetwork => NewOrLoadError::LoadedNetworkDoesNotMatch { - expected: network, + if let Some(changeset) = changeset { + let mut wallet = Self::load_from_changeset(changeset).map_err(|e| match e { + LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e), + LoadError::MissingNetwork => NewOrLoadError::LoadedNetworkDoesNotMatch { + expected: network, + got: None, + }, + LoadError::MissingGenesis => NewOrLoadError::LoadedGenesisDoesNotMatch { + expected: genesis_hash, + got: None, + }, + LoadError::MissingDescriptor(keychain) => { + NewOrLoadError::LoadedDescriptorDoesNotMatch { got: None, - }, - LoadError::MissingGenesis => NewOrLoadError::LoadedGenesisDoesNotMatch { - expected: genesis_hash, - got: None, - }, - LoadError::MissingDescriptor(keychain) => { - NewOrLoadError::LoadedDescriptorDoesNotMatch { - got: None, - keychain, - } + keychain, } - })?; - if wallet.network != network { - return Err(NewOrLoadError::LoadedNetworkDoesNotMatch { - expected: network, - got: Some(wallet.network), - }); - } - if wallet.chain.genesis_hash() != genesis_hash { - return Err(NewOrLoadError::LoadedGenesisDoesNotMatch { - expected: genesis_hash, - got: Some(wallet.chain.genesis_hash()), - }); - } - - let (expected_descriptor, expected_descriptor_keymap) = descriptor - .into_wallet_descriptor(&wallet.secp, network) - .map_err(NewOrLoadError::Descriptor)?; - let wallet_descriptor = wallet.public_descriptor(KeychainKind::External); - if wallet_descriptor != &expected_descriptor { - return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch { - got: Some(wallet_descriptor.clone()), - keychain: KeychainKind::External, - }); - } - // if expected descriptor has private keys add them as new signers - if !expected_descriptor_keymap.is_empty() { - let signer_container = SignersContainer::build( - expected_descriptor_keymap, - &expected_descriptor, - &wallet.secp, - ); - signer_container.signers().into_iter().for_each(|signer| { - wallet.add_signer( - KeychainKind::External, - SignerOrdering::default(), - signer.clone(), - ) - }); } + })?; + if wallet.network != network { + return Err(NewOrLoadError::LoadedNetworkDoesNotMatch { + expected: network, + got: Some(wallet.network), + }); + } + if wallet.chain.genesis_hash() != genesis_hash { + return Err(NewOrLoadError::LoadedGenesisDoesNotMatch { + expected: genesis_hash, + got: Some(wallet.chain.genesis_hash()), + }); + } - let (expected_change_descriptor, expected_change_descriptor_keymap) = - change_descriptor - .into_wallet_descriptor(&wallet.secp, network) - .map_err(NewOrLoadError::Descriptor)?; - let wallet_change_descriptor = wallet.public_descriptor(KeychainKind::Internal); - if wallet_change_descriptor != &expected_change_descriptor { - return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch { - got: Some(wallet_change_descriptor.clone()), - keychain: KeychainKind::Internal, - }); - } - // if expected change descriptor has private keys add them as new signers - if !expected_change_descriptor_keymap.is_empty() { - let signer_container = SignersContainer::build( - expected_change_descriptor_keymap, - &expected_change_descriptor, - &wallet.secp, - ); - signer_container.signers().into_iter().for_each(|signer| { - wallet.add_signer( - KeychainKind::Internal, - SignerOrdering::default(), - signer.clone(), - ) - }); - } + let (expected_descriptor, expected_descriptor_keymap) = descriptor + .into_wallet_descriptor(&wallet.secp, network) + .map_err(NewOrLoadError::Descriptor)?; + let wallet_descriptor = wallet.public_descriptor(KeychainKind::External); + if wallet_descriptor != &expected_descriptor { + return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch { + got: Some(wallet_descriptor.clone()), + keychain: KeychainKind::External, + }); + } + // if expected descriptor has private keys add them as new signers + if !expected_descriptor_keymap.is_empty() { + let signer_container = SignersContainer::build( + expected_descriptor_keymap, + &expected_descriptor, + &wallet.secp, + ); + signer_container.signers().into_iter().for_each(|signer| { + wallet.add_signer( + KeychainKind::External, + SignerOrdering::default(), + signer.clone(), + ) + }); + } - Ok(wallet) + let (expected_change_descriptor, expected_change_descriptor_keymap) = change_descriptor + .into_wallet_descriptor(&wallet.secp, network) + .map_err(NewOrLoadError::Descriptor)?; + let wallet_change_descriptor = wallet.public_descriptor(KeychainKind::Internal); + if wallet_change_descriptor != &expected_change_descriptor { + return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch { + got: Some(wallet_change_descriptor.clone()), + keychain: KeychainKind::Internal, + }); } - None => Self::new_with_genesis_hash( - descriptor, - change_descriptor, - db, - network, - genesis_hash, - ) - .map_err(|e| match e { - NewError::NonEmptyDatabase => { - unreachable!("database is already checked to have no data") - } - NewError::Descriptor(e) => NewOrLoadError::Descriptor(e), - NewError::Persist(e) => NewOrLoadError::Persist(e), - }), + // if expected change descriptor has private keys add them as new signers + if !expected_change_descriptor_keymap.is_empty() { + let signer_container = SignersContainer::build( + expected_change_descriptor_keymap, + &expected_change_descriptor, + &wallet.secp, + ); + signer_container.signers().into_iter().for_each(|signer| { + wallet.add_signer( + KeychainKind::Internal, + SignerOrdering::default(), + signer.clone(), + ) + }); + } + + Ok(wallet) + } else { + Self::new_with_genesis_hash(descriptor, change_descriptor, network, genesis_hash) + .map_err(|e| match e { + NewError::Descriptor(e) => NewOrLoadError::Descriptor(e), + }) } } @@ -732,29 +669,45 @@ impl Wallet { /// Attempt to reveal the next address of the given `keychain`. /// - /// This will increment the internal derivation index. If the keychain's descriptor doesn't + /// This will increment the keychain's derivation index. If the keychain's descriptor doesn't /// contain a wildcard or every address is already revealed up to the maximum derivation /// index defined in [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), - /// then returns the last revealed address. + /// then the last revealed address will be returned. /// - /// # Errors + /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or more + /// calls to this method before closing the wallet. For example: /// - /// If writing to persistent storage fails. - pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> anyhow::Result { - let ((index, spk), index_changeset) = self - .indexed_graph - .index + /// ```rust,no_run + /// # use bdk_chain::persist::PersistBackend; + /// # use bdk_wallet::wallet::{Wallet, ChangeSet}; + /// # use bdk_wallet::KeychainKind; + /// # use bdk_sqlite::{Store, rusqlite::Connection}; + /// # let conn = Connection::open_in_memory().expect("must open connection"); + /// # let mut db = Store::new(conn).expect("must create store"); + /// # let changeset = ChangeSet::default(); + /// # let mut wallet = Wallet::load_from_changeset(changeset).expect("load wallet"); + /// let next_address = wallet.reveal_next_address(KeychainKind::External); + /// db.write_changes(&wallet.take_staged())?; + /// + /// // Now it's safe to show the user their next address! + /// println!("Next address: {}", next_address.address); + /// # Ok::<(), anyhow::Error>(()) + /// ``` + pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> AddressInfo { + let index = &mut self.indexed_graph.index; + let stage = &mut self.stage; + + let ((index, spk), index_changeset) = index .reveal_next_spk(&keychain) .expect("keychain must exist"); - self.persist - .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?; + stage.append(indexed_tx_graph::ChangeSet::from(index_changeset).into()); - Ok(AddressInfo { + AddressInfo { index, address: Address::from_script(spk, self.network).expect("must have address form"), keychain, - }) + } } /// Reveal addresses up to and including the target `index` and return an iterator @@ -764,28 +717,26 @@ impl Wallet { /// possible index. If all addresses up to the given `index` are already revealed, then /// no new addresses are returned. /// - /// # Errors - /// - /// If writing to persistent storage fails. + /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or more + /// calls to this method before closing the wallet. See [`Wallet::reveal_next_address`]. pub fn reveal_addresses_to( &mut self, keychain: KeychainKind, index: u32, - ) -> anyhow::Result + '_> { + ) -> impl Iterator + '_ { let (spk_iter, index_changeset) = self .indexed_graph .index .reveal_to_target(&keychain, index) .expect("keychain must exist"); - self.persist - .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?; + self.stage(indexed_tx_graph::ChangeSet::from(index_changeset).into()); - Ok(spk_iter.map(move |(index, spk)| AddressInfo { + spk_iter.map(move |(index, spk)| AddressInfo { index, address: Address::from_script(&spk, self.network).expect("must have address form"), keychain, - })) + }) } /// Get the next unused address for the given `keychain`, i.e. the address with the lowest @@ -794,24 +745,23 @@ impl Wallet { /// This will attempt to derive and reveal a new address if no newly revealed addresses /// are available. See also [`reveal_next_address`](Self::reveal_next_address). /// - /// # Errors - /// - /// If writing to persistent storage fails. - pub fn next_unused_address(&mut self, keychain: KeychainKind) -> anyhow::Result { - let ((index, spk), index_changeset) = self - .indexed_graph - .index + /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or more + /// calls to this method before closing the wallet. See [`Wallet::reveal_next_address`]. + pub fn next_unused_address(&mut self, keychain: KeychainKind) -> AddressInfo { + let index = &mut self.indexed_graph.index; + + let ((index, spk), index_changeset) = index .next_unused_spk(&keychain) .expect("keychain must exist"); - self.persist - .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?; + self.stage + .append(indexed_tx_graph::ChangeSet::from(index_changeset).into()); - Ok(AddressInfo { + AddressInfo { index, address: Address::from_script(spk, self.network).expect("must have address form"), keychain, - }) + } } /// Marks an address used of the given `keychain` at `index`. @@ -950,19 +900,20 @@ impl Wallet { /// or [`calculate_fee_rate`] on a given transaction. Outputs inserted with this method will /// not be returned in [`list_unspent`] or [`list_output`]. /// - /// Any inserted `TxOut`s are not persisted until [`commit`] is called. - /// - /// **WARNING:** This should only be used to add `TxOut`s that the wallet does not own. Only + /// **WARNINGS:** This should only be used to add `TxOut`s that the wallet does not own. Only /// insert `TxOut`s that you trust the values for! /// + /// You must persist the changes resulting from one or more calls to this method if you need + /// the inserted `TxOut` data to be reloaded after closing the wallet. + /// See [`Wallet::reveal_next_address`]. + /// /// [`calculate_fee`]: Self::calculate_fee /// [`calculate_fee_rate`]: Self::calculate_fee_rate /// [`list_unspent`]: Self::list_unspent /// [`list_output`]: Self::list_output - /// [`commit`]: Self::commit pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) { let additions = self.indexed_graph.insert_txout(outpoint, txout); - self.persist.stage(ChangeSet::from(additions)); + self.stage(ChangeSet::from(additions)); } /// Calculates the fee of a given transaction. Returns [`Amount::ZERO`] if `tx` is a coinbase transaction. @@ -1116,11 +1067,14 @@ impl Wallet { } /// Add a new checkpoint to the wallet's internal view of the chain. - /// This stages but does not [`commit`] the change. /// /// Returns whether anything changed with the insertion (e.g. `false` if checkpoint was already /// there). /// + /// **WARNING**: You must persist the changes resulting from one or more calls to this method + /// if you need the inserted checkpoint data to be reloaded after closing the wallet. + /// See [`Wallet::reveal_next_address`]. + /// /// [`commit`]: Self::commit pub fn insert_checkpoint( &mut self, @@ -1128,12 +1082,12 @@ impl Wallet { ) -> Result { let changeset = self.chain.insert_block(block_id)?; let changed = !changeset.is_empty(); - self.persist.stage(changeset.into()); + self.stage(changeset.into()); Ok(changed) } - /// Add a transaction to the wallet's internal view of the chain. This stages but does not - /// [`commit`] the change. + /// Add a transaction to the wallet's internal view of the chain. This stages the change, + /// you must persist it later. /// /// Returns whether anything changed with the transaction insertion (e.g. `false` if the /// transaction was already inserted at the same position). @@ -1142,10 +1096,13 @@ impl Wallet { /// Therefore you should use [`insert_checkpoint`] to insert new checkpoints before manually /// inserting new transactions. /// - /// **WARNING:** If `position` is confirmed, we anchor the `tx` to a the lowest checkpoint that + /// **WARNING**: If `position` is confirmed, we anchor the `tx` to the lowest checkpoint that /// is >= the `position`'s height. The caller is responsible for ensuring the `tx` exists in our /// local view of the best chain's history. /// + /// You must persist the changes resulting from one or more calls to this method if you need + /// the inserted tx to be reloaded after closing the wallet. + /// /// [`commit`]: Self::commit /// [`latest_checkpoint`]: Self::latest_checkpoint /// [`insert_checkpoint`]: Self::insert_checkpoint @@ -1187,7 +1144,7 @@ impl Wallet { } let changed = !changeset.is_empty(); - self.persist.stage(changeset); + self.stage(changeset); Ok(changed) } @@ -1238,7 +1195,7 @@ impl Wallet { /// # use bdk_wallet::bitcoin::Network; /// let descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/0/*)"; /// let change_descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/1/*)"; - /// let wallet = Wallet::new_no_persist(descriptor, change_descriptor, Network::Testnet)?; + /// let wallet = Wallet::new(descriptor, change_descriptor, Network::Testnet)?; /// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) { /// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/* /// println!("secret_key: {}", secret_key); @@ -1265,7 +1222,6 @@ impl Wallet { /// # use bdk_wallet::*; /// # use bdk_wallet::wallet::ChangeSet; /// # use bdk_wallet::wallet::error::CreateTxError; - /// # use bdk_persist::PersistBackend; /// # use anyhow::Error; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); @@ -1516,11 +1472,9 @@ impl Wallet { .expect("keychain must exist"); let spk = spk.into(); self.indexed_graph.index.mark_used(change_keychain, index); - self.persist - .stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from( - index_changeset, - ))); - self.persist.commit().map_err(CreateTxError::Persist)?; + self.stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from( + index_changeset, + ))); spk } }; @@ -1619,7 +1573,6 @@ impl Wallet { /// # use bdk_wallet::*; /// # use bdk_wallet::wallet::ChangeSet; /// # use bdk_wallet::wallet::error::CreateTxError; - /// # use bdk_persist::PersistBackend; /// # use anyhow::Error; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); @@ -1795,7 +1748,6 @@ impl Wallet { /// # use bdk_wallet::*; /// # use bdk_wallet::wallet::ChangeSet; /// # use bdk_wallet::wallet::error::CreateTxError; - /// # use bdk_persist::PersistBackend; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); @@ -2317,11 +2269,14 @@ impl Wallet { .to_string() } - /// Applies an update to the wallet and stages the changes (but does not [`commit`] them). + /// Applies an update to the wallet and stages the changes (but does not persist them). /// /// Usually you create an `update` by interacting with some blockchain data source and inserting /// transactions related to your wallet into it. /// + /// After applying updates you should persist the staged wallet changes. For an example of how + /// to persist staged wallet changes see [`Wallet::reveal_next_address`]. ` + /// /// [`commit`]: Self::commit pub fn apply_update(&mut self, update: impl Into) -> Result<(), CannotConnectError> { let update = update.into(); @@ -2340,27 +2295,10 @@ impl Wallet { changeset.append(ChangeSet::from( self.indexed_graph.apply_update(update.graph), )); - self.persist.stage(changeset); + self.stage(changeset); Ok(()) } - /// Commits all currently [`staged`] changed to the persistence backend returning and error when - /// this fails. - /// - /// This returns whether the `update` resulted in any changes. - /// - /// [`staged`]: Self::staged - pub fn commit(&mut self) -> anyhow::Result { - self.persist.commit().map(|c| c.is_some()) - } - - /// Returns the changes that will be committed with the next call to [`commit`]. - /// - /// [`commit`]: Self::commit - pub fn staged(&self) -> &ChangeSet { - self.persist.staged() - } - /// Get a reference to the inner [`TxGraph`]. pub fn tx_graph(&self) -> &TxGraph { self.indexed_graph.graph() @@ -2409,6 +2347,10 @@ impl Wallet { /// The `connected_to` parameter informs the wallet how this block connects to the internal /// [`LocalChain`]. Relevant transactions are filtered from the `block` and inserted into the /// internal [`TxGraph`]. + /// + /// **WARNING**: You must persist the changes resulting from one or more calls to this method + /// if you need the inserted block data to be reloaded after closing the wallet. + /// See [`Wallet::reveal_next_address`]. pub fn apply_block_connected_to( &mut self, block: &Block, @@ -2426,7 +2368,7 @@ impl Wallet { .apply_block_relevant(block, height) .into(), ); - self.persist.stage(changeset); + self.stage(changeset); Ok(()) } @@ -2438,6 +2380,10 @@ impl Wallet { /// when the transaction was last seen in the mempool. This is used for conflict resolution /// when there is conflicting unconfirmed transactions. The transaction with the later /// `last_seen` is prioritized. + /// + /// **WARNING**: You must persist the changes resulting from one or more calls to this method + /// if you need the applied unconfirmed transactions to be reloaded after closing the wallet. + /// See [`Wallet::reveal_next_address`]. pub fn apply_unconfirmed_txs<'t>( &mut self, unconfirmed_txs: impl IntoIterator, @@ -2445,7 +2391,7 @@ impl Wallet { let indexed_graph_changeset = self .indexed_graph .batch_insert_relevant_unconfirmed(unconfirmed_txs); - self.persist.stage(ChangeSet::from(indexed_graph_changeset)); + self.stage(ChangeSet::from(indexed_graph_changeset)); } } @@ -2572,7 +2518,7 @@ macro_rules! doctest_wallet { let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)"; let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)"; - let mut wallet = Wallet::new_no_persist( + let mut wallet = Wallet::new( descriptor, change_descriptor, Network::Regtest, diff --git a/crates/wallet/src/wallet/signer.rs b/crates/wallet/src/wallet/signer.rs index 280e7066f4..4bfec59a89 100644 --- a/crates/wallet/src/wallet/signer.rs +++ b/crates/wallet/src/wallet/signer.rs @@ -69,7 +69,7 @@ //! //! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)"; //! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)"; -//! let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, Network::Testnet)?; +//! let mut wallet = Wallet::new(descriptor, change_descriptor, Network::Testnet)?; //! wallet.add_signer( //! KeychainKind::External, //! SignerOrdering(200), diff --git a/crates/wallet/src/wallet/tx_builder.rs b/crates/wallet/src/wallet/tx_builder.rs index fbe5081f71..91df66a3ff 100644 --- a/crates/wallet/src/wallet/tx_builder.rs +++ b/crates/wallet/src/wallet/tx_builder.rs @@ -19,7 +19,6 @@ //! # use bdk_wallet::*; //! # use bdk_wallet::wallet::ChangeSet; //! # use bdk_wallet::wallet::error::CreateTxError; -//! # use bdk_persist::PersistBackend; //! # use anyhow::Error; //! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); //! # let mut wallet = doctest_wallet!(); @@ -68,7 +67,6 @@ use crate::{KeychainKind, LocalOutput, Utxo, WeightedUtxo}; /// # use core::str::FromStr; /// # use bdk_wallet::wallet::ChangeSet; /// # use bdk_wallet::wallet::error::CreateTxError; -/// # use bdk_persist::PersistBackend; /// # use anyhow::Error; /// # let mut wallet = doctest_wallet!(); /// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); @@ -638,7 +636,6 @@ impl<'a, Cs> TxBuilder<'a, Cs> { /// # use bdk_wallet::*; /// # use bdk_wallet::wallet::ChangeSet; /// # use bdk_wallet::wallet::error::CreateTxError; - /// # use bdk_persist::PersistBackend; /// # use anyhow::Error; /// # let to_address = /// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") @@ -673,6 +670,9 @@ impl<'a, Cs: CoinSelectionAlgorithm> TxBuilder<'a, Cs> { /// Returns a new [`Psbt`] per [`BIP174`]. /// /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki + /// + /// **WARNING**: To avoid change address reuse you must persist the changes resulting from one + /// or more calls to this method before closing the wallet. See [`Wallet::reveal_next_address`]. pub fn finish(self) -> Result { self.wallet .borrow_mut() diff --git a/crates/wallet/tests/common.rs b/crates/wallet/tests/common.rs index d77147f4d8..96cdabddaa 100644 --- a/crates/wallet/tests/common.rs +++ b/crates/wallet/tests/common.rs @@ -16,7 +16,7 @@ use std::str::FromStr; /// to a foreign address and one returning 50_000 back to the wallet. The remaining 1000 /// sats are the transaction fee. pub fn get_funded_wallet_with_change(descriptor: &str, change: &str) -> (Wallet, bitcoin::Txid) { - let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Regtest).unwrap(); + let mut wallet = Wallet::new(descriptor, change, Network::Regtest).unwrap(); let receive_address = wallet.peek_address(KeychainKind::External, 0).address; let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5") .expect("address") diff --git a/crates/wallet/tests/wallet.rs b/crates/wallet/tests/wallet.rs index dff699a6ff..de8a05eac2 100644 --- a/crates/wallet/tests/wallet.rs +++ b/crates/wallet/tests/wallet.rs @@ -1,11 +1,11 @@ +use anyhow::anyhow; use std::path::Path; use std::str::FromStr; use assert_matches::assert_matches; use bdk_chain::collections::BTreeMap; use bdk_chain::COINBASE_MATURITY; -use bdk_chain::{BlockId, ConfirmationTime}; -use bdk_persist::PersistBackend; +use bdk_chain::{persist::PersistBackend, BlockId, ConfirmationTime}; use bdk_sqlite::rusqlite::Connection; use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor}; use bdk_wallet::psbt::PsbtUtils; @@ -13,8 +13,7 @@ use bdk_wallet::signer::{SignOptions, SignerError}; use bdk_wallet::wallet::coin_selection::{self, LargestFirstCoinSelection}; use bdk_wallet::wallet::error::CreateTxError; use bdk_wallet::wallet::tx_builder::AddForeignUtxoError; -use bdk_wallet::wallet::NewError; -use bdk_wallet::wallet::{AddressInfo, Balance, Wallet}; +use bdk_wallet::wallet::{AddressInfo, Balance, NewError, Wallet}; use bdk_wallet::KeychainKind; use bitcoin::hashes::Hash; use bitcoin::key::Secp256k1; @@ -31,7 +30,7 @@ mod common; use common::*; fn receive_output(wallet: &mut Wallet, value: u64, height: ConfirmationTime) -> OutPoint { - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let tx = Transaction { version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, @@ -85,18 +84,30 @@ fn load_recovers_wallet() -> anyhow::Result<()> { // create new wallet let wallet_spk_index = { - let db = create_new(&file_path).expect("must create db"); let mut wallet = - Wallet::new(desc, change_desc, db, Network::Testnet).expect("must init wallet"); + Wallet::new(desc, change_desc, Network::Testnet).expect("must init wallet"); + + wallet.reveal_next_address(KeychainKind::External); + + // persist new wallet changes + let staged_changeset = wallet.take_staged(); + let db = &mut create_new(&file_path).expect("must create db"); + db.write_changes(&staged_changeset) + .map_err(|e| anyhow!("write changes error: {}", e))?; - wallet.reveal_next_address(KeychainKind::External).unwrap(); wallet.spk_index().clone() }; // recover wallet { - let db = recover(&file_path).expect("must recover db"); - let wallet = Wallet::load(db).expect("must recover wallet"); + // load persisted wallet changes + let db = &mut recover(&file_path).expect("must recover db"); + let changeset = db + .load_changes() + .expect("must recover wallet") + .expect("changeset"); + + let wallet = Wallet::load_from_changeset(changeset).expect("must recover wallet"); assert_eq!(wallet.network(), Network::Testnet); assert_eq!( wallet.spk_index().keychains().collect::>(), @@ -115,13 +126,6 @@ fn load_recovers_wallet() -> anyhow::Result<()> { ); } - // `new` can only be called on empty db - { - let db = recover(&file_path).expect("must recover db"); - let result = Wallet::new(desc, change_desc, db, Network::Testnet); - assert!(matches!(result, Err(NewError::NonEmptyDatabase))); - } - Ok(()) } @@ -152,16 +156,22 @@ fn new_or_load() -> anyhow::Result<()> { // init wallet when non-existent let wallet_keychains: BTreeMap<_, _> = { - let db = new_or_load(&file_path).expect("must create db"); - let wallet = Wallet::new_or_load(desc, change_desc, db, Network::Testnet) + let wallet = &mut Wallet::new_or_load(desc, change_desc, None, Network::Testnet) .expect("must init wallet"); + let staged_changeset = wallet.take_staged(); + let mut db = new_or_load(&file_path).expect("must create db"); + db.write_changes(&staged_changeset) + .map_err(|e| anyhow!("write changes error: {}", e))?; wallet.keychains().map(|(k, v)| (*k, v.clone())).collect() }; // wrong network { - let db = new_or_load(&file_path).expect("must create db"); - let err = Wallet::new_or_load(desc, change_desc, db, Network::Bitcoin) + let mut db = new_or_load(&file_path).expect("must create db"); + let changeset = db + .load_changes() + .map_err(|e| anyhow!("load changes error: {}", e))?; + let err = Wallet::new_or_load(desc, change_desc, changeset, Network::Bitcoin) .expect_err("wrong network"); assert!( matches!( @@ -182,11 +192,14 @@ fn new_or_load() -> anyhow::Result<()> { let got_blockhash = bitcoin::blockdata::constants::genesis_block(Network::Testnet).block_hash(); - let db = new_or_load(&file_path).expect("must open db"); + let db = &mut new_or_load(&file_path).expect("must open db"); + let changeset = db + .load_changes() + .map_err(|e| anyhow!("load changes error: {}", e))?; let err = Wallet::new_or_load_with_genesis_hash( desc, change_desc, - db, + changeset, Network::Testnet, exp_blockhash, ) @@ -204,15 +217,19 @@ fn new_or_load() -> anyhow::Result<()> { // wrong external descriptor { - let exp_descriptor = get_test_tr_single_sig(); + let (exp_descriptor, exp_change_desc) = get_test_tr_single_sig_xprv_with_change_desc(); let got_descriptor = desc .into_wallet_descriptor(&Secp256k1::new(), Network::Testnet) .unwrap() .0; - let db = new_or_load(&file_path).expect("must open db"); - let err = Wallet::new_or_load(exp_descriptor, change_desc, db, Network::Testnet) - .expect_err("wrong external descriptor"); + let db = &mut new_or_load(&file_path).expect("must open db"); + let changeset = db + .load_changes() + .map_err(|e| anyhow!("load changes error: {}", e))?; + let err = + Wallet::new_or_load(exp_descriptor, exp_change_desc, changeset, Network::Testnet) + .expect_err("wrong external descriptor"); assert!( matches!( err, @@ -232,8 +249,11 @@ fn new_or_load() -> anyhow::Result<()> { .unwrap() .0; - let db = new_or_load(&file_path).expect("must open db"); - let err = Wallet::new_or_load(desc, exp_descriptor, db, Network::Testnet) + let db = &mut new_or_load(&file_path).expect("must open db"); + let changeset = db + .load_changes() + .map_err(|e| anyhow!("load changes error: {}", e))?; + let err = Wallet::new_or_load(desc, exp_descriptor, changeset, Network::Testnet) .expect_err("wrong internal descriptor"); assert!( matches!( @@ -248,8 +268,11 @@ fn new_or_load() -> anyhow::Result<()> { // all parameters match { - let db = new_or_load(&file_path).expect("must open db"); - let wallet = Wallet::new_or_load(desc, change_desc, db, Network::Testnet) + let db = &mut new_or_load(&file_path).expect("must open db"); + let changeset = db + .load_changes() + .map_err(|e| anyhow!("load changes error: {}", e))?; + let wallet = Wallet::new_or_load(desc, change_desc, changeset, Network::Testnet) .expect("must recover wallet"); assert_eq!(wallet.network(), Network::Testnet); assert!(wallet @@ -274,9 +297,14 @@ fn new_or_load() -> anyhow::Result<()> { fn test_error_external_and_internal_are_the_same() { // identical descriptors should fail to create wallet let desc = get_test_wpkh(); - let err = Wallet::new_no_persist(desc, desc, Network::Testnet); + let err = Wallet::new(desc, desc, Network::Testnet); assert!( - matches!(&err, Err(DescriptorError::ExternalAndInternalAreTheSame)), + matches!( + &err, + Err(NewError::Descriptor( + DescriptorError::ExternalAndInternalAreTheSame + )) + ), "expected same descriptors error, got {:?}", err, ); @@ -284,9 +312,14 @@ fn test_error_external_and_internal_are_the_same() { // public + private of same descriptor should fail to create wallet let desc = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)"; let change_desc = "wpkh([3c31d632/84'/1'/0']tpubDCYwFkks2cg78N7eoYbBatsFEGje8vW8arSKW4rLwD1AU1s9KJMDRHE32JkvYERuiFjArrsH7qpWSpJATed5ShZbG9KsskA5Rmi6NSYgYN2/0/*)"; - let err = Wallet::new_no_persist(desc, change_desc, Network::Testnet); + let err = Wallet::new(desc, change_desc, Network::Testnet); assert!( - matches!(err, Err(DescriptorError::ExternalAndInternalAreTheSame)), + matches!( + err, + Err(NewError::Descriptor( + DescriptorError::ExternalAndInternalAreTheSame + )) + ), "expected same descriptors error, got {:?}", err, ); @@ -462,7 +495,7 @@ fn test_create_tx_empty_recipients() { #[should_panic(expected = "NoUtxosSelected")] fn test_create_tx_manually_selected_empty_utxos() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -473,7 +506,7 @@ fn test_create_tx_manually_selected_empty_utxos() { #[test] fn test_create_tx_version_0() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -484,7 +517,7 @@ fn test_create_tx_version_0() { #[test] fn test_create_tx_version_1_csv() { let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -495,7 +528,7 @@ fn test_create_tx_version_1_csv() { #[test] fn test_create_tx_custom_version() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -509,7 +542,7 @@ fn test_create_tx_custom_version() { fn test_create_tx_default_locktime_is_last_sync_height() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -522,7 +555,7 @@ fn test_create_tx_default_locktime_is_last_sync_height() { #[test] fn test_create_tx_fee_sniping_locktime_last_sync() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -538,7 +571,7 @@ fn test_create_tx_fee_sniping_locktime_last_sync() { #[test] fn test_create_tx_default_locktime_cltv() { let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -549,7 +582,7 @@ fn test_create_tx_default_locktime_cltv() { #[test] fn test_create_tx_custom_locktime() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -566,7 +599,7 @@ fn test_create_tx_custom_locktime() { #[test] fn test_create_tx_custom_locktime_compatible_with_cltv() { let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -579,7 +612,7 @@ fn test_create_tx_custom_locktime_compatible_with_cltv() { #[test] fn test_create_tx_custom_locktime_incompatible_with_cltv() { let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -592,7 +625,7 @@ fn test_create_tx_custom_locktime_incompatible_with_cltv() { #[test] fn test_create_tx_no_rbf_csv() { let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -603,7 +636,7 @@ fn test_create_tx_no_rbf_csv() { #[test] fn test_create_tx_with_default_rbf_csv() { let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -617,7 +650,7 @@ fn test_create_tx_with_default_rbf_csv() { #[test] fn test_create_tx_with_custom_rbf_csv() { let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -630,7 +663,7 @@ fn test_create_tx_with_custom_rbf_csv() { #[test] fn test_create_tx_no_rbf_cltv() { let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -641,7 +674,7 @@ fn test_create_tx_no_rbf_cltv() { #[test] fn test_create_tx_invalid_rbf_sequence() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -652,7 +685,7 @@ fn test_create_tx_invalid_rbf_sequence() { #[test] fn test_create_tx_custom_rbf_sequence() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -665,7 +698,7 @@ fn test_create_tx_custom_rbf_sequence() { #[test] fn test_create_tx_change_policy() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -689,7 +722,7 @@ fn test_create_tx_change_policy() { #[test] fn test_create_tx_default_sequence() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -709,7 +742,7 @@ macro_rules! check_fee { #[test] fn test_create_tx_drain_wallet_and_drain_to() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -728,7 +761,7 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() { let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") .unwrap() .assume_checked(); - let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let drain_addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(20_000)) @@ -757,7 +790,7 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() { #[test] fn test_create_tx_drain_to_and_utxos() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect(); let mut builder = wallet.build_tx(); builder @@ -778,7 +811,7 @@ fn test_create_tx_drain_to_and_utxos() { #[should_panic(expected = "NoRecipients")] fn test_create_tx_drain_to_no_drain_wallet_no_utxos() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let drain_addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(drain_addr.script_pubkey()); builder.finish().unwrap(); @@ -787,7 +820,7 @@ fn test_create_tx_drain_to_no_drain_wallet_no_utxos() { #[test] fn test_create_tx_default_fee_rate() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -799,7 +832,7 @@ fn test_create_tx_default_fee_rate() { #[test] fn test_create_tx_custom_fee_rate() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -813,7 +846,7 @@ fn test_create_tx_custom_fee_rate() { #[test] fn test_create_tx_absolute_fee() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .drain_to(addr.script_pubkey()) @@ -833,7 +866,7 @@ fn test_create_tx_absolute_fee() { #[test] fn test_create_tx_absolute_zero_fee() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .drain_to(addr.script_pubkey()) @@ -854,7 +887,7 @@ fn test_create_tx_absolute_zero_fee() { #[should_panic(expected = "InsufficientFunds")] fn test_create_tx_absolute_high_fee() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .drain_to(addr.script_pubkey()) @@ -868,7 +901,7 @@ fn test_create_tx_add_change() { use bdk_wallet::wallet::tx_builder::TxOrdering; let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -887,7 +920,7 @@ fn test_create_tx_add_change() { #[test] fn test_create_tx_skip_change_dust() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(49_800)); let psbt = builder.finish().unwrap(); @@ -902,7 +935,7 @@ fn test_create_tx_skip_change_dust() { #[should_panic(expected = "InsufficientFunds")] fn test_create_tx_drain_to_dust_amount() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); // very high fee rate, so that the only output would be below dust let mut builder = wallet.build_tx(); builder @@ -915,7 +948,7 @@ fn test_create_tx_drain_to_dust_amount() { #[test] fn test_create_tx_ordering_respected() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) @@ -936,7 +969,7 @@ fn test_create_tx_ordering_respected() { #[test] fn test_create_tx_default_sighash() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)); let psbt = builder.finish().unwrap(); @@ -947,7 +980,7 @@ fn test_create_tx_default_sighash() { #[test] fn test_create_tx_custom_sighash() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) @@ -966,7 +999,7 @@ fn test_create_tx_input_hd_keypaths() { use core::str::FromStr; let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -988,7 +1021,7 @@ fn test_create_tx_output_hd_keypaths() { let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1010,7 +1043,7 @@ fn test_create_tx_set_redeem_script_p2sh() { let (mut wallet, _) = get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1033,7 +1066,7 @@ fn test_create_tx_set_witness_script_p2wsh() { let (mut wallet, _) = get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1054,7 +1087,7 @@ fn test_create_tx_set_witness_script_p2wsh() { fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() { let (mut wallet, _) = get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1072,7 +1105,7 @@ fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() { fn test_create_tx_non_witness_utxo() { let (mut wallet, _) = get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1085,7 +1118,7 @@ fn test_create_tx_non_witness_utxo() { fn test_create_tx_only_witness_utxo() { let (mut wallet, _) = get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .drain_to(addr.script_pubkey()) @@ -1101,7 +1134,7 @@ fn test_create_tx_only_witness_utxo() { fn test_create_tx_shwpkh_has_witness_utxo() { let (mut wallet, _) = get_funded_wallet("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1113,7 +1146,7 @@ fn test_create_tx_shwpkh_has_witness_utxo() { fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() { let (mut wallet, _) = get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1130,7 +1163,6 @@ fn test_create_tx_add_utxo() { output: vec![TxOut { script_pubkey: wallet .next_unused_address(KeychainKind::External) - .unwrap() .script_pubkey(), value: Amount::from_sat(25_000), }], @@ -1180,7 +1212,6 @@ fn test_create_tx_manually_selected_insufficient() { output: vec![TxOut { script_pubkey: wallet .next_unused_address(KeychainKind::External) - .unwrap() .script_pubkey(), value: Amount::from_sat(25_000), }], @@ -1226,7 +1257,7 @@ fn test_create_tx_policy_path_required() { #[test] fn test_create_tx_policy_path_no_csv() { let (desc, change_desc) = get_test_wpkh_with_change_desc(); - let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Regtest).unwrap(); + let mut wallet = Wallet::new(desc, change_desc, Network::Regtest).expect("wallet"); let tx = Transaction { version: transaction::Version::non_standard(0), @@ -1235,7 +1266,6 @@ fn test_create_tx_policy_path_no_csv() { output: vec![TxOut { script_pubkey: wallet .next_unused_address(KeychainKind::External) - .unwrap() .script_pubkey(), value: Amount::from_sat(50_000), }], @@ -1309,7 +1339,7 @@ fn test_create_tx_global_xpubs_with_origin() { use bitcoin::hex::FromHex; let (mut wallet, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -1572,7 +1602,7 @@ fn test_get_psbt_input() { )] fn test_create_tx_global_xpubs_origin_missing() { let (mut wallet, _) = get_funded_wallet("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -1586,7 +1616,7 @@ fn test_create_tx_global_xpubs_master_without_origin() { use bitcoin::hex::FromHex; let (mut wallet, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -1607,7 +1637,7 @@ fn test_create_tx_global_xpubs_master_without_origin() { #[should_panic(expected = "IrreplaceableTransaction")] fn test_bump_fee_irreplaceable_tx() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -1624,7 +1654,7 @@ fn test_bump_fee_irreplaceable_tx() { #[should_panic(expected = "TransactionConfirmed")] fn test_bump_fee_confirmed_tx() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -1648,7 +1678,7 @@ fn test_bump_fee_confirmed_tx() { #[test] fn test_bump_fee_low_fee_rate() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -1682,7 +1712,7 @@ fn test_bump_fee_low_fee_rate() { #[should_panic(expected = "FeeTooLow")] fn test_bump_fee_low_abs() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -1705,7 +1735,7 @@ fn test_bump_fee_low_abs() { #[should_panic(expected = "FeeTooLow")] fn test_bump_fee_zero_abs() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) @@ -1927,7 +1957,6 @@ fn test_bump_fee_drain_wallet() { output: vec![TxOut { script_pubkey: wallet .next_unused_address(KeychainKind::External) - .unwrap() .script_pubkey(), value: Amount::from_sat(25_000), }], @@ -1993,7 +2022,6 @@ fn test_bump_fee_remove_output_manually_selected_only() { output: vec![TxOut { script_pubkey: wallet .next_unused_address(KeychainKind::External) - .unwrap() .script_pubkey(), value: Amount::from_sat(25_000), }], @@ -2050,7 +2078,6 @@ fn test_bump_fee_add_input() { output: vec![TxOut { script_pubkey: wallet .next_unused_address(KeychainKind::External) - .unwrap() .script_pubkey(), value: Amount::from_sat(25_000), }], @@ -2568,7 +2595,7 @@ fn test_fee_amount_negative_drain_val() { #[test] fn test_sign_single_xprv() { let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2583,7 +2610,7 @@ fn test_sign_single_xprv() { #[test] fn test_sign_single_xprv_with_master_fingerprint_and_path() { let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2598,7 +2625,7 @@ fn test_sign_single_xprv_with_master_fingerprint_and_path() { #[test] fn test_sign_single_xprv_bip44_path() { let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2613,7 +2640,7 @@ fn test_sign_single_xprv_bip44_path() { #[test] fn test_sign_single_xprv_sh_wpkh() { let (mut wallet, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2629,7 +2656,7 @@ fn test_sign_single_xprv_sh_wpkh() { fn test_sign_single_wif() { let (mut wallet, _) = get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2644,7 +2671,7 @@ fn test_sign_single_wif() { #[test] fn test_sign_single_xprv_no_hd_keypaths() { let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2731,7 +2758,7 @@ fn test_remove_partial_sigs_after_finalize_sign_option() { let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); for remove_partial_sigs in &[true, false] { - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2761,7 +2788,7 @@ fn test_try_finalize_sign_option() { let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); for try_finalize in &[true, false] { - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2795,7 +2822,7 @@ fn test_sign_nonstandard_sighash() { let sighash = EcdsaSighashType::NonePlusAnyoneCanPay; let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .drain_to(addr.script_pubkey()) @@ -2840,7 +2867,7 @@ fn test_sign_nonstandard_sighash() { fn test_unused_address() { let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; let change_desc = get_test_wpkh(); - let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Testnet).unwrap(); + let mut wallet = Wallet::new(desc, change_desc, Network::Testnet).expect("wallet"); // `list_unused_addresses` should be empty if we haven't revealed any assert!(wallet @@ -2851,7 +2878,6 @@ fn test_unused_address() { assert_eq!( wallet .next_unused_address(KeychainKind::External) - .unwrap() .to_string(), "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" ); @@ -2869,13 +2895,12 @@ fn test_unused_address() { fn test_next_unused_address() { let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; let change = get_test_wpkh(); - let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Testnet).unwrap(); + let mut wallet = Wallet::new(descriptor, change, Network::Testnet).expect("wallet"); assert_eq!(wallet.derivation_index(KeychainKind::External), None); assert_eq!( wallet .next_unused_address(KeychainKind::External) - .unwrap() .to_string(), "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" ); @@ -2884,7 +2909,6 @@ fn test_next_unused_address() { assert_eq!( wallet .next_unused_address(KeychainKind::External) - .unwrap() .to_string(), "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" ); @@ -2892,11 +2916,11 @@ fn test_next_unused_address() { // test mark used / unused assert!(wallet.mark_used(KeychainKind::External, 0)); - let next_unused_addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let next_unused_addr = wallet.next_unused_address(KeychainKind::External); assert_eq!(next_unused_addr.index, 1); assert!(wallet.unmark_used(KeychainKind::External, 0)); - let next_unused_addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let next_unused_addr = wallet.next_unused_address(KeychainKind::External); assert_eq!(next_unused_addr.index, 0); // use the above address @@ -2905,7 +2929,6 @@ fn test_next_unused_address() { assert_eq!( wallet .next_unused_address(KeychainKind::External) - .unwrap() .to_string(), "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" ); @@ -2919,7 +2942,7 @@ fn test_next_unused_address() { fn test_peek_address_at_index() { let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; let change_desc = get_test_wpkh(); - let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Testnet).unwrap(); + let mut wallet = Wallet::new(desc, change_desc, Network::Testnet).unwrap(); assert_eq!( wallet.peek_address(KeychainKind::External, 1).to_string(), @@ -2940,7 +2963,6 @@ fn test_peek_address_at_index() { assert_eq!( wallet .reveal_next_address(KeychainKind::External) - .unwrap() .to_string(), "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" ); @@ -2948,7 +2970,6 @@ fn test_peek_address_at_index() { assert_eq!( wallet .reveal_next_address(KeychainKind::External) - .unwrap() .to_string(), "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" ); @@ -2956,7 +2977,7 @@ fn test_peek_address_at_index() { #[test] fn test_peek_address_at_index_not_derivable() { - let wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)", + let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)", get_test_wpkh(), Network::Testnet).unwrap(); assert_eq!( @@ -2977,12 +2998,12 @@ fn test_peek_address_at_index_not_derivable() { #[test] fn test_returns_index_and_address() { - let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", + let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", get_test_wpkh(), Network::Testnet).unwrap(); // new index 0 assert_eq!( - wallet.reveal_next_address(KeychainKind::External).unwrap(), + wallet.reveal_next_address(KeychainKind::External), AddressInfo { index: 0, address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a") @@ -2994,7 +3015,7 @@ fn test_returns_index_and_address() { // new index 1 assert_eq!( - wallet.reveal_next_address(KeychainKind::External).unwrap(), + wallet.reveal_next_address(KeychainKind::External), AddressInfo { index: 1, address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7") @@ -3018,7 +3039,7 @@ fn test_returns_index_and_address() { // new index 2 assert_eq!( - wallet.reveal_next_address(KeychainKind::External).unwrap(), + wallet.reveal_next_address(KeychainKind::External), AddressInfo { index: 2, address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2") @@ -3044,7 +3065,7 @@ fn test_sending_to_bip350_bech32m_address() { fn test_get_address() { use bdk_wallet::descriptor::template::Bip84; let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); - let wallet = Wallet::new_no_persist( + let wallet = Wallet::new( Bip84(key, KeychainKind::External), Bip84(key, KeychainKind::Internal), Network::Regtest, @@ -3077,14 +3098,10 @@ fn test_get_address() { #[test] fn test_reveal_addresses() { let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc(); - let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Signet).unwrap(); + let mut wallet = Wallet::new(desc, change_desc, Network::Signet).unwrap(); let keychain = KeychainKind::External; - let last_revealed_addr = wallet - .reveal_addresses_to(keychain, 9) - .unwrap() - .last() - .unwrap(); + let last_revealed_addr = wallet.reveal_addresses_to(keychain, 9).last().unwrap(); assert_eq!(wallet.derivation_index(keychain), Some(9)); let unused_addrs = wallet.list_unused_addresses(keychain).collect::>(); @@ -3092,7 +3109,7 @@ fn test_reveal_addresses() { assert_eq!(unused_addrs.last().unwrap(), &last_revealed_addr); // revealing to an already revealed index returns nothing - let mut already_revealed = wallet.reveal_addresses_to(keychain, 9).unwrap(); + let mut already_revealed = wallet.reveal_addresses_to(keychain, 9); assert!(already_revealed.next().is_none()); } @@ -3102,7 +3119,7 @@ fn test_get_address_no_reuse() { use std::collections::HashSet; let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); - let mut wallet = Wallet::new_no_persist( + let mut wallet = Wallet::new( Bip84(key, KeychainKind::External), Bip84(key, KeychainKind::Internal), Network::Regtest, @@ -3112,16 +3129,10 @@ fn test_get_address_no_reuse() { let mut used_set = HashSet::new(); (0..3).for_each(|_| { - let external_addr = wallet - .reveal_next_address(KeychainKind::External) - .unwrap() - .address; + let external_addr = wallet.reveal_next_address(KeychainKind::External).address; assert!(used_set.insert(external_addr)); - let internal_addr = wallet - .reveal_next_address(KeychainKind::Internal) - .unwrap() - .address; + let internal_addr = wallet.reveal_next_address(KeychainKind::Internal).address; assert!(used_set.insert(internal_addr)); }); } @@ -3130,7 +3141,7 @@ fn test_get_address_no_reuse() { fn test_taproot_remove_tapfields_after_finalize_sign_option() { let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -3156,7 +3167,7 @@ fn test_taproot_remove_tapfields_after_finalize_sign_option() { fn test_taproot_psbt_populate_tap_key_origins() { let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc(); let (mut wallet, _) = get_funded_wallet_with_change(desc, change_desc); - let addr = wallet.reveal_next_address(KeychainKind::External).unwrap(); + let addr = wallet.reveal_next_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); @@ -3192,7 +3203,7 @@ fn test_taproot_psbt_populate_tap_key_origins() { fn test_taproot_psbt_populate_tap_key_origins_repeated_key() { let (mut wallet, _) = get_funded_wallet_with_change(get_test_tr_repeated_key(), get_test_tr_single_sig()); - let addr = wallet.reveal_next_address(KeychainKind::External).unwrap(); + let addr = wallet.reveal_next_address(KeychainKind::External); let path = vec![("rn4nre9c".to_string(), vec![0])] .into_iter() @@ -3259,7 +3270,7 @@ fn test_taproot_psbt_input_tap_tree() { use bitcoin::taproot; let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); @@ -3302,7 +3313,7 @@ fn test_taproot_psbt_input_tap_tree() { #[test] fn test_taproot_sign_missing_witness_utxo() { let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -3342,7 +3353,7 @@ fn test_taproot_sign_missing_witness_utxo() { #[test] fn test_taproot_sign_using_non_witness_utxo() { let (mut wallet, prev_txid) = get_funded_wallet(get_test_tr_single_sig()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -3410,7 +3421,7 @@ fn test_taproot_foreign_utxo() { } fn test_spend_from_wallet(mut wallet: Wallet) { - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -3434,7 +3445,7 @@ fn test_spend_from_wallet(mut wallet: Wallet) { #[test] fn test_taproot_no_key_spend() { let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -3469,7 +3480,7 @@ fn test_taproot_script_spend() { fn test_taproot_script_spend_sign_all_leaves() { use bdk_wallet::signer::TapLeavesOptions; let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -3500,7 +3511,7 @@ fn test_taproot_script_spend_sign_include_some_leaves() { use bitcoin::taproot::TapLeafHash; let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -3540,7 +3551,7 @@ fn test_taproot_script_spend_sign_exclude_some_leaves() { use bitcoin::taproot::TapLeafHash; let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -3578,7 +3589,7 @@ fn test_taproot_script_spend_sign_exclude_some_leaves() { fn test_taproot_script_spend_sign_no_leaves() { use bdk_wallet::signer::TapLeavesOptions; let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -3601,14 +3612,14 @@ fn test_taproot_script_spend_sign_no_leaves() { fn test_taproot_sign_derive_index_from_psbt() { let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let mut psbt = builder.finish().unwrap(); // re-create the wallet with an empty db - let wallet_empty = Wallet::new_no_persist( + let wallet_empty = Wallet::new( get_test_tr_single_sig_xprv(), get_test_tr_single_sig(), Network::Regtest, @@ -3626,7 +3637,7 @@ fn test_taproot_sign_derive_index_from_psbt() { #[test] fn test_taproot_sign_explicit_sighash_all() { let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .drain_to(addr.script_pubkey()) @@ -3646,7 +3657,7 @@ fn test_taproot_sign_non_default_sighash() { let sighash = TapSighashType::NonePlusAnyoneCanPay; let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder .drain_to(addr.script_pubkey()) @@ -3713,7 +3724,7 @@ fn test_taproot_sign_non_default_sighash() { #[test] fn test_spend_coinbase() { let (desc, change_desc) = get_test_wpkh_with_change_desc(); - let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Regtest).unwrap(); + let mut wallet = Wallet::new(desc, change_desc, Network::Regtest).unwrap(); let confirmation_height = 5; wallet @@ -3732,7 +3743,6 @@ fn test_spend_coinbase() { output: vec![TxOut { script_pubkey: wallet .next_unused_address(KeychainKind::External) - .unwrap() .script_pubkey(), value: Amount::from_sat(25_000), }], @@ -3822,7 +3832,7 @@ fn test_spend_coinbase() { fn test_allow_dust_limit() { let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv()); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); @@ -3848,7 +3858,7 @@ fn test_fee_rate_sign_no_grinding_high_r() { // instead of 70). We then check that our fee rate and fee calculation is // alright. let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); let mut builder = wallet.build_tx(); let mut data = PushBytesBuf::try_from(vec![0]).unwrap(); @@ -3914,7 +3924,7 @@ fn test_fee_rate_sign_grinding_low_r() { // We then check that our fee rate and fee calculation is alright and that our // signature is 70 bytes. let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); + let addr = wallet.next_unused_address(KeychainKind::External); let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); let mut builder = wallet.build_tx(); builder diff --git a/example-crates/example_cli/Cargo.toml b/example-crates/example_cli/Cargo.toml index 42a0b51b09..c85d2e9963 100644 --- a/example-crates/example_cli/Cargo.toml +++ b/example-crates/example_cli/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dependencies] bdk_chain = { path = "../../crates/chain", features = ["serde", "miniscript"]} -bdk_persist = { path = "../../crates/persist" } bdk_file_store = { path = "../../crates/file_store" } bdk_tmp_plan = { path = "../../nursery/tmp_plan" } bdk_coin_select = { path = "../../nursery/coin_select" } diff --git a/example-crates/example_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 319c23805f..7c6d1e1d56 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -3,6 +3,7 @@ use anyhow::Context; use bdk_coin_select::{coin_select_bnb, CoinSelector, CoinSelectorOpt, WeightedValue}; use bdk_file_store::Store; use serde::{de::DeserializeOwned, Serialize}; +use std::fmt::Debug; use std::{cmp::Reverse, collections::BTreeMap, path::PathBuf, sync::Mutex, time::Duration}; use bdk_chain::{ @@ -22,9 +23,9 @@ use bdk_chain::{ Anchor, Append, ChainOracle, DescriptorExt, FullTxOut, }; pub use bdk_file_store; -use bdk_persist::{Persist, PersistBackend}; pub use clap; +use bdk_chain::persist::{PersistBackend, StagedPersistBackend}; use clap::{Parser, Subcommand}; pub type KeychainTxGraph = IndexedTxGraph>; @@ -449,7 +450,7 @@ pub fn planned_utxos( graph: &Mutex>, - db: &Mutex>, + db: &Mutex>>, chain: &Mutex, keymap: &BTreeMap, network: Network, @@ -458,7 +459,14 @@ pub fn handle_commands anyhow::Result<()> where O::Error: std::error::Error + Send + Sync + 'static, - C: Default + Append + DeserializeOwned + Serialize + From>, + C: Default + + Append + + DeserializeOwned + + Serialize + + From> + + Send + + Sync + + Debug, { match cmd { Commands::ChainSpecific(_) => unreachable!("example code should handle this!"), @@ -669,7 +677,10 @@ where } /// The initial state returned by [`init`]. -pub struct Init { +pub struct Init +where + C: Default + Append + Serialize + DeserializeOwned + Debug + Send + Sync + 'static, +{ /// Arguments parsed by the cli. pub args: Args, /// Descriptor keymap. @@ -677,7 +688,7 @@ pub struct Init { /// Keychain-txout index. pub index: KeychainTxOutIndex, /// Persistence backend. - pub db: Mutex>, + pub db: Mutex>>, /// Initial changeset. pub init_changeset: C, } @@ -693,6 +704,7 @@ where + Append + Serialize + DeserializeOwned + + Debug + core::marker::Send + core::marker::Sync + 'static, @@ -727,13 +739,13 @@ where Err(err) => return Err(anyhow::anyhow!("failed to init db backend: {:?}", err)), }; - let init_changeset = db_backend.load_from_persistence()?.unwrap_or_default(); + let init_changeset = db_backend.load_changes()?.unwrap_or_default(); Ok(Init { args, keymap, index, - db: Mutex::new(Persist::new(db_backend)), + db: Mutex::new(StagedPersistBackend::new(db_backend)), init_changeset, }) } diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index e6c01c20be..e40ce835ab 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -3,6 +3,7 @@ const SEND_AMOUNT: Amount = Amount::from_sat(5000); const STOP_GAP: usize = 50; const BATCH_SIZE: usize = 5; +use anyhow::anyhow; use std::io::Write; use std::str::FromStr; @@ -11,24 +12,28 @@ use bdk_electrum::BdkElectrumClient; use bdk_file_store::Store; use bdk_wallet::bitcoin::{Address, Amount}; use bdk_wallet::chain::collections::HashSet; +use bdk_wallet::chain::persist::PersistBackend; use bdk_wallet::{bitcoin::Network, Wallet}; use bdk_wallet::{KeychainKind, SignOptions}; fn main() -> Result<(), anyhow::Error> { let db_path = std::env::temp_dir().join("bdk-electrum-example"); - let db = + let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?; let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; - + let changeset = db + .load_changes() + .map_err(|e| anyhow!("load changes error: {}", e))?; let mut wallet = Wallet::new_or_load( external_descriptor, internal_descriptor, - db, + changeset, Network::Testnet, )?; - let address = wallet.next_unused_address(KeychainKind::External)?; + let address = wallet.next_unused_address(KeychainKind::External); + db.write_changes(&wallet.take_staged())?; println!("Generated Address: {}", address); let balance = wallet.balance(); @@ -67,7 +72,7 @@ fn main() -> Result<(), anyhow::Error> { println!(); wallet.apply_update(update)?; - wallet.commit()?; + db.write_changes(&wallet.take_staged())?; let balance = wallet.balance(); println!("Wallet balance after syncing: {} sats", balance.total()); diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index f46779b3c1..2948673424 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -7,6 +7,7 @@ use bdk_wallet::{ }; use bdk_sqlite::{rusqlite::Connection, Store}; +use bdk_wallet::chain::persist::PersistBackend; const SEND_AMOUNT: Amount = Amount::from_sat(5000); const STOP_GAP: usize = 50; @@ -16,18 +17,20 @@ const PARALLEL_REQUESTS: usize = 5; async fn main() -> Result<(), anyhow::Error> { let db_path = "bdk-esplora-async-example.sqlite"; let conn = Connection::open(db_path)?; - let db = Store::new(conn)?; + let mut db = Store::new(conn)?; let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; + let changeset = db.load_changes()?; let mut wallet = Wallet::new_or_load( external_descriptor, internal_descriptor, - db, + changeset, Network::Signet, )?; - let address = wallet.next_unused_address(KeychainKind::External)?; + let address = wallet.next_unused_address(KeychainKind::External); + db.write_changes(&wallet.take_staged())?; println!("Generated Address: {}", address); let balance = wallet.balance(); @@ -75,7 +78,7 @@ async fn main() -> Result<(), anyhow::Error> { let _ = update.graph_update.update_last_seen_unconfirmed(now); wallet.apply_update(update)?; - wallet.commit()?; + db.write_changes(&wallet.take_staged())?; println!(); let balance = wallet.balance(); diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index 92caca2c9b..9325011d28 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -7,6 +7,7 @@ use std::{collections::BTreeSet, io::Write, str::FromStr}; use bdk_esplora::{esplora_client, EsploraExt}; use bdk_file_store::Store; +use bdk_wallet::chain::persist::PersistBackend; use bdk_wallet::{ bitcoin::{Address, Amount, Network}, KeychainKind, SignOptions, Wallet, @@ -14,19 +15,21 @@ use bdk_wallet::{ fn main() -> Result<(), anyhow::Error> { let db_path = std::env::temp_dir().join("bdk-esplora-example"); - let db = + let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?; let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; + let changeset = db.load_changes()?; let mut wallet = Wallet::new_or_load( external_descriptor, internal_descriptor, - db, + changeset, Network::Testnet, )?; - let address = wallet.next_unused_address(KeychainKind::External)?; + let address = wallet.next_unused_address(KeychainKind::External); + db.write_changes(&wallet.take_staged())?; println!("Generated Address: {}", address); let balance = wallet.balance(); @@ -52,7 +55,7 @@ fn main() -> Result<(), anyhow::Error> { let _ = update.graph_update.update_last_seen_unconfirmed(now); wallet.apply_update(update)?; - wallet.commit()?; + db.write_changes(&wallet.take_staged())?; println!(); let balance = wallet.balance(); diff --git a/example-crates/wallet_rpc/src/main.rs b/example-crates/wallet_rpc/src/main.rs index a64f0539e9..f5d12f8a60 100644 --- a/example-crates/wallet_rpc/src/main.rs +++ b/example-crates/wallet_rpc/src/main.rs @@ -3,6 +3,7 @@ use bdk_bitcoind_rpc::{ Emitter, }; use bdk_file_store::Store; +use bdk_wallet::chain::persist::PersistBackend; use bdk_wallet::{ bitcoin::{Block, Network, Transaction}, wallet::Wallet, @@ -86,13 +87,16 @@ fn main() -> anyhow::Result<()> { ); let start_load_wallet = Instant::now(); + let mut db = Store::::open_or_create_new( + DB_MAGIC.as_bytes(), + args.db_path, + )?; + let changeset = db.load_changes()?; + let mut wallet = Wallet::new_or_load( &args.descriptor, &args.change_descriptor, - Store::::open_or_create_new( - DB_MAGIC.as_bytes(), - args.db_path, - )?, + changeset, args.network, )?; println!( @@ -143,7 +147,7 @@ fn main() -> anyhow::Result<()> { let connected_to = block_emission.connected_to(); let start_apply_block = Instant::now(); wallet.apply_block_connected_to(&block_emission.block, height, connected_to)?; - wallet.commit()?; + db.write_changes(&wallet.take_staged())?; let elapsed = start_apply_block.elapsed().as_secs_f32(); println!( "Applied block {} at height {} in {}s", @@ -153,7 +157,7 @@ fn main() -> anyhow::Result<()> { Emission::Mempool(mempool_emission) => { let start_apply_mempool = Instant::now(); wallet.apply_unconfirmed_txs(mempool_emission.iter().map(|(tx, time)| (tx, *time))); - wallet.commit()?; + db.write_changes(&wallet.take_staged())?; println!( "Applied unconfirmed transactions in {}s", start_apply_mempool.elapsed().as_secs_f32()