# 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"
default = ["std", "miniscript"]
std = ["bitcoin/std", "miniscript?/std"]
serde = ["serde_crate", "bitcoin/serde", "miniscript?/serde"]
-async = ["async-trait"]
--- /dev/null
+//! 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.
+
+/// 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<K, A> {
+ /// 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<A, crate::keychain::ChangeSet<K>>,
+ /// Stores the network type of the transaction data.
+ pub network: Option<bitcoin::Network>,
+}
+
+#[cfg(feature = "miniscript")]
+impl<K, A> core::default::Default for CombinedChangeSet<K, A> {
+ fn default() -> Self {
+ Self {
+ chain: core::default::Default::default(),
+ indexed_tx_graph: core::default::Default::default(),
+ network: None,
+ }
+ }
+}
+
+#[cfg(feature = "miniscript")]
+impl<K: Ord, A: crate::Anchor> crate::Append for CombinedChangeSet<K, A> {
+ 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<K, A> From<crate::local_chain::ChangeSet> for CombinedChangeSet<K, A> {
+ fn from(chain: crate::local_chain::ChangeSet) -> Self {
+ Self {
+ chain,
+ ..Default::default()
+ }
+ }
+}
+
+#[cfg(feature = "miniscript")]
+impl<K, A> From<crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>>
+ for CombinedChangeSet<K, A>
+{
+ fn from(
+ indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>,
+ ) -> Self {
+ Self {
+ indexed_tx_graph,
+ ..Default::default()
+ }
+ }
+}
+
+#[cfg(feature = "miniscript")]
+impl<K, A> From<crate::keychain::ChangeSet<K>> for CombinedChangeSet<K, A> {
+ fn from(indexer: crate::keychain::ChangeSet<K>) -> Self {
+ Self {
+ indexed_tx_graph: crate::indexed_tx_graph::ChangeSet {
+ indexer,
+ ..Default::default()
+ },
+ ..Default::default()
+ }
+ }
+}
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)]
+++ /dev/null
-//! 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<K, A> {
- /// 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<A, crate::keychain::ChangeSet<K>>,
- /// Stores the network type of the transaction data.
- pub network: Option<bitcoin::Network>,
-}
-
-#[cfg(feature = "miniscript")]
-impl<K, A> core::default::Default for CombinedChangeSet<K, A> {
- fn default() -> Self {
- Self {
- chain: core::default::Default::default(),
- indexed_tx_graph: core::default::Default::default(),
- network: None,
- }
- }
-}
-
-#[cfg(feature = "miniscript")]
-impl<K: Ord, A: crate::Anchor> crate::Append for CombinedChangeSet<K, A> {
- 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<K, A> From<crate::local_chain::ChangeSet> for CombinedChangeSet<K, A> {
- fn from(chain: crate::local_chain::ChangeSet) -> Self {
- Self {
- chain,
- ..Default::default()
- }
- }
-}
-
-#[cfg(feature = "miniscript")]
-impl<K, A> From<crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>>
- for CombinedChangeSet<K, A>
-{
- fn from(
- indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>,
- ) -> Self {
- Self {
- indexed_tx_graph,
- ..Default::default()
- }
- }
-}
-
-#[cfg(feature = "miniscript")]
-impl<K, A> From<crate::keychain::ChangeSet<K>> for CombinedChangeSet<K, A> {
- fn from(indexer: crate::keychain::ChangeSet<K>) -> 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<C> {
- /// 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<Option<C>, Self::LoadError>;
-}
-
-impl<C> PersistBackend<C> for () {
- type WriteError = Infallible;
- type LoadError = Infallible;
-
- fn write_changes(&mut self, _changeset: &C) -> Result<(), Self::WriteError> {
- Ok(())
- }
-
- fn load_changes(&mut self) -> Result<Option<C>, 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<C> {
- /// 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<Option<C>, Self::LoadError>;
-}
-
-#[cfg(feature = "async")]
-#[async_trait]
-impl<C> PersistBackendAsync<C> 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<Option<C>, 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<B>(&mut self, backend: &mut B) -> Result<Option<Self>, B::WriteError>
- where
- B: PersistBackend<Self>,
- {
- // 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<B>(
- &mut self,
- changeset: Self,
- backend: &mut B,
- ) -> Result<Option<Self>, B::WriteError>
- where
- B: PersistBackend<Self>,
- {
- Append::append(self, changeset);
- self.commit_to(backend)
- }
-}
-
-impl<C: Append + Default> 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<B>(&mut self, backend: &mut B) -> Result<Option<Self>, B::WriteError>
- where
- B: PersistBackendAsync<Self> + 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<B>(
- &mut self,
- changeset: Self,
- backend: &mut B,
- ) -> Result<Option<Self>, B::WriteError>
- where
- B: PersistBackendAsync<Self> + Send + Sync,
- {
- Append::append(self, changeset);
- self.commit_to(backend).await
- }
-}
-
-#[cfg(feature = "async")]
-#[async_trait]
-impl<C: Append + Default + Send + Sync> StageExtAsync for C {}
# 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.
use crate::{bincode_options, EntryIter, FileError, IterError};
-use bdk_chain::persist::PersistBackend;
use bdk_chain::Append;
use bincode::Options;
use std::{
marker: PhantomData<C>,
}
-impl<C> PersistBackend<C> for Store<C>
-where
- C: Append
- + Debug
- + serde::Serialize
- + serde::de::DeserializeOwned
- + core::marker::Send
- + core::marker::Sync,
-{
- type WriteError = io::Error;
- type LoadError = AggregateChangesetsError<C>;
-
- fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> {
- self.append_changeset(changeset)
- }
-
- fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError> {
- self.aggregate_changesets()
- }
-}
-
impl<C> Store<C>
where
C: Append
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);
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,
};
}
}
-impl<K, A> PersistBackend<CombinedChangeSet<K, A>> for Store<K, A>
-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<K, A>,
- ) -> Result<(), Self::WriteError> {
- self.write(changeset)
- }
-
- fn load_changes(&mut self) -> Result<Option<CombinedChangeSet<K, A>>, Self::LoadError> {
- self.read()
- }
-}
-
/// Network table related functions.
impl<K, A> Store<K, A> {
/// Insert [`Network`] for which all other tables data is valid.
}
}
-/// Functions to read and write all [`ChangeSet`] data.
+/// Functions to read and write all [`CombinedChangeSet`] data.
impl<K, A> Store<K, A>
where
K: Ord + for<'de> Deserialize<'de> + Serialize + Send,
A: Anchor + for<'de> Deserialize<'de> + Serialize + Send,
{
- fn write(&mut self, changeset: &CombinedChangeSet<K, A>) -> Result<(), Error> {
+ /// Write the given `changeset` atomically.
+ pub fn write(&mut self, changeset: &CombinedChangeSet<K, A>) -> Result<(), Error> {
// no need to write anything if changeset is empty
if changeset.is_empty() {
return Ok(());
db_transaction.commit().map_err(Error::Sqlite)
}
- fn read(&mut self) -> Result<Option<CombinedChangeSet<K, A>>, Error> {
+ /// Read the entire database and return the aggregate [`CombinedChangeSet`].
+ pub fn read(&mut self) -> Result<Option<CombinedChangeSet<K, A>>, Error> {
let db_transaction = self.db_transaction()?;
let network = Self::select_network(&db_transaction)?;
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,
.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));
}
.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));
}
let mut store = Store::<Keychain, BlockId>::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));
}
[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"]
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,
/// 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<SignersContainer>,
}
/// The changes made to a wallet by applying an [`Update`].
-pub type ChangeSet =
- bdk_chain::persist::CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>;
+pub type ChangeSet = bdk_chain::CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>;
/// A derived address and the index it was found at.
/// For convenience this automatically derefs to `Address`
/// # 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();
///
/// 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()
/// 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/*)";
/// 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);
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<B>(&mut self, persist_backend: &mut B) -> Result<bool, B::WriteError>
- where
- B: PersistBackend<ChangeSet>,
- {
- 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<B>(
- &mut self,
- persist_backend: &mut B,
- ) -> Result<bool, B::WriteError>
- where
- B: bdk_chain::persist::PersistBackendAsync<ChangeSet> + 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<ChangeSet> {
+ if self.stage.is_empty() {
+ None
+ } else {
+ Some(core::mem::take(&mut self.stage))
+ }
}
/// Get a reference to the inner [`TxGraph`].
-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;
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;
#[test]
fn load_recovers_wallet() -> anyhow::Result<()> {
- fn run<B, FN, FR>(filename: &str, create_new: FN, recover: FR) -> anyhow::Result<()>
+ fn run<Db, New, Recover, Read, Write>(
+ filename: &str,
+ create_new: New,
+ recover: Recover,
+ read: Read,
+ write: Write,
+ ) -> anyhow::Result<()>
where
- B: PersistBackend<bdk_wallet::wallet::ChangeSet> + Send + Sync + 'static,
- FN: Fn(&Path) -> anyhow::Result<B>,
- FR: Fn(&Path) -> anyhow::Result<B>,
+ New: Fn(&Path) -> anyhow::Result<Db>,
+ Recover: Fn(&Path) -> anyhow::Result<Db>,
+ Read: Fn(&mut Db) -> anyhow::Result<Option<ChangeSet>>,
+ Write: Fn(&mut Db, &ChangeSet) -> anyhow::Result<()>,
{
let temp_dir = tempfile::tempdir().expect("must create tempdir");
let file_path = temp_dir.path().join(filename);
// 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()
};
{
// 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);
"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(())
#[test]
fn new_or_load() -> anyhow::Result<()> {
- fn run<B, F>(filename: &str, new_or_load: F) -> anyhow::Result<()>
+ fn run<Db, NewOrRecover, Read, Write>(
+ filename: &str,
+ new_or_load: NewOrRecover,
+ read: Read,
+ write: Write,
+ ) -> anyhow::Result<()>
where
- B: PersistBackend<bdk_wallet::wallet::ChangeSet> + Send + Sync + 'static,
- F: Fn(&Path) -> anyhow::Result<B>,
+ NewOrRecover: Fn(&Path) -> anyhow::Result<Db>,
+ Read: Fn(&mut Db) -> anyhow::Result<Option<ChangeSet>>,
+ Write: Fn(&mut Db, &ChangeSet) -> anyhow::Result<()>,
{
let temp_dir = tempfile::tempdir().expect("must create tempdir");
let file_path = temp_dir.path().join(filename);
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!(
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,
.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");
.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!(
// 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);
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(())
}
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,
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)?
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 !db_stage.is_empty() {
+ db.append_changeset(&core::mem::take(&mut db_stage))?;
+ }
println!(
"[{:>10}s] committed to db (took {}s)",
start.elapsed().as_secs_f32(),
);
{
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 !db_stage.is_empty() {
+ db.append_changeset(&core::mem::take(&mut db_stage))?;
+ }
}
}
RpcCommands::Live { rpc_args } => {
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 !db_stage.is_empty() {
+ db.append_changeset(&core::mem::take(&mut db_stage))?;
+ }
println!(
"[{:>10}s] committed to db (took {}s)",
start.elapsed().as_secs_f32(),
pub use bdk_file_store;
pub use clap;
-use bdk_chain::persist::PersistBackend;
use clap::{Parser, Subcommand};
pub type KeychainTxGraph<A> = IndexedTxGraph<A, KeychainTxOutIndex<Keychain>>;
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),
)))?;
// 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),
)))?;
// 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,
)))?;
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,
sync::Mutex,
};
-use bdk_chain::persist::PersistBackend;
use bdk_chain::{
bitcoin::{constants::genesis_block, Address, Network, Txid},
collections::BTreeSet,
};
let mut db = db.lock().unwrap();
- db.write_changes(&db_changeset)?;
+ db.append_changeset(&db_changeset)?;
Ok(())
}
sync::Mutex,
};
-use bdk_chain::persist::PersistBackend;
use bdk_chain::{
bitcoin::{constants::genesis_block, Address, Network, Txid},
indexed_tx_graph::{self, IndexedTxGraph},
// 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(())
}
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};
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,
)?;
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();
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());
};
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;
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,
)?;
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();
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();
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,
Store::<bdk_wallet::wallet::ChangeSet>::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,
)?;
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();
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();
Emitter,
};
use bdk_file_store::Store;
-use bdk_wallet::chain::persist::PersistBackend;
use bdk_wallet::{
bitcoin::{Block, Network, Transaction},
wallet::Wallet,
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,
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",
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()