Skip to content

Commit

Permalink
feat(chain): reintroduce a way to stage changesets before persisting
Browse files Browse the repository at this point in the history
A staging area is helpful because we can contain logic to ignore empty
changesets and not clear staging area if the persistence backend fails.
  • Loading branch information
evanlinjin authored and notmandatory committed Jun 13, 2024
1 parent 7f89ba4 commit 35411bb
Showing 1 changed file with 110 additions and 0 deletions.
110 changes: 110 additions & 0 deletions crates/chain/src/persist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ 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")]
Expand Down Expand Up @@ -88,6 +90,19 @@ impl<K, A> From<crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet
}
}

#[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
Expand Down Expand Up @@ -167,3 +182,98 @@ impl<C> PersistBackendAsync<C> for () {
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 {}

0 comments on commit 35411bb

Please sign in to comment.