]> Untitled Git - bdk/commitdiff
feat(bdk-persist): extract persistence traits to new crate
authorRob N <rob.netzke@gmail.com>
Thu, 25 Apr 2024 01:01:17 +0000 (15:01 -1000)
committerRob N <rob.netzke@gmail.com>
Sat, 27 Apr 2024 02:21:54 +0000 (16:21 -1000)
19 files changed:
Cargo.toml
README.md
crates/bdk/Cargo.toml
crates/bdk/README.md
crates/bdk/src/wallet/coin_selection.rs
crates/bdk/src/wallet/mod.rs
crates/bdk/src/wallet/tx_builder.rs
crates/chain/Cargo.toml
crates/chain/src/lib.rs
crates/chain/src/persist.rs [deleted file]
crates/file_store/Cargo.toml
crates/file_store/README.md
crates/file_store/src/store.rs
crates/persist/Cargo.toml [new file with mode: 0644]
crates/persist/README.md [new file with mode: 0644]
crates/persist/src/lib.rs [new file with mode: 0644]
crates/persist/src/persist.rs [new file with mode: 0644]
example-crates/example_cli/Cargo.toml
example-crates/example_cli/src/lib.rs

index 53ceb7cd47a97c52882f2faeaee5f730c2839b53..87428029af21d937db649b82e7fa98e5260ed4f2 100644 (file)
@@ -8,6 +8,7 @@ members = [
     "crates/esplora",
     "crates/bitcoind_rpc",
     "crates/hwi",
+    "crates/persist",
     "crates/testenv",
     "example-crates/example_cli",
     "example-crates/example_electrum",
index d3222430854972d7544505934afb4f6047489e4f..030ec2a4e2ea476a5c18f6d1efd61986ac0c5106 100644 (file)
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@ The project is split up into several crates in the `/crates` directory:
 
 - [`bdk`](./crates/bdk): Contains the central high level `Wallet` type that is built from the low-level mechanisms provided by the other components
 - [`chain`](./crates/chain): Tools for storing and indexing chain data
+- [`persist`](./crates/persist): Types that define data persistence of a BDK wallet
 - [`file_store`](./crates/file_store): A (experimental) persistence backend for storing chain data in a single file.
 - [`esplora`](./crates/esplora): Extends the [`esplora-client`] crate with methods to fetch chain data from an esplora HTTP server in the form that [`bdk_chain`] and `Wallet` can consume.
 - [`electrum`](./crates/electrum): Extends the [`electrum-client`] crate with methods to fetch chain data from an electrum server in the form that [`bdk_chain`] and `Wallet` can consume.
index 425199e101c26cdc39fbafe46c11ba35d16f17aa..982b2402a28c91c464ca2cb3e7fc54a88c5549a5 100644 (file)
@@ -20,6 +20,7 @@ bitcoin = { version = "0.31.0", features = ["serde", "base64", "rand-std"], defa
 serde = { version = "^1.0", features = ["derive"] }
 serde_json = { version = "^1.0" }
 bdk_chain = { path = "../chain", version = "0.12.0", features = ["miniscript", "serde"], default-features = false }
+bdk_persist = { path = "../persist", version = "0.1.0" }
 
 # Optional dependencies
 bip39 = { version = "2.0", optional = true }
index a3d53f2950496cfe437fc5fd4b264457ec677ce5..4722c5845b5ec2a3939c7088a1b079b8b3d9e95b 100644 (file)
@@ -219,7 +219,7 @@ license, shall be dual licensed as above, without any additional terms or
 conditions.
 
 [`Wallet`]: https://docs.rs/bdk/1.0.0-alpha.7/bdk/wallet/struct.Wallet.html
-[`PersistBackend`]: https://docs.rs/bdk_chain/latest/bdk_chain/trait.PersistBackend.html
+[`PersistBackend`]: https://docs.rs/bdk_persist/latest/bdk_persist/trait.PersistBackend.html
 [`bdk_chain`]: https://docs.rs/bdk_chain/latest
 [`bdk_file_store`]: https://docs.rs/bdk_file_store/latest
 [`bdk_electrum`]: https://docs.rs/bdk_electrum/latest
index 49cb56c0cb890f122506e32e5ecb0f64b3ef7a96..f1897677bf5987d401d406783d96a061c7ddfe2c 100644 (file)
@@ -28,7 +28,7 @@
 //! # use bitcoin::*;
 //! # use bdk::wallet::{self, ChangeSet, coin_selection::*, coin_selection};
 //! # use bdk::wallet::error::CreateTxError;
-//! # use bdk_chain::PersistBackend;
+//! # use bdk_persist::PersistBackend;
 //! # use bdk::*;
 //! # use bdk::wallet::coin_selection::decide_change;
 //! # use anyhow::Error;
index 1e24dd50e4436f54cb2156ca6aab125838aaddc1..1fe37d5508cdc6a63d73a16f2705c51d0563b265 100644 (file)
@@ -28,8 +28,9 @@ use bdk_chain::{
     },
     tx_graph::{CanonicalTx, TxGraph},
     Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut,
-    IndexedTxGraph, Persist, PersistBackend,
+    IndexedTxGraph,
 };
+use bdk_persist::{Persist, PersistBackend};
 use bitcoin::constants::genesis_block;
 use bitcoin::secp256k1::{All, Secp256k1};
 use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
@@ -1167,7 +1168,7 @@ impl Wallet {
     /// # use bdk::*;
     /// # use bdk::wallet::ChangeSet;
     /// # use bdk::wallet::error::CreateTxError;
-    /// # use bdk_chain::PersistBackend;
+    /// # use bdk_persist::PersistBackend;
     /// # use anyhow::Error;
     /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
     /// # let mut wallet = doctest_wallet!();
@@ -1549,7 +1550,7 @@ impl Wallet {
     /// # use bdk::*;
     /// # use bdk::wallet::ChangeSet;
     /// # use bdk::wallet::error::CreateTxError;
-    /// # use bdk_chain::PersistBackend;
+    /// # use bdk_persist::PersistBackend;
     /// # use anyhow::Error;
     /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
     /// # let mut wallet = doctest_wallet!();
@@ -1724,7 +1725,7 @@ impl Wallet {
     /// # use bdk::*;
     /// # use bdk::wallet::ChangeSet;
     /// # use bdk::wallet::error::CreateTxError;
-    /// # use bdk_chain::PersistBackend;
+    /// # use bdk_persist::PersistBackend;
     /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
     /// # let mut wallet = doctest_wallet!();
     /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
index e7df86678d34ec9f840c61a70f3a757327287a0c..355d80d3bcb8b48c21630bd57f3bf481a71829a4 100644 (file)
@@ -20,7 +20,7 @@
 //! # use bdk::wallet::ChangeSet;
 //! # use bdk::wallet::error::CreateTxError;
 //! # use bdk::wallet::tx_builder::CreateTx;
-//! # use bdk_chain::PersistBackend;
+//! # use bdk_persist::PersistBackend;
 //! # use anyhow::Error;
 //! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
 //! # let mut wallet = doctest_wallet!();
@@ -84,7 +84,7 @@ impl TxBuilderContext for BumpFee {}
 /// # use core::str::FromStr;
 /// # use bdk::wallet::ChangeSet;
 /// # use bdk::wallet::error::CreateTxError;
-/// # use bdk_chain::PersistBackend;
+/// # use bdk_persist::PersistBackend;
 /// # use anyhow::Error;
 /// # let mut wallet = doctest_wallet!();
 /// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
@@ -758,7 +758,7 @@ impl<'a, Cs: CoinSelectionAlgorithm> TxBuilder<'a, Cs, CreateTx> {
     /// # use bdk::wallet::ChangeSet;
     /// # use bdk::wallet::error::CreateTxError;
     /// # use bdk::wallet::tx_builder::CreateTx;
-    /// # use bdk_chain::PersistBackend;
+    /// # use bdk_persist::PersistBackend;
     /// # use anyhow::Error;
     /// # let to_address =
     /// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
index 56cadd85a170c545f5836770d2b92e62bfdf2de1..fda5924d2f02f9306b06286fd8dfabfd16860eeb 100644 (file)
@@ -14,7 +14,6 @@ readme = "README.md"
 
 [dependencies]
 # For no-std, remember to enable the bitcoin/no-std feature
-anyhow = { version = "1", default-features = false }
 bitcoin = { version = "0.31.0", default-features = false }
 serde_crate = { package = "serde", version = "1", optional = true, features = ["derive", "rc"] }
 
index 2065669714e400af4029eb604aa8d9c21d871c42..61c0b6d7ac875530b4685fd1f0b03ca0dde3c789 100644 (file)
@@ -35,8 +35,6 @@ pub use tx_data_traits::*;
 pub use tx_graph::TxGraph;
 mod chain_oracle;
 pub use chain_oracle::*;
-mod persist;
-pub use persist::*;
 
 #[doc(hidden)]
 pub mod example_utils;
diff --git a/crates/chain/src/persist.rs b/crates/chain/src/persist.rs
deleted file mode 100644 (file)
index 64efa55..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-use crate::Append;
-use alloc::boxed::Box;
-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<C> {
-    backend: Box<dyn PersistBackend<C> + Send + Sync>,
-    stage: C,
-}
-
-impl<C: fmt::Debug> fmt::Debug for Persist<C> {
-    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
-        write!(fmt, "{:?}", self.stage)?;
-        Ok(())
-    }
-}
-
-impl<C> Persist<C>
-where
-    C: Default + Append,
-{
-    /// Create a new [`Persist`] from [`PersistBackend`].
-    pub fn new(backend: impl PersistBackend<C> + 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<Option<C>> {
-        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<Option<C>> {
-        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<C> {
-    /// 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<Option<C>>;
-}
-
-impl<C> PersistBackend<C> for () {
-    fn write_changes(&mut self, _changeset: &C) -> anyhow::Result<()> {
-        Ok(())
-    }
-
-    fn load_from_persistence(&mut self) -> anyhow::Result<Option<C>> {
-        Ok(None)
-    }
-}
index 0d382b4bf7664a637f97d9d62fa13c89c2980b06..1343effc77737fface11508371bbc6d7f09b322d 100644 (file)
@@ -13,6 +13,7 @@ readme = "README.md"
 [dependencies]
 anyhow = { version = "1", default-features = false }
 bdk_chain = { path = "../chain", version = "0.12.0", features = [ "serde", "miniscript" ] }
+bdk_persist = { path = "../persist", version = "0.1.0"}
 bincode = { version = "1" }
 serde = { version = "1", features = ["derive"] }
 
index 4a334fcbfac5bcdac02f87367ebd570ced14ec55..54e41e007fb777727fae42d0512f5b4b89261145 100644 (file)
@@ -1,10 +1,10 @@
 # BDK File Store
 
 This is a simple append-only flat file implementation of
-[`Persist`](`bdk_chain::Persist`).
+[`PersistBackend`](bdk_persist::PersistBackend).
 
-The main structure is [`Store`](`crate::Store`), which can be used with [`bdk`]'s
+The main structure is [`Store`](crate::Store), which can be used with [`bdk`]'s
 `Wallet` to persist wallet data into a flat file.
 
 [`bdk`]: https://docs.rs/bdk/latest
-[`bdk_chain`]: https://docs.rs/bdk_chain/latest
+[`bdk_persist`]: https://docs.rs/bdk_persist/latest
index 16d8f7b72adb28ad321a6507cb63c1b07cd9968a..6cea927657492226907264deeaef1cc203f2a654 100644 (file)
@@ -1,6 +1,7 @@
 use crate::{bincode_options, EntryIter, FileError, IterError};
 use anyhow::anyhow;
-use bdk_chain::{Append, PersistBackend};
+use bdk_chain::Append;
+use bdk_persist::PersistBackend;
 use bincode::Options;
 use std::{
     fmt::{self, Debug},
@@ -11,8 +12,6 @@ use std::{
 };
 
 /// Persists an append-only list of changesets (`C`) to a single file.
-///
-/// The changesets are the results of altering a tracker implementation (`T`).
 #[derive(Debug)]
 pub struct Store<C>
 where
@@ -152,7 +151,7 @@ where
     ///
     /// You should usually check the error. In many applications, it may make sense to do a full
     /// wallet scan with a stop-gap after getting an error, since it is likely that one of the
-    /// changesets it was unable to read changed the derivation indices of the tracker.
+    /// changesets was unable to read changes of the derivation indices of a keychain.
     ///
     /// **WARNING**: This method changes the write position of the underlying file. The next
     /// changeset will be written over the erroring entry (or the end of the file if none existed).
@@ -240,9 +239,6 @@ mod test {
 
     type TestChangeSet = BTreeSet<String>;
 
-    #[derive(Debug)]
-    struct TestTracker;
-
     /// Check behavior of [`Store::create_new`] and [`Store::open`].
     #[test]
     fn construct_store() {
diff --git a/crates/persist/Cargo.toml b/crates/persist/Cargo.toml
new file mode 100644 (file)
index 0000000..257ab54
--- /dev/null
@@ -0,0 +1,19 @@
+[package]
+name = "bdk_persist"
+homepage = "https://bitcoindevkit.org"
+version = "0.1.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.12.0", default-features = false }
+
+
diff --git a/crates/persist/README.md b/crates/persist/README.md
new file mode 100644 (file)
index 0000000..1ed6ec8
--- /dev/null
@@ -0,0 +1,3 @@
+# BDK Persist
+
+This crate is home to the [`PersistBackend`](crate::PersistBackend) trait which defines the behavior of a database to perform the task of persisting changes made to BDK data structures. The [`Persist`](crate::Persist) type provides a convenient wrapper around a `PersistBackend` that allows staging changes before committing them.
\ No newline at end of file
diff --git a/crates/persist/src/lib.rs b/crates/persist/src/lib.rs
new file mode 100644 (file)
index 0000000..e055f2a
--- /dev/null
@@ -0,0 +1,5 @@
+#![doc = include_str!("../README.md")]
+#![no_std]
+#![warn(missing_docs)]
+mod persist;
+pub use persist::*;
diff --git a/crates/persist/src/persist.rs b/crates/persist/src/persist.rs
new file mode 100644 (file)
index 0000000..5d9df3b
--- /dev/null
@@ -0,0 +1,106 @@
+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<C> {
+    backend: Box<dyn PersistBackend<C> + Send + Sync>,
+    stage: C,
+}
+
+impl<C: fmt::Debug> fmt::Debug for Persist<C> {
+    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
+        write!(fmt, "{:?}", self.stage)?;
+        Ok(())
+    }
+}
+
+impl<C> Persist<C>
+where
+    C: Default + Append,
+{
+    /// Create a new [`Persist`] from [`PersistBackend`].
+    pub fn new(backend: impl PersistBackend<C> + 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<Option<C>> {
+        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<Option<C>> {
+        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<C> {
+    /// 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<Option<C>>;
+}
+
+impl<C> PersistBackend<C> for () {
+    fn write_changes(&mut self, _changeset: &C) -> anyhow::Result<()> {
+        Ok(())
+    }
+
+    fn load_from_persistence(&mut self) -> anyhow::Result<Option<C>> {
+        Ok(None)
+    }
+}
index c85d2e996310b0fc8e3fc28347e12cc867cce433..42a0b51b0906d275bd4ddb8e2d5a396f29988ee9 100644 (file)
@@ -7,6 +7,7 @@ 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" }
index e7c4efece201383879e82a3dab1e55ee677f6b71..5671a6b8243fc453d9629e947318d1980b5868fe 100644 (file)
@@ -19,9 +19,10 @@ use bdk_chain::{
         descriptor::{DescriptorSecretKey, KeyMap},
         Descriptor, DescriptorPublicKey,
     },
-    Anchor, Append, ChainOracle, DescriptorExt, FullTxOut, Persist, PersistBackend,
+    Anchor, Append, ChainOracle, DescriptorExt, FullTxOut,
 };
 pub use bdk_file_store;
+use bdk_persist::{Persist, PersistBackend};
 pub use clap;
 
 use clap::{Parser, Subcommand};