bdk_wallet/wallet/
persisted.rs

1use core::{
2    fmt,
3    future::Future,
4    marker::PhantomData,
5    ops::{Deref, DerefMut},
6    pin::Pin,
7};
8
9use alloc::{boxed::Box, string::ToString};
10use chain::Merge;
11
12use crate::{
13    descriptor::{calc_checksum, DescriptorError},
14    ChangeSet, CreateParams, LoadParams, Wallet,
15};
16
17/// Trait that persists [`PersistedWallet`].
18///
19/// For an async version, use [`AsyncWalletPersister`].
20///
21/// Associated functions of this trait should not be called directly, and the trait is designed so
22/// that associated functions are hard to find (since they are not methods!). [`WalletPersister`] is
23/// used by [`PersistedWallet`] (a light wrapper around [`Wallet`]) which enforces some level of
24/// safety. Refer to [`PersistedWallet`] for more about the safety checks.
25pub trait WalletPersister {
26    /// Error type of the persister.
27    type Error;
28
29    /// Initialize the `persister` and load all data.
30    ///
31    /// This is called by [`PersistedWallet::create`] and [`PersistedWallet::load`] to ensure
32    /// the [`WalletPersister`] is initialized and returns all data in the `persister`.
33    ///
34    /// # Implementation Details
35    ///
36    /// The database schema of the `persister` (if any), should be initialized and migrated here.
37    ///
38    /// The implementation must return all data currently stored in the `persister`. If there is no
39    /// data, return an empty changeset (using [`ChangeSet::default()`]).
40    ///
41    /// Error should only occur on database failure. Multiple calls to `initialize` should not
42    /// error. Calling `initialize` inbetween calls to `persist` should not error.
43    ///
44    /// Calling [`persist`] before the `persister` is `initialize`d may error. However, some
45    /// persister implementations may NOT require initialization at all (and not error).
46    ///
47    /// [`persist`]: WalletPersister::persist
48    fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error>;
49
50    /// Persist the given `changeset` to the `persister`.
51    ///
52    /// This method can fail if the `persister` is not [`initialize`]d.
53    ///
54    /// [`initialize`]: WalletPersister::initialize
55    fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error>;
56}
57
58type FutureResult<'a, T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'a>>;
59
60/// Async trait that persists [`PersistedWallet`].
61///
62/// For a blocking version, use [`WalletPersister`].
63///
64/// Associated functions of this trait should not be called directly, and the trait is designed so
65/// that associated functions are hard to find (since they are not methods!).
66/// [`AsyncWalletPersister`] is used by [`PersistedWallet`] (a light wrapper around [`Wallet`])
67/// which enforces some level of safety. Refer to [`PersistedWallet`] for more about the safety
68/// checks.
69pub trait AsyncWalletPersister {
70    /// Error type of the persister.
71    type Error;
72
73    /// Initialize the `persister` and load all data.
74    ///
75    /// This is called by [`PersistedWallet::create_async`] and [`PersistedWallet::load_async`] to
76    /// ensure the [`AsyncWalletPersister`] is initialized and returns all data in the `persister`.
77    ///
78    /// # Implementation Details
79    ///
80    /// The database schema of the `persister` (if any), should be initialized and migrated here.
81    ///
82    /// The implementation must return all data currently stored in the `persister`. If there is no
83    /// data, return an empty changeset (using [`ChangeSet::default()`]).
84    ///
85    /// Error should only occur on database failure. Multiple calls to `initialize` should not
86    /// error. Calling `initialize` inbetween calls to `persist` should not error.
87    ///
88    /// Calling [`persist`] before the `persister` is `initialize`d may error. However, some
89    /// persister implementations may NOT require initialization at all (and not error).
90    ///
91    /// [`persist`]: AsyncWalletPersister::persist
92    fn initialize<'a>(persister: &'a mut Self) -> FutureResult<'a, ChangeSet, Self::Error>
93    where
94        Self: 'a;
95
96    /// Persist the given `changeset` to the `persister`.
97    ///
98    /// This method can fail if the `persister` is not [`initialize`]d.
99    ///
100    /// [`initialize`]: AsyncWalletPersister::initialize
101    fn persist<'a>(
102        persister: &'a mut Self,
103        changeset: &'a ChangeSet,
104    ) -> FutureResult<'a, (), Self::Error>
105    where
106        Self: 'a;
107}
108
109/// Represents a persisted wallet which persists into type `P`.
110///
111/// This is a light wrapper around [`Wallet`] that enforces some level of safety-checking when used
112/// with a [`WalletPersister`] or [`AsyncWalletPersister`] implementation. Safety checks assume that
113/// [`WalletPersister`] and/or [`AsyncWalletPersister`] are implemented correctly.
114///
115/// Checks include:
116///
117/// * Ensure the persister is initialized before data is persisted.
118/// * Ensure there were no previously persisted wallet data before creating a fresh wallet and
119///   persisting it.
120/// * Only clear the staged changes of [`Wallet`] after persisting succeeds.
121/// * Ensure the wallet is persisted to the same `P` type as when created/loaded. Note that this is
122///   not completely fool-proof as you can have multiple instances of the same `P` type that are
123///   connected to different databases.
124#[derive(Debug)]
125pub struct PersistedWallet<P> {
126    inner: Wallet,
127    _marker: PhantomData<fn(&mut P)>,
128}
129
130impl<P> Deref for PersistedWallet<P> {
131    type Target = Wallet;
132
133    fn deref(&self) -> &Self::Target {
134        &self.inner
135    }
136}
137
138impl<P> DerefMut for PersistedWallet<P> {
139    fn deref_mut(&mut self) -> &mut Self::Target {
140        &mut self.inner
141    }
142}
143
144/// Methods when `P` is a [`WalletPersister`].
145impl<P: WalletPersister> PersistedWallet<P> {
146    /// Create a new [`PersistedWallet`] with the given `persister` and `params`.
147    pub fn create(
148        persister: &mut P,
149        params: CreateParams,
150    ) -> Result<Self, CreateWithPersistError<P::Error>> {
151        let existing = P::initialize(persister).map_err(CreateWithPersistError::Persist)?;
152        if !existing.is_empty() {
153            return Err(CreateWithPersistError::DataAlreadyExists(existing));
154        }
155        let mut inner =
156            Wallet::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
157        if let Some(changeset) = inner.take_staged() {
158            P::persist(persister, &changeset).map_err(CreateWithPersistError::Persist)?;
159        }
160        Ok(Self {
161            inner,
162            _marker: PhantomData,
163        })
164    }
165
166    /// Load a previously [`PersistedWallet`] from the given `persister` and `params`.
167    pub fn load(
168        persister: &mut P,
169        params: LoadParams,
170    ) -> Result<Option<Self>, LoadWithPersistError<P::Error>> {
171        let changeset = P::initialize(persister).map_err(LoadWithPersistError::Persist)?;
172        Wallet::load_with_params(changeset, params)
173            .map(|opt| {
174                opt.map(|inner| PersistedWallet {
175                    inner,
176                    _marker: PhantomData,
177                })
178            })
179            .map_err(LoadWithPersistError::InvalidChangeSet)
180    }
181
182    /// Persist staged changes of wallet into `persister`.
183    ///
184    /// Returns whether any new changes were persisted.
185    ///
186    /// If the `persister` errors, the staged changes will not be cleared.
187    pub fn persist(&mut self, persister: &mut P) -> Result<bool, P::Error> {
188        match self.inner.staged_mut() {
189            Some(stage) => {
190                P::persist(persister, &*stage)?;
191                let _ = stage.take();
192                Ok(true)
193            }
194            None => Ok(false),
195        }
196    }
197}
198
199/// Methods when `P` is an [`AsyncWalletPersister`].
200impl<P: AsyncWalletPersister> PersistedWallet<P> {
201    /// Create a new [`PersistedWallet`] with the given async `persister` and `params`.
202    pub async fn create_async(
203        persister: &mut P,
204        params: CreateParams,
205    ) -> Result<Self, CreateWithPersistError<P::Error>> {
206        let existing = P::initialize(persister)
207            .await
208            .map_err(CreateWithPersistError::Persist)?;
209        if !existing.is_empty() {
210            return Err(CreateWithPersistError::DataAlreadyExists(existing));
211        }
212        let mut inner =
213            Wallet::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
214        if let Some(changeset) = inner.take_staged() {
215            P::persist(persister, &changeset)
216                .await
217                .map_err(CreateWithPersistError::Persist)?;
218        }
219        Ok(Self {
220            inner,
221            _marker: PhantomData,
222        })
223    }
224
225    /// Load a previously [`PersistedWallet`] from the given async `persister` and `params`.
226    pub async fn load_async(
227        persister: &mut P,
228        params: LoadParams,
229    ) -> Result<Option<Self>, LoadWithPersistError<P::Error>> {
230        let changeset = P::initialize(persister)
231            .await
232            .map_err(LoadWithPersistError::Persist)?;
233        Wallet::load_with_params(changeset, params)
234            .map(|opt| {
235                opt.map(|inner| PersistedWallet {
236                    inner,
237                    _marker: PhantomData,
238                })
239            })
240            .map_err(LoadWithPersistError::InvalidChangeSet)
241    }
242
243    /// Persist staged changes of wallet into an async `persister`.
244    ///
245    /// Returns whether any new changes were persisted.
246    ///
247    /// If the `persister` errors, the staged changes will not be cleared.
248    pub async fn persist_async(&mut self, persister: &mut P) -> Result<bool, P::Error> {
249        match self.inner.staged_mut() {
250            Some(stage) => {
251                P::persist(persister, &*stage).await?;
252                let _ = stage.take();
253                Ok(true)
254            }
255            None => Ok(false),
256        }
257    }
258}
259
260#[cfg(feature = "rusqlite")]
261impl WalletPersister for bdk_chain::rusqlite::Transaction<'_> {
262    type Error = bdk_chain::rusqlite::Error;
263
264    fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error> {
265        ChangeSet::init_sqlite_tables(&*persister)?;
266        ChangeSet::from_sqlite(persister)
267    }
268
269    fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> {
270        changeset.persist_to_sqlite(persister)
271    }
272}
273
274#[cfg(feature = "rusqlite")]
275impl WalletPersister for bdk_chain::rusqlite::Connection {
276    type Error = bdk_chain::rusqlite::Error;
277
278    fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error> {
279        let db_tx = persister.transaction()?;
280        ChangeSet::init_sqlite_tables(&db_tx)?;
281        let changeset = ChangeSet::from_sqlite(&db_tx)?;
282        db_tx.commit()?;
283        Ok(changeset)
284    }
285
286    fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> {
287        let db_tx = persister.transaction()?;
288        changeset.persist_to_sqlite(&db_tx)?;
289        db_tx.commit()
290    }
291}
292
293/// Error for [`bdk_file_store`]'s implementation of [`WalletPersister`].
294#[cfg(feature = "file_store")]
295#[derive(Debug)]
296pub enum FileStoreError {
297    /// Error when loading from the store.
298    Load(bdk_file_store::StoreErrorWithDump<ChangeSet>),
299    /// Error when writing to the store.
300    Write(std::io::Error),
301}
302
303#[cfg(feature = "file_store")]
304impl core::fmt::Display for FileStoreError {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        use core::fmt::Display;
307        match self {
308            FileStoreError::Load(e) => Display::fmt(e, f),
309            FileStoreError::Write(e) => Display::fmt(e, f),
310        }
311    }
312}
313
314#[cfg(feature = "file_store")]
315impl std::error::Error for FileStoreError {}
316
317#[cfg(feature = "file_store")]
318impl WalletPersister for bdk_file_store::Store<ChangeSet> {
319    type Error = FileStoreError;
320
321    fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error> {
322        persister
323            .dump()
324            .map(Option::unwrap_or_default)
325            .map_err(FileStoreError::Load)
326    }
327
328    fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> {
329        persister.append(changeset).map_err(FileStoreError::Write)
330    }
331}
332
333/// Error type for [`PersistedWallet::load`].
334#[derive(Debug, PartialEq)]
335pub enum LoadWithPersistError<E> {
336    /// Error from persistence.
337    Persist(E),
338    /// Occurs when the loaded changeset cannot construct [`Wallet`].
339    InvalidChangeSet(crate::LoadError),
340}
341
342impl<E: fmt::Display> fmt::Display for LoadWithPersistError<E> {
343    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344        match self {
345            Self::Persist(err) => fmt::Display::fmt(err, f),
346            Self::InvalidChangeSet(err) => fmt::Display::fmt(&err, f),
347        }
348    }
349}
350
351#[cfg(feature = "std")]
352impl<E: fmt::Debug + fmt::Display> std::error::Error for LoadWithPersistError<E> {}
353
354/// Error type for [`PersistedWallet::create`].
355#[derive(Debug)]
356pub enum CreateWithPersistError<E> {
357    /// Error from persistence.
358    Persist(E),
359    /// Persister already has wallet data.
360    DataAlreadyExists(ChangeSet),
361    /// Occurs when the loaded changeset cannot construct [`Wallet`].
362    Descriptor(DescriptorError),
363}
364
365impl<E: fmt::Display> fmt::Display for CreateWithPersistError<E> {
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        match self {
368            Self::Persist(err) => write!(f, "{err}"),
369            Self::DataAlreadyExists(changeset) => {
370                write!(
371                    f,
372                    "Cannot create wallet in a persister which already contains data: "
373                )?;
374                changeset_info(f, changeset)
375            }
376            Self::Descriptor(err) => {
377                write!(f, "{err}")
378            }
379        }
380    }
381}
382
383#[cfg(feature = "std")]
384impl<E: fmt::Debug + fmt::Display> std::error::Error for CreateWithPersistError<E> {}
385
386/// Helper function to display basic information about a [`ChangeSet`].
387fn changeset_info(f: &mut fmt::Formatter<'_>, changeset: &ChangeSet) -> fmt::Result {
388    let network = changeset
389        .network
390        .as_ref()
391        .map_or("None".to_string(), |n| n.to_string());
392
393    let descriptor_checksum = changeset
394        .descriptor
395        .as_ref()
396        .and_then(|d| calc_checksum(&d.to_string()).ok())
397        .unwrap_or_else(|| "None".to_string());
398
399    let change_descriptor_checksum = changeset
400        .change_descriptor
401        .as_ref()
402        .and_then(|d| calc_checksum(&d.to_string()).ok())
403        .unwrap_or_else(|| "None".to_string());
404
405    let tx_count = changeset.tx_graph.txs.len();
406
407    let anchor_count = changeset.tx_graph.anchors.len();
408
409    let block_count = if let Some(&count) = changeset.local_chain.blocks.keys().last() {
410        count
411    } else {
412        0
413    };
414
415    writeln!(f, "  Network: {network}")?;
416    writeln!(f, "  Descriptor Checksum: {descriptor_checksum}")?;
417    writeln!(
418        f,
419        "  Change Descriptor Checksum: {change_descriptor_checksum}"
420    )?;
421    writeln!(f, "  Transaction Count: {tx_count}")?;
422    writeln!(f, "  Anchor Count: {anchor_count}")?;
423    writeln!(f, "  Block Count: {block_count}")?;
424
425    Ok(())
426}