diff --git a/crates/chain/Cargo.toml b/crates/chain/Cargo.toml index 293a24458..8b4a3c328 100644 --- a/crates/chain/Cargo.toml +++ b/crates/chain/Cargo.toml @@ -19,7 +19,6 @@ serde_crate = { package = "serde", version = "1", optional = true, features = [" # Use hashbrown as a feature flag to have HashSet and HashMap from it. hashbrown = { version = "0.9.1", optional = true, features = ["serde"] } miniscript = { version = "12.0.0", optional = true, default-features = false } -async-trait = { version = "0.1.80", optional = true } [dev-dependencies] rand = "0.8" @@ -29,4 +28,3 @@ proptest = "1.2.0" default = ["std", "miniscript"] std = ["bitcoin/std", "miniscript?/std"] serde = ["serde_crate", "bitcoin/serde", "miniscript?/serde"] -async = ["async-trait"] diff --git a/crates/chain/src/changeset.rs b/crates/chain/src/changeset.rs new file mode 100644 index 000000000..d84228409 --- /dev/null +++ b/crates/chain/src/changeset.rs @@ -0,0 +1,89 @@ +/// A changeset containing [`crate`] structures typically persisted together. +#[cfg(feature = "miniscript")] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(crate::serde::Deserialize, crate::serde::Serialize), + serde( + crate = "crate::serde", + bound( + deserialize = "A: Ord + crate::serde::Deserialize<'de>, K: Ord + crate::serde::Deserialize<'de>", + serialize = "A: Ord + crate::serde::Serialize, K: Ord + crate::serde::Serialize", + ), + ) +)] +pub struct CombinedChangeSet { + /// Changes to the [`LocalChain`](crate::local_chain::LocalChain). + pub chain: crate::local_chain::ChangeSet, + /// Changes to [`IndexedTxGraph`](crate::indexed_tx_graph::IndexedTxGraph). + pub indexed_tx_graph: crate::indexed_tx_graph::ChangeSet>, + /// Stores the network type of the transaction data. + pub network: Option, +} + +#[cfg(feature = "miniscript")] +impl core::default::Default for CombinedChangeSet { + fn default() -> Self { + Self { + chain: core::default::Default::default(), + indexed_tx_graph: core::default::Default::default(), + network: None, + } + } +} + +#[cfg(feature = "miniscript")] +impl crate::Append for CombinedChangeSet { + fn append(&mut self, other: Self) { + crate::Append::append(&mut self.chain, other.chain); + crate::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() + } +} + +#[cfg(feature = "miniscript")] +impl From for CombinedChangeSet { + fn from(chain: crate::local_chain::ChangeSet) -> Self { + Self { + chain, + ..Default::default() + } + } +} + +#[cfg(feature = "miniscript")] +impl From>> + for CombinedChangeSet +{ + fn from( + indexed_tx_graph: crate::indexed_tx_graph::ChangeSet>, + ) -> Self { + Self { + indexed_tx_graph, + ..Default::default() + } + } +} + +#[cfg(feature = "miniscript")] +impl From> for CombinedChangeSet { + fn from(indexer: crate::keychain::ChangeSet) -> Self { + Self { + indexed_tx_graph: crate::indexed_tx_graph::ChangeSet { + indexer, + ..Default::default() + }, + ..Default::default() + } + } +} diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index 7a5036a2f..94f81441e 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -50,7 +50,8 @@ pub use descriptor_ext::{DescriptorExt, DescriptorId}; mod spk_iter; #[cfg(feature = "miniscript")] pub use spk_iter::*; -pub mod persist; +mod changeset; +pub use changeset::*; pub mod spk_client; #[allow(unused_imports)] diff --git a/crates/chain/src/persist.rs b/crates/chain/src/persist.rs deleted file mode 100644 index 9fe69cfe3..000000000 --- a/crates/chain/src/persist.rs +++ /dev/null @@ -1,279 +0,0 @@ -//! 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 [`CombinedChangeSet`] type encapsulates a combination of [`crate`] structures that are -//! typically persisted together. - -#[cfg(feature = "async")] -use alloc::boxed::Box; -#[cfg(feature = "async")] -use async_trait::async_trait; -use core::convert::Infallible; -use core::fmt::{Debug, Display}; - -use crate::Append; - -/// A changeset containing [`crate`] structures typically persisted together. -#[derive(Debug, Clone, PartialEq)] -#[cfg(feature = "miniscript")] -#[cfg_attr( - feature = "serde", - derive(crate::serde::Deserialize, crate::serde::Serialize), - serde( - crate = "crate::serde", - bound( - deserialize = "A: Ord + crate::serde::Deserialize<'de>, K: Ord + crate::serde::Deserialize<'de>", - serialize = "A: Ord + crate::serde::Serialize, K: Ord + crate::serde::Serialize", - ), - ) -)] -pub struct CombinedChangeSet { - /// Changes to the [`LocalChain`](crate::local_chain::LocalChain). - pub chain: crate::local_chain::ChangeSet, - /// Changes to [`IndexedTxGraph`](crate::indexed_tx_graph::IndexedTxGraph). - pub indexed_tx_graph: crate::indexed_tx_graph::ChangeSet>, - /// Stores the network type of the transaction data. - pub network: Option, -} - -#[cfg(feature = "miniscript")] -impl core::default::Default for CombinedChangeSet { - fn default() -> Self { - Self { - chain: core::default::Default::default(), - indexed_tx_graph: core::default::Default::default(), - network: None, - } - } -} - -#[cfg(feature = "miniscript")] -impl crate::Append for CombinedChangeSet { - fn append(&mut self, other: Self) { - crate::Append::append(&mut self.chain, other.chain); - crate::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() - } -} - -#[cfg(feature = "miniscript")] -impl From for CombinedChangeSet { - fn from(chain: crate::local_chain::ChangeSet) -> Self { - Self { - chain, - ..Default::default() - } - } -} - -#[cfg(feature = "miniscript")] -impl From>> - for CombinedChangeSet -{ - fn from( - indexed_tx_graph: crate::indexed_tx_graph::ChangeSet>, - ) -> Self { - Self { - indexed_tx_graph, - ..Default::default() - } - } -} - -#[cfg(feature = "miniscript")] -impl From> for CombinedChangeSet { - fn from(indexer: crate::keychain::ChangeSet) -> Self { - Self { - indexed_tx_graph: crate::indexed_tx_graph::ChangeSet { - indexer, - ..Default::default() - }, - ..Default::default() - } - } -} - -/// A persistence backend for writing and loading changesets. -/// -/// `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 { - /// The error the backend returns when it fails to write. - type WriteError: Debug + Display; - - /// The error the backend returns when it fails to load changesets `C`. - type LoadError: Debug + Display; - - /// 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_changes - fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError>; - - /// Return the aggregate changeset `C` from persistence. - fn load_changes(&mut self) -> Result, Self::LoadError>; -} - -impl PersistBackend for () { - type WriteError = Infallible; - type LoadError = Infallible; - - fn write_changes(&mut self, _changeset: &C) -> Result<(), Self::WriteError> { - Ok(()) - } - - fn load_changes(&mut self) -> Result, Self::LoadError> { - Ok(None) - } -} - -#[cfg(feature = "async")] -/// An async persistence backend for writing and loading changesets. -/// -/// `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 PersistBackendAsync { - /// The error the backend returns when it fails to write. - type WriteError: Debug + Display; - - /// The error the backend returns when it fails to load changesets `C`. - type LoadError: Debug + Display; - - /// 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_changes - async fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError>; - - /// Return the aggregate changeset `C` from persistence. - async fn load_changes(&mut self) -> Result, Self::LoadError>; -} - -#[cfg(feature = "async")] -#[async_trait] -impl PersistBackendAsync for () { - type WriteError = Infallible; - type LoadError = Infallible; - - async fn write_changes(&mut self, _changeset: &C) -> Result<(), Self::WriteError> { - Ok(()) - } - - async fn load_changes(&mut self) -> Result, Self::LoadError> { - Ok(None) - } -} - -/// Extends a changeset so that it acts as a convenient staging area for any [`PersistBackend`]. -/// -/// Not all changes to the in-memory representation needs to be written to disk right away. -/// [`Append::append`] can be used to *stage* changes first and then [`StageExt::commit_to`] can be -/// used to write changes to disk. -pub trait StageExt: Append + Default + Sized { - /// Commit the staged changes to the persistence `backend`. - /// - /// Changes that are committed (if any) are returned. - /// - /// # Error - /// - /// Returns a backend-defined error if this fails. - fn commit_to(&mut self, backend: &mut B) -> Result, B::WriteError> - where - B: PersistBackend, - { - // do not do anything if changeset is empty - if self.is_empty() { - return Ok(None); - } - backend.write_changes(&*self)?; - // only clear if changes are written successfully to backend - Ok(Some(core::mem::take(self))) - } - - /// Stages a new `changeset` and commits it (alongside any other previously staged changes) to - /// the persistence `backend`. - /// - /// Convenience method for calling [`Append::append`] and then [`StageExt::commit_to`]. - fn append_and_commit_to( - &mut self, - changeset: Self, - backend: &mut B, - ) -> Result, B::WriteError> - where - B: PersistBackend, - { - Append::append(self, changeset); - self.commit_to(backend) - } -} - -impl StageExt for C {} - -/// Extends a changeset so that it acts as a convenient staging area for any -/// [`PersistBackendAsync`]. -/// -/// Not all changes to the in-memory representation needs to be written to disk right away. -/// [`Append::append`] can be used to *stage* changes first and then [`StageExtAsync::commit_to`] -/// can be used to write changes to disk. -#[cfg(feature = "async")] -#[async_trait] -pub trait StageExtAsync: Append + Default + Sized + Send + Sync { - /// Commit the staged changes to the persistence `backend`. - /// - /// Changes that are committed (if any) are returned. - /// - /// # Error - /// - /// Returns a backend-defined error if this fails. - async fn commit_to(&mut self, backend: &mut B) -> Result, B::WriteError> - where - B: PersistBackendAsync + Send + Sync, - { - // do not do anything if changeset is empty - if self.is_empty() { - return Ok(None); - } - backend.write_changes(&*self).await?; - // only clear if changes are written successfully to backend - Ok(Some(core::mem::take(self))) - } - - /// Stages a new `changeset` and commits it (alongside any other previously staged changes) to - /// the persistence `backend`. - /// - /// Convenience method for calling [`Append::append`] and then [`StageExtAsync::commit_to`]. - async fn append_and_commit_to( - &mut self, - changeset: Self, - backend: &mut B, - ) -> Result, B::WriteError> - where - B: PersistBackendAsync + Send + Sync, - { - Append::append(self, changeset); - self.commit_to(backend).await - } -} - -#[cfg(feature = "async")] -#[async_trait] -impl StageExtAsync for C {} diff --git a/crates/chain/src/tx_data_traits.rs b/crates/chain/src/tx_data_traits.rs index b3ab3515e..7944f95c0 100644 --- a/crates/chain/src/tx_data_traits.rs +++ b/crates/chain/src/tx_data_traits.rs @@ -114,12 +114,21 @@ pub trait AnchorFromBlockPosition: Anchor { } /// Trait that makes an object appendable. -pub trait Append { +pub trait Append: Default { /// Append another object of the same type onto `self`. fn append(&mut self, other: Self); /// Returns whether the structure is considered empty. fn is_empty(&self) -> bool; + + /// Take the value, replacing it with the default value. + fn take(&mut self) -> Option { + if self.is_empty() { + None + } else { + Some(core::mem::take(self)) + } + } } impl Append for BTreeMap { diff --git a/crates/file_store/Cargo.toml b/crates/file_store/Cargo.toml index 392424cb3..a0df36cbc 100644 --- a/crates/file_store/Cargo.toml +++ b/crates/file_store/Cargo.toml @@ -5,7 +5,7 @@ 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 PersistBackend for Bitcoin Dev Kit." +description = "A simple append-only flat file database for persisting bdk_chain data." keywords = ["bitcoin", "persist", "persistence", "bdk", "file"] authors = ["Bitcoin Dev Kit Developers"] readme = "README.md" diff --git a/crates/file_store/README.md b/crates/file_store/README.md index cad196b2d..7b00aa0e0 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_chain::persist::PersistBackend). +This is a simple append-only flat file database for persisting [`bdk_chain`] changesets. 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 2f451db72..26a08809d 100644 --- a/crates/file_store/src/store.rs +++ b/crates/file_store/src/store.rs @@ -1,5 +1,4 @@ use crate::{bincode_options, EntryIter, FileError, IterError}; -use bdk_chain::persist::PersistBackend; use bdk_chain::Append; use bincode::Options; use std::{ @@ -21,27 +20,6 @@ where marker: PhantomData, } -impl PersistBackend for Store -where - C: Append - + Debug - + serde::Serialize - + serde::de::DeserializeOwned - + core::marker::Send - + core::marker::Sync, -{ - type WriteError = io::Error; - type LoadError = AggregateChangesetsError; - - fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> { - self.append_changeset(changeset) - } - - fn load_changes(&mut self) -> Result, Self::LoadError> { - self.aggregate_changesets() - } -} - impl Store where C: Append @@ -448,7 +426,7 @@ mod test { acc }); // We write after a short read. - db.write_changes(&last_changeset) + db.append_changeset(&last_changeset) .expect("last write must succeed"); Append::append(&mut exp_aggregation, last_changeset.clone()); drop(db); diff --git a/crates/sqlite/Cargo.toml b/crates/sqlite/Cargo.toml index 238c818f7..8dff341a9 100644 --- a/crates/sqlite/Cargo.toml +++ b/crates/sqlite/Cargo.toml @@ -5,7 +5,7 @@ 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 PersistBackend for Bitcoin Dev Kit." +description = "A simple SQLite relational database client for persisting bdk_chain data." keywords = ["bitcoin", "persist", "persistence", "bdk", "sqlite"] authors = ["Bitcoin Dev Kit Developers"] readme = "README.md" diff --git a/crates/sqlite/README.md b/crates/sqlite/README.md index 26f4650bc..ba612bded 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`. +This is a simple [SQLite] relational database client for persisting [`bdk_chain`] changesets. The main structure is `Store` which persists `CombinedChangeSet` data into a SQLite database file. - +[`bdk_chain`]:https://docs.rs/bdk_chain/latest/bdk_chain/ [SQLite]: https://www.sqlite.org/index.html diff --git a/crates/sqlite/src/store.rs b/crates/sqlite/src/store.rs index 5bead7bc9..4cd81f50b 100644 --- a/crates/sqlite/src/store.rs +++ b/crates/sqlite/src/store.rs @@ -12,7 +12,7 @@ use std::str::FromStr; use std::sync::{Arc, Mutex}; use crate::Error; -use bdk_chain::persist::{CombinedChangeSet, PersistBackend}; +use bdk_chain::CombinedChangeSet; use bdk_chain::{ indexed_tx_graph, keychain, local_chain, tx_graph, Anchor, Append, DescriptorExt, DescriptorId, }; @@ -57,26 +57,6 @@ where } } -impl PersistBackend> for Store -where - K: Ord + for<'de> Deserialize<'de> + Serialize + Send, - A: Anchor + for<'de> Deserialize<'de> + Serialize + Send, -{ - type WriteError = Error; - type LoadError = Error; - - fn write_changes( - &mut self, - changeset: &CombinedChangeSet, - ) -> Result<(), Self::WriteError> { - self.write(changeset) - } - - fn load_changes(&mut self) -> Result>, Self::LoadError> { - self.read() - } -} - /// Network table related functions. impl Store { /// Insert [`Network`] for which all other tables data is valid. @@ -482,13 +462,14 @@ where } } -/// Functions to read and write all [`ChangeSet`] data. +/// Functions to read and write all [`CombinedChangeSet`] data. impl Store where K: Ord + for<'de> Deserialize<'de> + Serialize + Send, A: Anchor + for<'de> Deserialize<'de> + Serialize + Send, { - fn write(&mut self, changeset: &CombinedChangeSet) -> Result<(), Error> { + /// Write the given `changeset` atomically. + pub fn write(&mut self, changeset: &CombinedChangeSet) -> Result<(), Error> { // no need to write anything if changeset is empty if changeset.is_empty() { return Ok(()); @@ -513,7 +494,8 @@ where db_transaction.commit().map_err(Error::Sqlite) } - fn read(&mut self) -> Result>, Error> { + /// Read the entire database and return the aggregate [`CombinedChangeSet`]. + pub fn read(&mut self) -> Result>, Error> { let db_transaction = self.db_transaction()?; let network = Self::select_network(&db_transaction)?; @@ -563,7 +545,7 @@ 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::CombinedChangeSet; use bdk_chain::{ indexed_tx_graph, keychain, tx_graph, BlockId, ConfirmationHeightAnchor, ConfirmationTimeHeightAnchor, DescriptorExt, @@ -591,10 +573,10 @@ mod test { .expect("create new memory db store"); test_changesets.iter().for_each(|changeset| { - store.write_changes(changeset).expect("write changeset"); + store.write(changeset).expect("write changeset"); }); - let agg_changeset = store.load_changes().expect("aggregated changeset"); + let agg_changeset = store.read().expect("aggregated changeset"); assert_eq!(agg_changeset, Some(agg_test_changesets)); } @@ -612,10 +594,10 @@ mod test { .expect("create new memory db store"); test_changesets.iter().for_each(|changeset| { - store.write_changes(changeset).expect("write changeset"); + store.write(changeset).expect("write changeset"); }); - let agg_changeset = store.load_changes().expect("aggregated changeset"); + let agg_changeset = store.read().expect("aggregated changeset"); assert_eq!(agg_changeset, Some(agg_test_changesets)); } @@ -629,10 +611,10 @@ mod test { let mut store = Store::::new(conn).expect("create new memory db store"); test_changesets.iter().for_each(|changeset| { - store.write_changes(changeset).expect("write changeset"); + store.write(changeset).expect("write changeset"); }); - let agg_changeset = store.load_changes().expect("aggregated changeset"); + let agg_changeset = store.read().expect("aggregated changeset"); assert_eq!(agg_changeset, Some(agg_test_changesets)); } diff --git a/crates/wallet/Cargo.toml b/crates/wallet/Cargo.toml index d5b52be06..d7f3290ac 100644 --- a/crates/wallet/Cargo.toml +++ b/crates/wallet/Cargo.toml @@ -30,7 +30,6 @@ js-sys = "0.3" [features] default = ["std"] std = ["bitcoin/std", "miniscript/std", "bdk_chain/std"] -async = ["bdk_chain/async"] compiler = ["miniscript/compiler"] all-keys = ["keys-bip39"] keys-bip39 = ["bip39"] diff --git a/crates/wallet/README.md b/crates/wallet/README.md index 9aba47821..ffe99474e 100644 --- a/crates/wallet/README.md +++ b/crates/wallet/README.md @@ -57,31 +57,42 @@ that the `Wallet` can use to update its view of the chain. ## Persistence -To persist `Wallet` state data on disk use an implementation of the [`PersistBackend`] trait. +To persist `Wallet` state data use a data store crate that reads and writes [`bdk_chain::CombinedChangeSet`]. **Implementations** -* [`bdk_file_store`]: A simple flat-file implementation of [`PersistBackend`]. -* [`bdk_sqlite`]: A simple sqlite implementation of [`PersistBackend`]. +* [`bdk_file_store`]: Stores wallet changes in a simple flat file. +* [`bdk_sqlite`]: Stores wallet changes in a SQLite relational database file. **Example** -```rust,compile_fail -use bdk_wallet::{bitcoin::Network, wallet::{ChangeSet, Wallet}}; +```rust,no_run +use bdk_wallet::{bitcoin::Network, KeychainKind, wallet::{ChangeSet, Wallet}}; fn main() { - // Create a new file `Store`. - let mut db = bdk_file_store::Store::::open_or_create_new(b"magic_bytes", "path/to/my_wallet.db").expect("create store"); + // Open or create a new file store for wallet data. + let mut db = + bdk_file_store::Store::::open_or_create_new(b"magic_bytes", "/tmp/my_wallet.db") + .expect("create store"); + // Create a wallet with initial wallet data read from the file store. let descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)"; 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_to(&mut db).expect("must commit changes to database"); + let changeset = db.aggregate_changesets().expect("changeset loaded"); + let mut wallet = + Wallet::new_or_load(descriptor, change_descriptor, changeset, Network::Testnet) + .expect("create or load wallet"); + + // Get a new address to receive bitcoin. + let receive_address = wallet.reveal_next_address(KeychainKind::External); + // Persist staged wallet data changes to the file store. + let staged_changeset = wallet.take_staged(); + if let Some(changeset) = staged_changeset { + db.append_changeset(&changeset) + .expect("must commit changes to database"); + } + println!("Your new receive address is: {}", receive_address.address); } ``` @@ -222,7 +233,6 @@ 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/persist/trait.PersistBackend.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 diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 5a21c1518..883e954a7 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -26,7 +26,6 @@ use bdk_chain::{ local_chain::{ self, ApplyHeaderError, CannotConnectError, CheckPoint, CheckPointIter, LocalChain, }, - persist::{PersistBackend, StageExt}, spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult}, tx_graph::{CanonicalTx, TxGraph}, Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut, @@ -85,12 +84,12 @@ 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. +/// The user is responsible for loading and writing wallet changes which are represented as +/// [`ChangeSet`]s (see [`take_staged`]). Also see individual functions and example for instructions +/// on when [`Wallet`] state needs to be persisted. /// -/// [`PersistBackend`]: bdk_chain::persist::PersistBackend /// [`signer`]: crate::signer +/// [`take_staged`]: Wallet::take_staged #[derive(Debug)] pub struct Wallet { signers: Arc, @@ -141,8 +140,7 @@ impl From for Update { } /// The changes made to a wallet by applying an [`Update`]. -pub type ChangeSet = - bdk_chain::persist::CombinedChangeSet; +pub type ChangeSet = bdk_chain::CombinedChangeSet; /// A derived address and the index it was found at. /// For convenience this automatically derefs to `Address` @@ -408,14 +406,13 @@ impl Wallet { /// # use bdk_wallet::descriptor::Descriptor; /// # use bitcoin::key::Secp256k1; /// # use bdk_wallet::KeychainKind; - /// # use bdk_sqlite::{Store, rusqlite::Connection}; + /// 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 mut db = Store::new(conn).expect("must create db"); + /// let conn = Connection::open(file_path).expect("must open connection"); + /// 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(); @@ -423,7 +420,7 @@ 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 changeset = db.load_changes()?.expect("there must be an existing changeset"); + /// let changeset = db.read()?.expect("there must be an existing changeset"); /// let mut wallet = Wallet::load_from_changeset(changeset)?; /// /// external_signer_container.signers().into_iter() @@ -482,13 +479,12 @@ impl Wallet { /// 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 bdk_sqlite::{Store, rusqlite::Connection}; /// # use bitcoin::Network::Testnet; - /// # let conn = Connection::open_in_memory().expect("must open connection"); + /// 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 changeset = db.read()?; /// /// let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; /// let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; @@ -666,16 +662,17 @@ impl Wallet { /// calls to this method before closing the wallet. For example: /// /// ```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"); + /// use bdk_sqlite::{rusqlite::Connection, Store}; + /// 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); - /// wallet.commit_to(&mut db)?; + /// if let Some(changeset) = wallet.take_staged() { + /// db.write(&changeset)?; + /// } /// /// // Now it's safe to show the user their next address! /// println!("Next address: {}", next_address.address); @@ -2283,44 +2280,18 @@ impl Wallet { Ok(()) } - /// Commits all currently [`staged`](Wallet::staged) changes to the `persist_backend`. - /// - /// This returns whether anything was persisted. - /// - /// # Error - /// - /// Returns a backend-defined error if this fails. - pub fn commit_to(&mut self, persist_backend: &mut B) -> Result - where - B: PersistBackend, - { - let committed = StageExt::commit_to(&mut self.stage, persist_backend)?; - Ok(committed.is_some()) - } - - /// Commits all currently [`staged`](Wallet::staged) changes to the async `persist_backend`. - /// - /// This returns whether anything was persisted. - /// - /// # Error - /// - /// Returns a backend-defined error if this fails. - #[cfg(feature = "async")] - pub async fn commit_to_async( - &mut self, - persist_backend: &mut B, - ) -> Result - where - B: bdk_chain::persist::PersistBackendAsync + Send + Sync, - { - let committed = - bdk_chain::persist::StageExtAsync::commit_to(&mut self.stage, persist_backend).await?; - Ok(committed.is_some()) + /// Get a reference of the staged [`ChangeSet`] that are yet to be committed (if any). + pub fn staged(&self) -> Option<&ChangeSet> { + if self.stage.is_empty() { + None + } else { + Some(&self.stage) + } } - /// Get the staged [`ChangeSet`] that is yet to be committed. - pub fn staged(&self) -> &ChangeSet { - &self.stage + /// Take the staged [`ChangeSet`] to be persisted now (if any). + pub fn take_staged(&mut self) -> Option { + self.stage.take() } /// Get a reference to the inner [`TxGraph`]. diff --git a/crates/wallet/tests/wallet.rs b/crates/wallet/tests/wallet.rs index 3ca18b760..7303bdcd8 100644 --- a/crates/wallet/tests/wallet.rs +++ b/crates/wallet/tests/wallet.rs @@ -1,11 +1,10 @@ -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::{persist::PersistBackend, BlockId, ConfirmationTime}; +use bdk_chain::{BlockId, ConfirmationTime}; use bdk_sqlite::rusqlite::Connection; use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor}; use bdk_wallet::psbt::PsbtUtils; @@ -13,7 +12,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::{AddressInfo, Balance, NewError, Wallet}; +use bdk_wallet::wallet::{AddressInfo, Balance, ChangeSet, NewError, Wallet}; use bdk_wallet::KeychainKind; use bitcoin::hashes::Hash; use bitcoin::key::Secp256k1; @@ -72,11 +71,18 @@ const DB_MAGIC: &[u8] = &[0x21, 0x24, 0x48]; #[test] fn load_recovers_wallet() -> anyhow::Result<()> { - fn run(filename: &str, create_new: FN, recover: FR) -> anyhow::Result<()> + fn run( + filename: &str, + create_new: New, + recover: Recover, + read: Read, + write: Write, + ) -> anyhow::Result<()> where - B: PersistBackend + Send + Sync + 'static, - FN: Fn(&Path) -> anyhow::Result, - FR: Fn(&Path) -> anyhow::Result, + New: Fn(&Path) -> anyhow::Result, + Recover: Fn(&Path) -> anyhow::Result, + Read: Fn(&mut Db) -> anyhow::Result>, + Write: Fn(&mut Db, &ChangeSet) -> anyhow::Result<()>, { let temp_dir = tempfile::tempdir().expect("must create tempdir"); let file_path = temp_dir.path().join(filename); @@ -91,9 +97,9 @@ fn load_recovers_wallet() -> anyhow::Result<()> { // persist new wallet changes let mut db = create_new(&file_path).expect("must create db"); - wallet - .commit_to(&mut db) - .map_err(|e| anyhow!("write changes error: {}", e))?; + if let Some(changeset) = wallet.take_staged() { + write(&mut db, &changeset)?; + } wallet.spk_index().clone() }; @@ -101,10 +107,7 @@ fn load_recovers_wallet() -> anyhow::Result<()> { { // 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 changeset = read(db).expect("must recover wallet").expect("changeset"); let wallet = Wallet::load_from_changeset(changeset).expect("must recover wallet"); assert_eq!(wallet.network(), Network::Testnet); @@ -132,11 +135,15 @@ fn load_recovers_wallet() -> anyhow::Result<()> { "store.db", |path| Ok(bdk_file_store::Store::create_new(DB_MAGIC, path)?), |path| Ok(bdk_file_store::Store::open(DB_MAGIC, path)?), + |db| Ok(bdk_file_store::Store::aggregate_changesets(db)?), + |db, changeset| Ok(bdk_file_store::Store::append_changeset(db, changeset)?), )?; run( "store.sqlite", |path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?), |path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?), + |db| Ok(bdk_sqlite::Store::read(db)?), + |db, changeset| Ok(bdk_sqlite::Store::write(db, changeset)?), )?; Ok(()) @@ -144,10 +151,16 @@ fn load_recovers_wallet() -> anyhow::Result<()> { #[test] fn new_or_load() -> anyhow::Result<()> { - fn run(filename: &str, new_or_load: F) -> anyhow::Result<()> + fn run( + filename: &str, + new_or_load: NewOrRecover, + read: Read, + write: Write, + ) -> anyhow::Result<()> where - B: PersistBackend + Send + Sync + 'static, - F: Fn(&Path) -> anyhow::Result, + NewOrRecover: Fn(&Path) -> anyhow::Result, + Read: Fn(&mut Db) -> anyhow::Result>, + Write: Fn(&mut Db, &ChangeSet) -> anyhow::Result<()>, { let temp_dir = tempfile::tempdir().expect("must create tempdir"); let file_path = temp_dir.path().join(filename); @@ -158,18 +171,16 @@ fn new_or_load() -> anyhow::Result<()> { let wallet = &mut Wallet::new_or_load(desc, change_desc, None, Network::Testnet) .expect("must init wallet"); let mut db = new_or_load(&file_path).expect("must create db"); - wallet - .commit_to(&mut db) - .map_err(|e| anyhow!("write changes error: {}", e))?; + if let Some(changeset) = wallet.take_staged() { + write(&mut db, &changeset)?; + } wallet.keychains().map(|(k, v)| (*k, v.clone())).collect() }; // wrong network { 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 changeset = read(&mut db)?; let err = Wallet::new_or_load(desc, change_desc, changeset, Network::Bitcoin) .expect_err("wrong network"); assert!( @@ -192,9 +203,7 @@ fn new_or_load() -> anyhow::Result<()> { bitcoin::blockdata::constants::genesis_block(Network::Testnet).block_hash(); 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 changeset = read(db)?; let err = Wallet::new_or_load_with_genesis_hash( desc, change_desc, @@ -223,9 +232,7 @@ fn new_or_load() -> anyhow::Result<()> { .0; 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 changeset = read(db)?; let err = Wallet::new_or_load(exp_descriptor, exp_change_desc, changeset, Network::Testnet) .expect_err("wrong external descriptor"); @@ -249,9 +256,7 @@ fn new_or_load() -> anyhow::Result<()> { .0; 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 changeset = read(db)?; let err = Wallet::new_or_load(desc, exp_descriptor, changeset, Network::Testnet) .expect_err("wrong internal descriptor"); assert!( @@ -268,9 +273,7 @@ fn new_or_load() -> anyhow::Result<()> { // all parameters match { 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 changeset = read(db)?; let wallet = Wallet::new_or_load(desc, change_desc, changeset, Network::Testnet) .expect("must recover wallet"); assert_eq!(wallet.network(), Network::Testnet); @@ -282,12 +285,18 @@ fn new_or_load() -> anyhow::Result<()> { Ok(()) } - run("store.db", |path| { - Ok(bdk_file_store::Store::open_or_create_new(DB_MAGIC, path)?) - })?; - run("store.sqlite", |path| { - Ok(bdk_sqlite::Store::new(Connection::open(path)?)?) - })?; + run( + "store.db", + |path| Ok(bdk_file_store::Store::open_or_create_new(DB_MAGIC, path)?), + |db| Ok(bdk_file_store::Store::aggregate_changesets(db)?), + |db, changeset| Ok(bdk_file_store::Store::append_changeset(db, changeset)?), + )?; + run( + "store.sqlite", + |path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?), + |db| Ok(bdk_sqlite::Store::read(db)?), + |db, changeset| Ok(bdk_sqlite::Store::write(db, changeset)?), + )?; Ok(()) } diff --git a/example-crates/example_bitcoind_rpc_polling/src/main.rs b/example-crates/example_bitcoind_rpc_polling/src/main.rs index 76b9ad799..38e796474 100644 --- a/example-crates/example_bitcoind_rpc_polling/src/main.rs +++ b/example-crates/example_bitcoind_rpc_polling/src/main.rs @@ -11,7 +11,6 @@ use bdk_bitcoind_rpc::{ bitcoincore_rpc::{Auth, Client, RpcApi}, Emitter, }; -use bdk_chain::persist::{PersistBackend, StageExt}; use bdk_chain::{ bitcoin::{constants::genesis_block, Block, Transaction}, indexed_tx_graph, keychain, @@ -138,7 +137,7 @@ fn main() -> anyhow::Result<()> { let genesis_hash = genesis_block(args.network).block_hash(); let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash); let mut db = db.lock().unwrap(); - db.write_changes(&(chain_changeset, Default::default()))?; + db.append_changeset(&(chain_changeset, Default::default()))?; chain } else { LocalChain::from_changeset(init_chain_changeset)? @@ -197,7 +196,9 @@ fn main() -> anyhow::Result<()> { if last_db_commit.elapsed() >= DB_COMMIT_DELAY { let db = &mut *db.lock().unwrap(); last_db_commit = Instant::now(); - db_stage.commit_to(db)?; + if let Some(changeset) = db_stage.take() { + db.append_changeset(&changeset)?; + } println!( "[{:>10}s] committed to db (took {}s)", start.elapsed().as_secs_f32(), @@ -233,10 +234,10 @@ fn main() -> anyhow::Result<()> { ); { let db = &mut *db.lock().unwrap(); - db_stage.append_and_commit_to( - (local_chain::ChangeSet::default(), graph_changeset), - db, - )?; + db_stage.append((local_chain::ChangeSet::default(), graph_changeset)); + if let Some(changeset) = db_stage.take() { + db.append_changeset(&changeset)?; + } } } RpcCommands::Live { rpc_args } => { @@ -324,7 +325,9 @@ fn main() -> anyhow::Result<()> { if last_db_commit.elapsed() >= DB_COMMIT_DELAY { let db = &mut *db.lock().unwrap(); last_db_commit = Instant::now(); - db_stage.commit_to(db)?; + if let Some(changeset) = db_stage.take() { + db.append_changeset(&changeset)?; + } println!( "[{:>10}s] committed to db (took {}s)", start.elapsed().as_secs_f32(), diff --git a/example-crates/example_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 6a3f43201..b1862fb81 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -25,7 +25,6 @@ use bdk_chain::{ pub use bdk_file_store; pub use clap; -use bdk_chain::persist::PersistBackend; use clap::{Parser, Subcommand}; pub type KeychainTxGraph = IndexedTxGraph>; @@ -482,7 +481,7 @@ where let ((spk_i, spk), index_changeset) = spk_chooser(index, &Keychain::External).expect("Must exist"); let db = &mut *db.lock().unwrap(); - db.write_changes(&C::from(( + db.append_changeset(&C::from(( local_chain::ChangeSet::default(), indexed_tx_graph::ChangeSet::from(index_changeset), )))?; @@ -630,7 +629,7 @@ where // If we're unable to persist this, then we don't want to broadcast. { let db = &mut *db.lock().unwrap(); - db.write_changes(&C::from(( + db.append_changeset(&C::from(( local_chain::ChangeSet::default(), indexed_tx_graph::ChangeSet::from(index_changeset), )))?; @@ -655,7 +654,7 @@ where // We know the tx is at least unconfirmed now. Note if persisting here fails, // it's not a big deal since we can always find it again form // blockchain. - db.lock().unwrap().write_changes(&C::from(( + db.lock().unwrap().append_changeset(&C::from(( local_chain::ChangeSet::default(), keychain_changeset, )))?; @@ -736,7 +735,7 @@ where Err(err) => return Err(anyhow::anyhow!("failed to init db backend: {:?}", err)), }; - let init_changeset = db_backend.load_changes()?.unwrap_or_default(); + let init_changeset = db_backend.aggregate_changesets()?.unwrap_or_default(); Ok(Init { args, diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index 2b77e97c4..4789269dd 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -3,7 +3,6 @@ use std::{ sync::Mutex, }; -use bdk_chain::persist::PersistBackend; use bdk_chain::{ bitcoin::{constants::genesis_block, Address, Network, Txid}, collections::BTreeSet, @@ -352,6 +351,6 @@ fn main() -> anyhow::Result<()> { }; let mut db = db.lock().unwrap(); - db.write_changes(&db_changeset)?; + db.append_changeset(&db_changeset)?; Ok(()) } diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs index 13e6e6abb..3f9b87210 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -4,7 +4,6 @@ use std::{ sync::Mutex, }; -use bdk_chain::persist::PersistBackend; use bdk_chain::{ bitcoin::{constants::genesis_block, Address, Network, Txid}, indexed_tx_graph::{self, IndexedTxGraph}, @@ -362,6 +361,6 @@ fn main() -> anyhow::Result<()> { // We persist the changes let mut db = db.lock().unwrap(); - db.write_changes(&(local_chain_changeset, indexed_tx_graph_changeset))?; + db.append_changeset(&(local_chain_changeset, indexed_tx_graph_changeset))?; Ok(()) } diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index c7c563c15..2adf090aa 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -12,7 +12,6 @@ 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}; @@ -23,7 +22,7 @@ fn main() -> Result<(), anyhow::Error> { let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; let changeset = db - .load_changes() + .aggregate_changesets() .map_err(|e| anyhow!("load changes error: {}", e))?; let mut wallet = Wallet::new_or_load( external_descriptor, @@ -33,7 +32,9 @@ fn main() -> Result<(), anyhow::Error> { )?; let address = wallet.next_unused_address(KeychainKind::External); - wallet.commit_to(&mut db)?; + if let Some(changeset) = wallet.take_staged() { + db.append_changeset(&changeset)?; + } println!("Generated Address: {}", address); let balance = wallet.balance(); @@ -72,7 +73,9 @@ fn main() -> Result<(), anyhow::Error> { println!(); wallet.apply_update(update)?; - wallet.commit_to(&mut db)?; + if let Some(changeset) = wallet.take_staged() { + db.append_changeset(&changeset)?; + } 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 6a666d49b..0fd82b985 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -7,7 +7,6 @@ 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; @@ -20,7 +19,7 @@ async fn main() -> Result<(), anyhow::Error> { 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 changeset = db.read()?; let mut wallet = Wallet::new_or_load( external_descriptor, @@ -30,7 +29,9 @@ async fn main() -> Result<(), anyhow::Error> { )?; let address = wallet.next_unused_address(KeychainKind::External); - wallet.commit_to(&mut db)?; + if let Some(changeset) = wallet.take_staged() { + db.write(&changeset)?; + } println!("Generated Address: {}", address); let balance = wallet.balance(); @@ -78,7 +79,9 @@ async fn main() -> Result<(), anyhow::Error> { let _ = update.graph_update.update_last_seen_unconfirmed(now); wallet.apply_update(update)?; - wallet.commit_to(&mut db)?; + if let Some(changeset) = wallet.take_staged() { + db.write(&changeset)?; + } 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 21e9fe688..32211b04b 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -7,7 +7,6 @@ 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, @@ -19,7 +18,7 @@ fn main() -> Result<(), anyhow::Error> { 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 changeset = db.aggregate_changesets()?; let mut wallet = Wallet::new_or_load( external_descriptor, @@ -29,7 +28,9 @@ fn main() -> Result<(), anyhow::Error> { )?; let address = wallet.next_unused_address(KeychainKind::External); - wallet.commit_to(&mut db)?; + if let Some(changeset) = wallet.take_staged() { + db.append_changeset(&changeset)?; + } println!("Generated Address: {}", address); let balance = wallet.balance(); @@ -55,7 +56,9 @@ fn main() -> Result<(), anyhow::Error> { let _ = update.graph_update.update_last_seen_unconfirmed(now); wallet.apply_update(update)?; - wallet.commit_to(&mut db)?; + if let Some(changeset) = wallet.take_staged() { + db.append_changeset(&changeset)?; + } println!(); let balance = wallet.balance(); diff --git a/example-crates/wallet_rpc/src/main.rs b/example-crates/wallet_rpc/src/main.rs index e0d617493..e09e6d762 100644 --- a/example-crates/wallet_rpc/src/main.rs +++ b/example-crates/wallet_rpc/src/main.rs @@ -3,7 +3,6 @@ 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, @@ -91,7 +90,7 @@ fn main() -> anyhow::Result<()> { DB_MAGIC.as_bytes(), args.db_path, )?; - let changeset = db.load_changes()?; + let changeset = db.aggregate_changesets()?; let mut wallet = Wallet::new_or_load( &args.descriptor, @@ -147,7 +146,9 @@ 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_to(&mut db)?; + if let Some(changeset) = wallet.take_staged() { + db.append_changeset(&changeset)?; + } let elapsed = start_apply_block.elapsed().as_secs_f32(); println!( "Applied block {} at height {} in {}s", @@ -157,7 +158,9 @@ 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_to(&mut db)?; + if let Some(changeset) = wallet.take_staged() { + db.append_changeset(&changeset)?; + } println!( "Applied unconfirmed transactions in {}s", start_apply_mempool.elapsed().as_secs_f32()