Skip to content

Commit 739b54f

Browse files
committed
Merge #1684: Refactor file store
54251a7 docs(file_store): Show how to overwrite original file during recovery (志宇) 52f2061 refactor(store)!: change Store's method and error names (nymius) fc76d66 feat(store): add `Bincode` error variant to FileError enum (nymius) 3998788 fix(store): replace `Path.exists` by `OpenOptions.create_new` (nymius) Pull request description: ### Description The `Store::open` method doesn't recovers the previous `Store` state saved in the file and emplaces the file pointer just after the magic bytes prefix, this later is agravated by `Store::append_changeset` which sets the file pointer after the last written changeset. The combination of both causes the lost of any previous changeset there may have been in the file. Is natural to think this shouldn't be the expected behavior, as @KnowWhoami pointed out in #1517, and the `Store` should recover the previous changesets stored in the file store. The approach proposed in #1632 triggered a discussion about more changes in `Store`, which motivated the current refactor. Besides the fix for #1517, the following methods have been changed/renamed/repurposed in Store: - `create`: create file and retrieve store, if exists fail. - `load`: load changesets from file, aggregate them and return aggregated changeset and `Store`. If there are problems with decoding, fail. - `dump`: aggregate all changesets and return them. - `load_or_create`: try load or fallback to create. Fixes #1517. Overrides #1632. ### Notes to the reviewers **Load** return type is `Result<(Option<C>, Store), StoreErrorWithDump>` to allow methods which doesn't use `WalletPersister` to get the aggregated changeset right away. **Dump** is kept to allow `WalletPersister::initialize` method to retrieve the aggregated changeset without forcing the inclusion of the additional parameters needed by `store::load` to the trait, which would also affect the `rusqlite` implementation. ### Changelog notice #### Added: - `StoreError` enum, which includes `Io`, `Bincode` and `InvalidMagicBytes`. - "not intended for production" notice in general `README` for `file store`. #### Changed: - `Store::create_new` to `Store::create`, with new return type: `Result<Self, StoreError>` - `Store::open` to `Store::load`, with new return type: `Result<(Self, Option<C>), StoreErrorWithDump<C>>` - `Store::open_or_create` to `Store::load_or_create`, with new return type: `Result<(Option<C>, Self), StoreErrorWithDump<C>> ` - `Store::aggregate_changesets` to `Store::dump`, with new return type: `Result<Option<C>, StoreErrorWithDump<C>>` - `FileError` to `StoreError` - `AggregateChangesetsError` to `StoreErrorWithDump`, which now can include all the variants of `StoreError` in the error field. #### Deleted: - `IterError` deleted. <!-- Notice the release manager should include in the release tag message changelog --> <!-- See https://keepachangelog.com/en/1.0.0/ for examples --> ### Checklists #### All Submissions: * ~~I've signed all my commits~~ * ~~I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)~~ * ~~I ran `cargo fmt` and `cargo clippy` before committing~~ #### New Features: * ~~I've added tests for the new feature~~ * ~~I've added docs for the new feature~~ #### Bugfixes: * ~~This pull request breaks the existing API~~ * ~~I've added tests to reproduce the issue which are now passing~~ * ~~I'm linking the issue being fixed by this PR~~ ACKs for top commit: evanlinjin: ACK 54251a7 Tree-SHA512: d41dee149af7d0c2eba4f0b84b360eb2aad2b5f3df2d3160de285370e637619c25156678ee84a12295c7d2410704182819f6c5c612f68f81556747ad7dd0eb17
2 parents 1975835 + 54251a7 commit 739b54f

File tree

8 files changed

+402
-270
lines changed

8 files changed

+402
-270
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The project is split up into several crates in the `/crates` directory:
4242
- [`wallet`](./crates/wallet): Contains the central high level `Wallet` type that is built from the low-level mechanisms provided by the other components
4343
- [`chain`](./crates/chain): Tools for storing and indexing chain data
4444
- [`persist`](./crates/persist): Types that define data persistence of a BDK wallet
45-
- [`file_store`](./crates/file_store): A (experimental) persistence backend for storing chain data in a single file.
45+
- [`file_store`](./crates/file_store): Persistence backend for storing chain data in a single file. Intended for testing and development purposes, not for production.
4646
- [`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.
4747
- [`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.
4848

crates/file_store/src/entry_iter.rs

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::StoreError;
12
use bincode::Options;
23
use std::{
34
fs::File,
@@ -37,7 +38,7 @@ impl<T> Iterator for EntryIter<'_, T>
3738
where
3839
T: serde::de::DeserializeOwned,
3940
{
40-
type Item = Result<T, IterError>;
41+
type Item = Result<T, StoreError>;
4142

4243
fn next(&mut self) -> Option<Self::Item> {
4344
if self.finished {
@@ -63,7 +64,7 @@ where
6364
}
6465
}
6566
self.db_file.seek(io::SeekFrom::Start(pos_before_read))?;
66-
Err(IterError::Bincode(*e))
67+
Err(StoreError::Bincode(*e))
6768
}
6869
}
6970
})()
@@ -80,29 +81,3 @@ impl<T> Drop for EntryIter<'_, T> {
8081
}
8182
}
8283
}
83-
84-
/// Error type for [`EntryIter`].
85-
#[derive(Debug)]
86-
pub enum IterError {
87-
/// Failure to read from the file.
88-
Io(io::Error),
89-
/// Failure to decode data from the file.
90-
Bincode(bincode::ErrorKind),
91-
}
92-
93-
impl core::fmt::Display for IterError {
94-
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
95-
match self {
96-
IterError::Io(e) => write!(f, "io error trying to read entry {}", e),
97-
IterError::Bincode(e) => write!(f, "bincode error while reading entry {}", e),
98-
}
99-
}
100-
}
101-
102-
impl From<io::Error> for IterError {
103-
fn from(value: io::Error) -> Self {
104-
IterError::Io(value)
105-
}
106-
}
107-
108-
impl std::error::Error for IterError {}

crates/file_store/src/lib.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ pub(crate) fn bincode_options() -> impl bincode::Options {
1313

1414
/// Error that occurs due to problems encountered with the file.
1515
#[derive(Debug)]
16-
pub enum FileError {
16+
pub enum StoreError {
1717
/// IO error, this may mean that the file is too short.
1818
Io(io::Error),
1919
/// Magic bytes do not match what is expected.
2020
InvalidMagicBytes { got: Vec<u8>, expected: Vec<u8> },
21+
/// Failure to decode data from the file.
22+
Bincode(bincode::ErrorKind),
2123
}
2224

23-
impl core::fmt::Display for FileError {
25+
impl core::fmt::Display for StoreError {
2426
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2527
match self {
2628
Self::Io(e) => write!(f, "io error trying to read file: {}", e),
@@ -29,14 +31,15 @@ impl core::fmt::Display for FileError {
2931
"file has invalid magic bytes: expected={:?} got={:?}",
3032
expected, got,
3133
),
34+
Self::Bincode(e) => write!(f, "bincode error while reading entry {}", e),
3235
}
3336
}
3437
}
3538

36-
impl From<io::Error> for FileError {
39+
impl From<io::Error> for StoreError {
3740
fn from(value: io::Error) -> Self {
3841
Self::Io(value)
3942
}
4043
}
4144

42-
impl std::error::Error for FileError {}
45+
impl std::error::Error for StoreError {}

0 commit comments

Comments
 (0)