Skip to content

Commit 05d353c

Browse files
committed
Merge #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 #895 and #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
2 parents e3c1370 + 4963240 commit 05d353c

File tree

10 files changed

+548
-100
lines changed

10 files changed

+548
-100
lines changed

crates/chain/src/indexed_tx_graph.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ impl<A: Anchor, IA: Append> Append for IndexedAdditions<A, IA> {
301301
self.graph_additions.append(other.graph_additions);
302302
self.index_additions.append(other.index_additions);
303303
}
304+
305+
fn is_empty(&self) -> bool {
306+
self.graph_additions.is_empty() && self.index_additions.is_empty()
307+
}
304308
}
305309

306310
/// Represents a structure that can index transaction data.

crates/chain/src/keychain.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ impl<K: Ord> Append for DerivationAdditions<K> {
8484

8585
self.0.append(&mut other.0);
8686
}
87+
88+
fn is_empty(&self) -> bool {
89+
self.0.is_empty()
90+
}
8791
}
8892

8993
impl<K> Default for DerivationAdditions<K> {

crates/chain/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub mod tx_graph;
3333
pub use tx_data_traits::*;
3434
mod chain_oracle;
3535
pub use chain_oracle::*;
36+
mod persist;
37+
pub use persist::*;
3638

3739
#[doc(hidden)]
3840
pub mod example_utils;

crates/chain/src/persist.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use core::convert::Infallible;
2+
3+
use crate::Append;
4+
5+
/// `Persist` wraps a [`PersistBackend`] (`B`) to create a convenient staging area for changes (`C`)
6+
/// before they are persisted.
7+
///
8+
/// Not all changes to the in-memory representation needs to be written to disk right away, so
9+
/// [`Persist::stage`] can be used to *stage* changes first and then [`Persist::commit`] can be used
10+
/// to write changes to disk.
11+
#[derive(Debug)]
12+
pub struct Persist<B, C> {
13+
backend: B,
14+
stage: C,
15+
}
16+
17+
impl<B, C> Persist<B, C>
18+
where
19+
B: PersistBackend<C>,
20+
C: Default + Append,
21+
{
22+
/// Create a new [`Persist`] from [`PersistBackend`].
23+
pub fn new(backend: B) -> Self {
24+
Self {
25+
backend,
26+
stage: Default::default(),
27+
}
28+
}
29+
30+
/// Stage a `changeset` to be commited later with [`commit`].
31+
///
32+
/// [`commit`]: Self::commit
33+
pub fn stage(&mut self, changeset: C) {
34+
self.stage.append(changeset)
35+
}
36+
37+
/// Get the changes that have not been commited yet.
38+
pub fn staged(&self) -> &C {
39+
&self.stage
40+
}
41+
42+
/// Commit the staged changes to the underlying persistance backend.
43+
///
44+
/// Returns a backend-defined error if this fails.
45+
pub fn commit(&mut self) -> Result<(), B::WriteError> {
46+
let mut temp = C::default();
47+
core::mem::swap(&mut temp, &mut self.stage);
48+
self.backend.write_changes(&temp)
49+
}
50+
}
51+
52+
/// A persistence backend for [`Persist`].
53+
///
54+
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
55+
/// that are to be persisted, or retrieved from persistence.
56+
pub trait PersistBackend<C> {
57+
/// The error the backend returns when it fails to write.
58+
type WriteError: core::fmt::Debug;
59+
60+
/// The error the backend returns when it fails to load changesets `C`.
61+
type LoadError: core::fmt::Debug;
62+
63+
/// Writes a changeset to the persistence backend.
64+
///
65+
/// It is up to the backend what it does with this. It could store every changeset in a list or
66+
/// it inserts the actual changes into a more structured database. All it needs to guarantee is
67+
/// that [`load_from_persistence`] restores a keychain tracker to what it should be if all
68+
/// changesets had been applied sequentially.
69+
///
70+
/// [`load_from_persistence`]: Self::load_from_persistence
71+
fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError>;
72+
73+
/// Return the aggregate changeset `C` from persistence.
74+
fn load_from_persistence(&mut self) -> Result<C, Self::LoadError>;
75+
}
76+
77+
impl<C: Default> PersistBackend<C> for () {
78+
type WriteError = Infallible;
79+
80+
type LoadError = Infallible;
81+
82+
fn write_changes(&mut self, _changeset: &C) -> Result<(), Self::WriteError> {
83+
Ok(())
84+
}
85+
86+
fn load_from_persistence(&mut self) -> Result<C, Self::LoadError> {
87+
Ok(C::default())
88+
}
89+
}

crates/chain/src/tx_data_traits.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::collections::BTreeMap;
22
use crate::collections::BTreeSet;
33
use crate::BlockId;
4+
use alloc::vec::Vec;
45
use bitcoin::{Block, OutPoint, Transaction, TxOut};
56

67
/// Trait to do something with every txout contained in a structure.
@@ -64,20 +65,56 @@ impl<A: Anchor> Anchor for &'static A {
6465
pub trait Append {
6566
/// Append another object of the same type onto `self`.
6667
fn append(&mut self, other: Self);
68+
69+
/// Returns whether the structure is considered empty.
70+
fn is_empty(&self) -> bool;
6771
}
6872

6973
impl Append for () {
7074
fn append(&mut self, _other: Self) {}
75+
76+
fn is_empty(&self) -> bool {
77+
true
78+
}
7179
}
7280

7381
impl<K: Ord, V> Append for BTreeMap<K, V> {
7482
fn append(&mut self, mut other: Self) {
7583
BTreeMap::append(self, &mut other)
7684
}
85+
86+
fn is_empty(&self) -> bool {
87+
BTreeMap::is_empty(self)
88+
}
7789
}
7890

7991
impl<T: Ord> Append for BTreeSet<T> {
8092
fn append(&mut self, mut other: Self) {
8193
BTreeSet::append(self, &mut other)
8294
}
95+
96+
fn is_empty(&self) -> bool {
97+
BTreeSet::is_empty(self)
98+
}
99+
}
100+
101+
impl<T> Append for Vec<T> {
102+
fn append(&mut self, mut other: Self) {
103+
Vec::append(self, &mut other)
104+
}
105+
106+
fn is_empty(&self) -> bool {
107+
Vec::is_empty(self)
108+
}
109+
}
110+
111+
impl<A: Append, B: Append> Append for (A, B) {
112+
fn append(&mut self, other: Self) {
113+
Append::append(&mut self.0, other.0);
114+
Append::append(&mut self.1, other.1);
115+
}
116+
117+
fn is_empty(&self) -> bool {
118+
Append::is_empty(&self.0) && Append::is_empty(&self.1)
119+
}
83120
}

crates/chain/src/tx_graph.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,13 @@ impl<A: Ord> Append for Additions<A> {
940940
.collect::<Vec<_>>(),
941941
);
942942
}
943+
944+
fn is_empty(&self) -> bool {
945+
self.tx.is_empty()
946+
&& self.txout.is_empty()
947+
&& self.anchors.is_empty()
948+
&& self.last_seen.is_empty()
949+
}
943950
}
944951

945952
impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use bincode::Options;
2+
use std::{
3+
fs::File,
4+
io::{self, Seek},
5+
marker::PhantomData,
6+
};
7+
8+
use crate::bincode_options;
9+
10+
/// Iterator over entries in a file store.
11+
///
12+
/// Reads and returns an entry each time [`next`] is called. If an error occurs while reading the
13+
/// iterator will yield a `Result::Err(_)` instead and then `None` for the next call to `next`.
14+
///
15+
/// [`next`]: Self::next
16+
pub struct EntryIter<'t, T> {
17+
db_file: Option<&'t mut File>,
18+
19+
/// The file position for the first read of `db_file`.
20+
start_pos: Option<u64>,
21+
types: PhantomData<T>,
22+
}
23+
24+
impl<'t, T> EntryIter<'t, T> {
25+
pub fn new(start_pos: u64, db_file: &'t mut File) -> Self {
26+
Self {
27+
db_file: Some(db_file),
28+
start_pos: Some(start_pos),
29+
types: PhantomData,
30+
}
31+
}
32+
}
33+
34+
impl<'t, T> Iterator for EntryIter<'t, T>
35+
where
36+
T: serde::de::DeserializeOwned,
37+
{
38+
type Item = Result<T, IterError>;
39+
40+
fn next(&mut self) -> Option<Self::Item> {
41+
// closure which reads a single entry starting from `self.pos`
42+
let read_one = |f: &mut File, start_pos: Option<u64>| -> Result<Option<T>, IterError> {
43+
let pos = match start_pos {
44+
Some(pos) => f.seek(io::SeekFrom::Start(pos))?,
45+
None => f.stream_position()?,
46+
};
47+
48+
match bincode_options().deserialize_from(&*f) {
49+
Ok(changeset) => {
50+
f.stream_position()?;
51+
Ok(Some(changeset))
52+
}
53+
Err(e) => {
54+
if let bincode::ErrorKind::Io(inner) = &*e {
55+
if inner.kind() == io::ErrorKind::UnexpectedEof {
56+
let eof = f.seek(io::SeekFrom::End(0))?;
57+
if pos == eof {
58+
return Ok(None);
59+
}
60+
}
61+
}
62+
f.seek(io::SeekFrom::Start(pos))?;
63+
Err(IterError::Bincode(*e))
64+
}
65+
}
66+
};
67+
68+
let result = read_one(self.db_file.as_mut()?, self.start_pos.take());
69+
if result.is_err() {
70+
self.db_file = None;
71+
}
72+
result.transpose()
73+
}
74+
}
75+
76+
impl From<io::Error> for IterError {
77+
fn from(value: io::Error) -> Self {
78+
IterError::Io(value)
79+
}
80+
}
81+
82+
/// Error type for [`EntryIter`].
83+
#[derive(Debug)]
84+
pub enum IterError {
85+
/// Failure to read from the file.
86+
Io(io::Error),
87+
/// Failure to decode data from the file.
88+
Bincode(bincode::ErrorKind),
89+
}
90+
91+
impl core::fmt::Display for IterError {
92+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
93+
match self {
94+
IterError::Io(e) => write!(f, "io error trying to read entry {}", e),
95+
IterError::Bincode(e) => write!(f, "bincode error while reading entry {}", e),
96+
}
97+
}
98+
}
99+
100+
impl std::error::Error for IterError {}

0 commit comments

Comments
 (0)