forked from bitcoindevkit/bdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge bitcoindevkit#965: Implement persistence with the new structures
4963240 Add more `impl`s for `Append` and docs for file store `magic` (志宇) 2aa08a5 [persist_redesign] Introduce redesigned `persist` types (志宇) Pull request description: ### Description This is part of bitcoindevkit#895 and bitcoindevkit#971 * Introduce a more generic version of the `keychain::persist::*` structures that only needs a single generic for the changeset type. Additional changes: * The `Append` trait has a new method `is_empty`. * Introduce `Store` structure for `bdk_file_store` (which implements `PersistBackend`). ### Changelog notice ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [x] I've added tests for the new feature * [x] I've added docs for the new feature Top commit has no ACKs. Tree-SHA512: 0211fbe7d7e27805d3ed3a80b42f184cdff1cebb32fd559aa9838e4a7f7c7e47b6c366b6ef68e299f876bafed549b8d1d8b8cc0366bf5b61db079504a565b9b4
- Loading branch information
Showing
10 changed files
with
548 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
use core::convert::Infallible; | ||
|
||
use crate::Append; | ||
|
||
/// `Persist` wraps a [`PersistBackend`] (`B`) 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. | ||
#[derive(Debug)] | ||
pub struct Persist<B, C> { | ||
backend: B, | ||
stage: C, | ||
} | ||
|
||
impl<B, C> Persist<B, C> | ||
where | ||
B: PersistBackend<C>, | ||
C: Default + Append, | ||
{ | ||
/// Create a new [`Persist`] from [`PersistBackend`]. | ||
pub fn new(backend: B) -> Self { | ||
Self { | ||
backend, | ||
stage: Default::default(), | ||
} | ||
} | ||
|
||
/// Stage a `changeset` to be commited later with [`commit`]. | ||
/// | ||
/// [`commit`]: Self::commit | ||
pub fn stage(&mut self, changeset: C) { | ||
self.stage.append(changeset) | ||
} | ||
|
||
/// Get the changes that have not been commited yet. | ||
pub fn staged(&self) -> &C { | ||
&self.stage | ||
} | ||
|
||
/// Commit the staged changes to the underlying persistance backend. | ||
/// | ||
/// Returns a backend-defined error if this fails. | ||
pub fn commit(&mut self) -> Result<(), B::WriteError> { | ||
let mut temp = C::default(); | ||
core::mem::swap(&mut temp, &mut self.stage); | ||
self.backend.write_changes(&temp) | ||
} | ||
} | ||
|
||
/// 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> { | ||
/// The error the backend returns when it fails to write. | ||
type WriteError: core::fmt::Debug; | ||
|
||
/// The error the backend returns when it fails to load changesets `C`. | ||
type LoadError: core::fmt::Debug; | ||
|
||
/// 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) -> Result<(), Self::WriteError>; | ||
|
||
/// Return the aggregate changeset `C` from persistence. | ||
fn load_from_persistence(&mut self) -> Result<C, Self::LoadError>; | ||
} | ||
|
||
impl<C: Default> PersistBackend<C> for () { | ||
type WriteError = Infallible; | ||
|
||
type LoadError = Infallible; | ||
|
||
fn write_changes(&mut self, _changeset: &C) -> Result<(), Self::WriteError> { | ||
Ok(()) | ||
} | ||
|
||
fn load_from_persistence(&mut self) -> Result<C, Self::LoadError> { | ||
Ok(C::default()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
use bincode::Options; | ||
use std::{ | ||
fs::File, | ||
io::{self, Seek}, | ||
marker::PhantomData, | ||
}; | ||
|
||
use crate::bincode_options; | ||
|
||
/// Iterator over entries in a file store. | ||
/// | ||
/// Reads and returns an entry each time [`next`] is called. If an error occurs while reading the | ||
/// iterator will yield a `Result::Err(_)` instead and then `None` for the next call to `next`. | ||
/// | ||
/// [`next`]: Self::next | ||
pub struct EntryIter<'t, T> { | ||
db_file: Option<&'t mut File>, | ||
|
||
/// The file position for the first read of `db_file`. | ||
start_pos: Option<u64>, | ||
types: PhantomData<T>, | ||
} | ||
|
||
impl<'t, T> EntryIter<'t, T> { | ||
pub fn new(start_pos: u64, db_file: &'t mut File) -> Self { | ||
Self { | ||
db_file: Some(db_file), | ||
start_pos: Some(start_pos), | ||
types: PhantomData, | ||
} | ||
} | ||
} | ||
|
||
impl<'t, T> Iterator for EntryIter<'t, T> | ||
where | ||
T: serde::de::DeserializeOwned, | ||
{ | ||
type Item = Result<T, IterError>; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
// closure which reads a single entry starting from `self.pos` | ||
let read_one = |f: &mut File, start_pos: Option<u64>| -> Result<Option<T>, IterError> { | ||
let pos = match start_pos { | ||
Some(pos) => f.seek(io::SeekFrom::Start(pos))?, | ||
None => f.stream_position()?, | ||
}; | ||
|
||
match bincode_options().deserialize_from(&*f) { | ||
Ok(changeset) => { | ||
f.stream_position()?; | ||
Ok(Some(changeset)) | ||
} | ||
Err(e) => { | ||
if let bincode::ErrorKind::Io(inner) = &*e { | ||
if inner.kind() == io::ErrorKind::UnexpectedEof { | ||
let eof = f.seek(io::SeekFrom::End(0))?; | ||
if pos == eof { | ||
return Ok(None); | ||
} | ||
} | ||
} | ||
f.seek(io::SeekFrom::Start(pos))?; | ||
Err(IterError::Bincode(*e)) | ||
} | ||
} | ||
}; | ||
|
||
let result = read_one(self.db_file.as_mut()?, self.start_pos.take()); | ||
if result.is_err() { | ||
self.db_file = None; | ||
} | ||
result.transpose() | ||
} | ||
} | ||
|
||
impl From<io::Error> for IterError { | ||
fn from(value: io::Error) -> Self { | ||
IterError::Io(value) | ||
} | ||
} | ||
|
||
/// Error type for [`EntryIter`]. | ||
#[derive(Debug)] | ||
pub enum IterError { | ||
/// Failure to read from the file. | ||
Io(io::Error), | ||
/// Failure to decode data from the file. | ||
Bincode(bincode::ErrorKind), | ||
} | ||
|
||
impl core::fmt::Display for IterError { | ||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
match self { | ||
IterError::Io(e) => write!(f, "io error trying to read entry {}", e), | ||
IterError::Bincode(e) => write!(f, "bincode error while reading entry {}", e), | ||
} | ||
} | ||
} | ||
|
||
impl std::error::Error for IterError {} |
Oops, something went wrong.