Skip to content

Commit

Permalink
refactor(persist): update file_store, sqlite, wallet to use bdk_chain…
Browse files Browse the repository at this point in the history
…::persist

Also update examples and remove bdk_persist crate.
  • Loading branch information
notmandatory committed Jun 13, 2024
1 parent 54b0c11 commit 9e97ac0
Show file tree
Hide file tree
Showing 39 changed files with 540 additions and 1,084 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ members = [
"crates/esplora",
"crates/bitcoind_rpc",
"crates/hwi",
"crates/persist",
"crates/testenv",
"example-crates/example_cli",
"example-crates/example_electrum",
Expand Down
3 changes: 1 addition & 2 deletions crates/chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ rand = "0.8"
proptest = "1.2.0"

[features]
default = ["std", "miniscript", "persist"]
default = ["std", "miniscript"]
std = ["bitcoin/std", "miniscript?/std"]
serde = ["serde_crate", "bitcoin/serde", "miniscript?/serde"]
persist = ["miniscript"]
async = ["async-trait"]
2 changes: 1 addition & 1 deletion crates/chain/src/keychain/txout_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl<K> KeychainTxOutIndex<K> {
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
/// Return a reference to the internal [`SpkTxOutIndex`].
///
/// **WARNING:** The internal index will contain lookahead spks. Refer to
/// **WARNING**: The internal index will contain lookahead spks. Refer to
/// [struct-level docs](KeychainTxOutIndex) for more about `lookahead`.
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
&self.inner
Expand Down
2 changes: 1 addition & 1 deletion crates/chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub use descriptor_ext::{DescriptorExt, DescriptorId};
mod spk_iter;
#[cfg(feature = "miniscript")]
pub use spk_iter::*;
#[cfg(feature = "persist")]
#[cfg(feature = "miniscript")]
pub mod persist;
pub mod spk_client;

Expand Down
299 changes: 5 additions & 294 deletions crates/chain/src/persist.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
//! This module is home to the [`Persist`] trait which defines the behavior of a data store
//! This module is home to the [`PersistBackend`] trait which defines the behavior of a data store
//! required to persist changes made to BDK data structures.
//!
//! The [`StagedPersist`] type provides a convenient wrapper around implementations of [`Persist`] that
//! allows changes to be staged before committing them.
//!
//! The [`CombinedChangeSet`] type encapsulates a combination of [`crate`] structures that are
//! typically persisted together.

Expand All @@ -16,7 +13,6 @@ use bitcoin::Network;
use core::convert::Infallible;
use core::default::Default;
use core::fmt::{Debug, Display};
use core::mem;

/// A changeset containing [`crate`] structures typically persisted together.
#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -92,7 +88,7 @@ impl<K, A> From<indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>>
///
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
/// that are to be persisted, or retrieved from persistence.
pub trait Persist<C> {
pub trait PersistBackend<C> {
/// The error the backend returns when it fails to write.
type WriteError: Debug + Display;

Expand All @@ -113,7 +109,7 @@ pub trait Persist<C> {
fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError>;
}

impl<C> Persist<C> for () {
impl<C> PersistBackend<C> for () {
type WriteError = Infallible;
type LoadError = Infallible;

Expand All @@ -132,7 +128,7 @@ impl<C> Persist<C> for () {
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
/// that are to be persisted, or retrieved from persistence.
#[async_trait]
pub trait PersistAsync<C> {
pub trait PersistBackendAsync<C> {
/// The error the backend returns when it fails to write.
type WriteError: Debug + Display;

Expand All @@ -155,7 +151,7 @@ pub trait PersistAsync<C> {

#[cfg(feature = "async")]
#[async_trait]
impl<C> PersistAsync<C> for () {
impl<C> PersistBackendAsync<C> for () {
type WriteError = Infallible;
type LoadError = Infallible;

Expand All @@ -167,288 +163,3 @@ impl<C> PersistAsync<C> for () {
Ok(None)
}
}

/// `StagedPersist` adds a convenient staging area for changesets before they are persisted.
///
/// Not all changes to the in-memory representation needs to be written to disk right away, so
/// [`crate::persist::StagedPersist::stage`] can be used to *stage* changes first and then
/// [`crate::persist::StagedPersist::commit`] can be used to write changes to disk.
pub struct StagedPersist<C, P: Persist<C>> {
inner: P,
stage: C,
}

impl<C, P: Persist<C>> Persist<C> for StagedPersist<C, P> {
type WriteError = P::WriteError;
type LoadError = P::LoadError;

fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> {
self.inner.write_changes(changeset)
}

fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError> {
self.inner.load_changes()
}
}

impl<C, P> StagedPersist<C, P>
where
C: Default + Append,
P: Persist<C>,
{
/// Create a new [`StagedPersist`] adding staging to an inner data store that implements
/// [`Persist`].
pub fn new(persist: P) -> Self {
Self {
inner: persist,
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
}

/// Take the changes that have not been committed yet.
///
/// New staged is set to default;
pub fn take_staged(&mut self) -> C {
mem::take(&mut 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) -> Result<Option<C>, P::WriteError> {
if self.staged().is_empty() {
return Ok(None);
}
let staged = self.take_staged();
self.write_changes(&staged)
// if written successfully, take and return `self.stage`
.map(|_| Some(staged))
}

/// 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) -> Result<Option<C>, P::WriteError> {
self.stage(changeset);
self.commit()
}
}

#[cfg(feature = "async")]
/// `StagedPersistAsync` adds a convenient async staging area for changesets before they are persisted.
///
/// Not all changes to the in-memory representation needs to be written to disk right away, so
/// [`StagedPersistAsync::stage`] can be used to *stage* changes first and then
/// [`StagedPersistAsync::commit`] can be used to write changes to disk.
pub struct StagedPersistAsync<C, P: PersistAsync<C>> {
inner: P,
staged: C,
}

#[cfg(feature = "async")]
#[async_trait]
impl<C: Send + Sync, P: PersistAsync<C> + Send> PersistAsync<C> for StagedPersistAsync<C, P> {
type WriteError = P::WriteError;
type LoadError = P::LoadError;

async fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> {
self.inner.write_changes(changeset).await
}

async fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError> {
self.inner.load_changes().await
}
}

#[cfg(feature = "async")]
impl<C, P> StagedPersistAsync<C, P>
where
C: Default + Append + Send + Sync,
P: PersistAsync<C> + Send,
{
/// Create a new [`StagedPersistAsync`] adding staging to an inner data store that implements
/// [`PersistAsync`].
pub fn new(persist: P) -> Self {
Self {
inner: persist,
staged: Default::default(),
}
}

/// Stage a `changeset` to be committed later with [`commit`].
///
/// [`commit`]: Self::commit
pub fn stage(&mut self, changeset: C) {
self.staged.append(changeset)
}

/// Get the changes that have not been committed yet.
pub fn staged(&self) -> &C {
&self.staged
}

/// Take the changes that have not been committed yet.
///
/// New staged is set to default;
pub fn take_staged(&mut self) -> C {
mem::take(&mut self.staged)
}

/// 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 async fn commit(&mut self) -> Result<Option<C>, P::WriteError> {
if self.staged().is_empty() {
return Ok(None);
}
let staged = self.take_staged();
self.write_changes(&staged)
.await
// if written successfully, take and return `self.stage`
.map(|_| Some(staged))
}

/// 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 async fn stage_and_commit(&mut self, changeset: C) -> Result<Option<C>, P::WriteError> {
self.stage(changeset);
self.commit().await
}
}

#[cfg(test)]
mod test {
extern crate core;

use crate::persist::{Persist, StagedPersist};
use crate::Append;
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::prelude::rust_2015::{String, ToString};
use TestError::FailedWrite;

struct TestBackend<C: Default + Append + Clone + ToString> {
changeset: C,
}

#[derive(Debug, Eq, PartialEq)]
enum TestError {
FailedWrite,
FailedLoad,
}

impl Display for TestError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}

impl Error for TestError {}

#[derive(Clone, Default)]
struct TestChangeSet(Option<String>);

impl fmt::Display for TestChangeSet {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.clone().0.unwrap_or_default())
}
}

impl Append for TestChangeSet {
fn append(&mut self, other: Self) {
if other.0.is_some() {
self.0 = other.0
}
}

fn is_empty(&self) -> bool {
self.0.is_none()
}
}

impl<C> Persist<C> for TestBackend<C>
where
C: Default + Append + Clone + ToString,
{
type WriteError = TestError;
type LoadError = TestError;

fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> {
if changeset.to_string() == "ERROR" {
Err(FailedWrite)
} else {
self.changeset = changeset.clone();
Ok(())
}
}

fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError> {
if self.changeset.to_string() == "ERROR" {
Err(Self::LoadError::FailedLoad)
} else {
Ok(Some(self.changeset.clone()))
}
}
}

#[test]
fn test_persist_stage_commit() {
let backend = TestBackend {
changeset: TestChangeSet(None),
};

let mut staged_backend = StagedPersist::new(backend);
staged_backend.stage(TestChangeSet(Some("ONE".to_string())));
staged_backend.stage(TestChangeSet(None));
staged_backend.stage(TestChangeSet(Some("TWO".to_string())));
let result = staged_backend.commit();
assert!(matches!(result, Ok(Some(TestChangeSet(Some(v)))) if v == *"TWO".to_string()));

let result = staged_backend.commit();
assert!(matches!(result, Ok(None)));

staged_backend.stage(TestChangeSet(Some("TWO".to_string())));
let result = staged_backend.stage_and_commit(TestChangeSet(Some("ONE".to_string())));
assert!(matches!(result, Ok(Some(TestChangeSet(Some(v)))) if v == *"ONE".to_string()));
}

#[test]
fn test_persist_commit_error() {
let backend = TestBackend {
changeset: TestChangeSet(None),
};
let mut staged_backend = StagedPersist::new(backend);
staged_backend.stage(TestChangeSet(Some("ERROR".to_string())));
let result = staged_backend.commit();
assert!(matches!(result, Err(e) if e == FailedWrite));
}
}
4 changes: 1 addition & 3 deletions crates/file_store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk_file_store"
description = "A simple append-only flat file implementation of Persist for Bitcoin Dev Kit."
description = "A simple append-only flat file implementation of PersistBackend for Bitcoin Dev Kit."
keywords = ["bitcoin", "persist", "persistence", "bdk", "file"]
authors = ["Bitcoin Dev Kit Developers"]
readme = "README.md"

[dependencies]
anyhow = { version = "1", default-features = false }
bdk_chain = { path = "../chain", version = "0.15.0", features = [ "serde", "miniscript" ] }
bdk_persist = { path = "../persist", version = "0.3.0"}
bincode = { version = "1" }
serde = { version = "1", features = ["derive"] }

Expand Down
Loading

0 comments on commit 9e97ac0

Please sign in to comment.