"crates/esplora",
"crates/bitcoind_rpc",
"crates/hwi",
+ "crates/persist",
"crates/testenv",
"example-crates/example_cli",
"example-crates/example_electrum",
- [`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.
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 }
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
//! # 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;
},
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};
/// # 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!();
/// # 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!();
/// # 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();
//! # 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!();
/// # 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();
/// # 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")
[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"] }
pub use tx_graph::TxGraph;
mod chain_oracle;
pub use chain_oracle::*;
-mod persist;
-pub use persist::*;
#[doc(hidden)]
pub mod example_utils;
+++ /dev/null
-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)
- }
-}
[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"] }
# 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
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},
};
/// 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
///
/// 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).
type TestChangeSet = BTreeSet<String>;
- #[derive(Debug)]
- struct TestTracker;
-
/// Check behavior of [`Store::create_new`] and [`Store::open`].
#[test]
fn construct_store() {
--- /dev/null
+[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 }
+
+
--- /dev/null
+# 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
--- /dev/null
+#![doc = include_str!("../README.md")]
+#![no_std]
+#![warn(missing_docs)]
+mod persist;
+pub use persist::*;
--- /dev/null
+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)
+ }
+}
[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" }
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};