]> Untitled Git - bdk/commitdiff
feat(wallet)!: introduce `WalletPersister`
author志宇 <hello@evanlinjin.me>
Fri, 9 Aug 2024 16:14:15 +0000 (16:14 +0000)
committer志宇 <hello@evanlinjin.me>
Thu, 15 Aug 2024 04:05:32 +0000 (04:05 +0000)
This replaces `bdk_chain::PersistWith` which wanted to persist anything
(not only `Wallet`), hence, requiring a whole bunch of generic
parameters.

Having `WalletPersister` dedicated to persisting `Wallet` simplifies the
trait by a lot.

In addition, `AsyncWalletPersister` has proper lifetime bounds whereas
`bdk_chain::PersistAsyncWith` did not.

crates/wallet/src/wallet/params.rs
crates/wallet/src/wallet/persisted.rs
crates/wallet/tests/wallet.rs

index 9b0795395815bd9286ddc5b52daf17cc8f5bbe2d..22e7a5b73a92e337f7b38a92816d24320fc73c4b 100644 (file)
@@ -1,12 +1,13 @@
 use alloc::boxed::Box;
-use bdk_chain::{keychain_txout::DEFAULT_LOOKAHEAD, PersistAsyncWith, PersistWith};
+use bdk_chain::keychain_txout::DEFAULT_LOOKAHEAD;
 use bitcoin::{BlockHash, Network};
 use miniscript::descriptor::KeyMap;
 
 use crate::{
     descriptor::{DescriptorError, ExtendedDescriptor, IntoWalletDescriptor},
     utils::SecpCtx,
-    KeychainKind, Wallet,
+    AsyncWalletPersister, CreateWithPersistError, KeychainKind, LoadWithPersistError, Wallet,
+    WalletPersister,
 };
 
 use super::{ChangeSet, LoadError, PersistedWallet};
@@ -109,25 +110,25 @@ impl CreateParams {
     }
 
     /// Create [`PersistedWallet`] with the given `Db`.
-    pub fn create_wallet<Db>(
+    pub fn create_wallet<P>(
         self,
-        db: &mut Db,
-    ) -> Result<PersistedWallet, <Wallet as PersistWith<Db>>::CreateError>
+        persister: &mut P,
+    ) -> Result<PersistedWallet, CreateWithPersistError<P::Error>>
     where
-        Wallet: PersistWith<Db, CreateParams = Self>,
+        P: WalletPersister,
     {
-        PersistedWallet::create(db, self)
+        PersistedWallet::create(persister, self)
     }
 
     /// Create [`PersistedWallet`] with the given async `Db`.
-    pub async fn create_wallet_async<Db>(
+    pub async fn create_wallet_async<P>(
         self,
-        db: &mut Db,
-    ) -> Result<PersistedWallet, <Wallet as PersistAsyncWith<Db>>::CreateError>
+        persister: &mut P,
+    ) -> Result<PersistedWallet, CreateWithPersistError<P::Error>>
     where
-        Wallet: PersistAsyncWith<Db, CreateParams = Self>,
+        P: AsyncWalletPersister,
     {
-        PersistedWallet::create_async(db, self).await
+        PersistedWallet::create_async(persister, self).await
     }
 
     /// Create [`Wallet`] without persistence.
@@ -220,25 +221,25 @@ impl LoadParams {
     }
 
     /// Load [`PersistedWallet`] with the given `Db`.
-    pub fn load_wallet<Db>(
+    pub fn load_wallet<P>(
         self,
-        db: &mut Db,
-    ) -> Result<Option<PersistedWallet>, <Wallet as PersistWith<Db>>::LoadError>
+        persister: &mut P,
+    ) -> Result<Option<PersistedWallet>, LoadWithPersistError<P::Error>>
     where
-        Wallet: PersistWith<Db, LoadParams = Self>,
+        P: WalletPersister,
     {
-        PersistedWallet::load(db, self)
+        PersistedWallet::load(persister, self)
     }
 
     /// Load [`PersistedWallet`] with the given async `Db`.
-    pub async fn load_wallet_async<Db>(
+    pub async fn load_wallet_async<P>(
         self,
-        db: &mut Db,
-    ) -> Result<Option<PersistedWallet>, <Wallet as PersistAsyncWith<Db>>::LoadError>
+        persister: &mut P,
+    ) -> Result<Option<PersistedWallet>, LoadWithPersistError<P::Error>>
     where
-        Wallet: PersistAsyncWith<Db, LoadParams = Self>,
+        P: AsyncWalletPersister,
     {
-        PersistedWallet::load_async(db, self).await
+        PersistedWallet::load_async(persister, self).await
     }
 
     /// Load [`Wallet`] without persistence.
index cc9f267f4348f1cc7494ece5c6f99b1b10b98ca0..a7181a3c08a4e3748e93860192d69ef1e7722000 100644 (file)
-use core::fmt;
+use core::{
+    fmt,
+    future::Future,
+    ops::{Deref, DerefMut},
+    pin::Pin,
+};
 
-use crate::{descriptor::DescriptorError, Wallet};
+use alloc::boxed::Box;
+use chain::{Merge, Staged};
+
+use crate::{descriptor::DescriptorError, ChangeSet, CreateParams, LoadParams, Wallet};
+
+/// Trait that persists [`Wallet`].
+///
+/// For an async version, use [`AsyncWalletPersister`].
+///
+/// Associated functions of this trait should not be called directly, and the trait is designed so
+/// that associated functions are hard to find (since they are not methods!). [`WalletPersister`] is
+/// used by [`PersistedWallet`] (a light wrapper around [`Wallet`]) which enforces some level of
+/// safety. Refer to [`PersistedWallet`] for more about the safety checks.
+pub trait WalletPersister {
+    /// Error type of the persister.
+    type Error;
+
+    /// Initialize the `persister` and load all data.
+    ///
+    /// This is called by [`PersistedWallet::create`] and [`PersistedWallet::load`] to ensure
+    /// the [`WalletPersister`] is initialized and returns all data in the `persister`.
+    ///
+    /// # Implementation Details
+    ///
+    /// The database schema of the `persister` (if any), should be initialized and migrated here.
+    ///
+    /// The implementation must return all data currently stored in the `persister`. If there is no
+    /// data, return an empty changeset (using [`ChangeSet::default()`]).
+    ///
+    /// Error should only occur on database failure. Multiple calls to `initialize` should not
+    /// error. Calling [`persist`] before calling `initialize` should not error either.
+    ///
+    /// [`persist`]: WalletPersister::persist
+    fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error>;
+
+    /// Persist the given `changeset` to the `persister`.
+    ///
+    /// This method can fail if the `persister` is not [`initialize`]d.
+    ///
+    /// [`initialize`]: WalletPersister::initialize
+    fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error>;
+}
+
+type FutureResult<'a, T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'a>>;
+
+/// Async trait that persists [`Wallet`].
+///
+/// For a blocking version, use [`WalletPersister`].
+///
+/// Associated functions of this trait should not be called directly, and the trait is designed so
+/// that associated functions are hard to find (since they are not methods!). [`WalletPersister`] is
+/// used by [`PersistedWallet`] (a light wrapper around [`Wallet`]) which enforces some level of
+/// safety. Refer to [`PersistedWallet`] for more about the safety checks.
+pub trait AsyncWalletPersister {
+    /// Error type of the persister.
+    type Error;
+
+    /// Initialize the `persister` and load all data.
+    ///
+    /// This is called by [`PersistedWallet::create_async`] and [`PersistedWallet::load_async`] to
+    /// ensure the [`WalletPersister`] is initialized and returns all data in the `persister`.
+    ///
+    /// # Implementation Details
+    ///
+    /// The database schema of the `persister` (if any), should be initialized and migrated here.
+    ///
+    /// The implementation must return all data currently stored in the `persister`. If there is no
+    /// data, return an empty changeset (using [`ChangeSet::default()`]).
+    ///
+    /// Error should only occur on database failure. Multiple calls to `initialize` should not
+    /// error. Calling [`persist`] before calling `initialize` should not error either.
+    ///
+    /// [`persist`]: AsyncWalletPersister::persist
+    fn initialize<'a>(persister: &'a mut Self) -> FutureResult<'a, ChangeSet, Self::Error>
+    where
+        Self: 'a;
+
+    /// Persist the given `changeset` to the `persister`.
+    ///
+    /// This method can fail if the `persister` is not [`initialize`]d.
+    ///
+    /// [`initialize`]: AsyncWalletPersister::initialize
+    fn persist<'a>(
+        persister: &'a mut Self,
+        changeset: &'a ChangeSet,
+    ) -> FutureResult<'a, (), Self::Error>
+    where
+        Self: 'a;
+}
 
 /// Represents a persisted wallet.
-pub type PersistedWallet = bdk_chain::Persisted<Wallet>;
+///
+/// This is a light wrapper around [`Wallet`] that enforces some level of safety-checking when used
+/// with a [`WalletPersister`] or [`AsyncWalletPersister`] implementation. Safety checks assume that
+/// [`WalletPersister`] and/or [`AsyncWalletPersister`] are implemented correctly.
+///
+/// Checks include:
+///
+/// * Ensure the persister is initialized before data is persisted.
+/// * Ensure there were no previously persisted wallet data before creating a fresh wallet and
+///     persisting it.
+/// * Only clear the staged changes of [`Wallet`] after persisting succeeds.
+#[derive(Debug)]
+pub struct PersistedWallet(pub(crate) Wallet);
 
-#[cfg(feature = "rusqlite")]
-impl<'c> chain::PersistWith<bdk_chain::rusqlite::Transaction<'c>> for Wallet {
-    type CreateParams = crate::CreateParams;
-    type LoadParams = crate::LoadParams;
-
-    type CreateError = CreateWithPersistError<bdk_chain::rusqlite::Error>;
-    type LoadError = LoadWithPersistError<bdk_chain::rusqlite::Error>;
-    type PersistError = bdk_chain::rusqlite::Error;
-
-    fn create(
-        db: &mut bdk_chain::rusqlite::Transaction<'c>,
-        params: Self::CreateParams,
-    ) -> Result<Self, Self::CreateError> {
-        let mut wallet =
-            Self::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
-        if let Some(changeset) = wallet.take_staged() {
-            changeset
-                .persist_to_sqlite(db)
+impl Deref for PersistedWallet {
+    type Target = Wallet;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for PersistedWallet {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl PersistedWallet {
+    /// Create a new [`PersistedWallet`] with the given `persister` and `params`.
+    pub fn create<P>(
+        persister: &mut P,
+        params: CreateParams,
+    ) -> Result<Self, CreateWithPersistError<P::Error>>
+    where
+        P: WalletPersister,
+    {
+        let existing = P::initialize(persister).map_err(CreateWithPersistError::Persist)?;
+        if !existing.is_empty() {
+            return Err(CreateWithPersistError::DataAlreadyExists(existing));
+        }
+        let mut inner =
+            Wallet::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
+        if let Some(changeset) = inner.take_staged() {
+            P::persist(persister, &changeset).map_err(CreateWithPersistError::Persist)?;
+        }
+        Ok(Self(inner))
+    }
+
+    /// Create a new [`PersistedWallet`] witht the given async `persister` and `params`.
+    pub async fn create_async<P>(
+        persister: &mut P,
+        params: CreateParams,
+    ) -> Result<Self, CreateWithPersistError<P::Error>>
+    where
+        P: AsyncWalletPersister,
+    {
+        let existing = P::initialize(persister)
+            .await
+            .map_err(CreateWithPersistError::Persist)?;
+        if !existing.is_empty() {
+            return Err(CreateWithPersistError::DataAlreadyExists(existing));
+        }
+        let mut inner =
+            Wallet::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
+        if let Some(changeset) = inner.take_staged() {
+            P::persist(persister, &changeset)
+                .await
                 .map_err(CreateWithPersistError::Persist)?;
         }
-        Ok(wallet)
+        Ok(Self(inner))
+    }
+
+    /// Load a previously [`PersistedWallet`] from the given `persister` and `params`.
+    pub fn load<P>(
+        persister: &mut P,
+        params: LoadParams,
+    ) -> Result<Option<Self>, LoadWithPersistError<P::Error>>
+    where
+        P: WalletPersister,
+    {
+        let changeset = P::initialize(persister).map_err(LoadWithPersistError::Persist)?;
+        Wallet::load_with_params(changeset, params)
+            .map(|opt| opt.map(PersistedWallet))
+            .map_err(LoadWithPersistError::InvalidChangeSet)
     }
 
-    fn load(
-        conn: &mut bdk_chain::rusqlite::Transaction<'c>,
-        params: Self::LoadParams,
-    ) -> Result<Option<Self>, Self::LoadError> {
-        let changeset =
-            crate::ChangeSet::from_sqlite(conn).map_err(LoadWithPersistError::Persist)?;
-        if chain::Merge::is_empty(&changeset) {
-            return Ok(None);
+    /// Load a previously [`PersistedWallet`] from the given async `persister` and `params`.
+    pub async fn load_async<P>(
+        persister: &mut P,
+        params: LoadParams,
+    ) -> Result<Option<Self>, LoadWithPersistError<P::Error>>
+    where
+        P: AsyncWalletPersister,
+    {
+        let changeset = P::initialize(persister)
+            .await
+            .map_err(LoadWithPersistError::Persist)?;
+        Wallet::load_with_params(changeset, params)
+            .map(|opt| opt.map(PersistedWallet))
+            .map_err(LoadWithPersistError::InvalidChangeSet)
+    }
+
+    /// Persist staged changes of wallet into `persister`.
+    ///
+    /// If the `persister` errors, the staged changes will not be cleared.
+    pub fn persist<P>(&mut self, persister: &mut P) -> Result<bool, P::Error>
+    where
+        P: WalletPersister,
+    {
+        let stage = Staged::staged(&mut self.0);
+        if stage.is_empty() {
+            return Ok(false);
         }
-        Self::load_with_params(changeset, params).map_err(LoadWithPersistError::InvalidChangeSet)
+        P::persist(persister, &*stage)?;
+        stage.take();
+        Ok(true)
     }
 
-    fn persist(
-        db: &mut bdk_chain::rusqlite::Transaction<'c>,
-        changeset: &<Self as chain::Staged>::ChangeSet,
-    ) -> Result<(), Self::PersistError> {
-        changeset.persist_to_sqlite(db)
+    /// Persist staged changes of wallet into an async `persister`.
+    ///
+    /// If the `persister` errors, the staged changes will not be cleared.
+    pub async fn persist_async<'a, P>(&'a mut self, persister: &mut P) -> Result<bool, P::Error>
+    where
+        P: AsyncWalletPersister,
+    {
+        let stage = Staged::staged(&mut self.0);
+        if stage.is_empty() {
+            return Ok(false);
+        }
+        P::persist(persister, &*stage).await?;
+        stage.take();
+        Ok(true)
     }
 }
 
 #[cfg(feature = "rusqlite")]
-impl chain::PersistWith<bdk_chain::rusqlite::Connection> for Wallet {
-    type CreateParams = crate::CreateParams;
-    type LoadParams = crate::LoadParams;
-
-    type CreateError = CreateWithPersistError<bdk_chain::rusqlite::Error>;
-    type LoadError = LoadWithPersistError<bdk_chain::rusqlite::Error>;
-    type PersistError = bdk_chain::rusqlite::Error;
-
-    fn create(
-        db: &mut bdk_chain::rusqlite::Connection,
-        params: Self::CreateParams,
-    ) -> Result<Self, Self::CreateError> {
-        let mut db_tx = db.transaction().map_err(CreateWithPersistError::Persist)?;
-        let wallet = chain::PersistWith::create(&mut db_tx, params)?;
-        db_tx.commit().map_err(CreateWithPersistError::Persist)?;
-        Ok(wallet)
-    }
-
-    fn load(
-        db: &mut bdk_chain::rusqlite::Connection,
-        params: Self::LoadParams,
-    ) -> Result<Option<Self>, Self::LoadError> {
-        let mut db_tx = db.transaction().map_err(LoadWithPersistError::Persist)?;
-        let wallet_opt = chain::PersistWith::load(&mut db_tx, params)?;
-        db_tx.commit().map_err(LoadWithPersistError::Persist)?;
-        Ok(wallet_opt)
-    }
-
-    fn persist(
-        db: &mut bdk_chain::rusqlite::Connection,
-        changeset: &<Self as chain::Staged>::ChangeSet,
-    ) -> Result<(), Self::PersistError> {
-        let db_tx = db.transaction()?;
+impl<'c> WalletPersister for bdk_chain::rusqlite::Transaction<'c> {
+    type Error = bdk_chain::rusqlite::Error;
+
+    fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error> {
+        ChangeSet::from_sqlite(persister)
+    }
+
+    fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> {
+        changeset.persist_to_sqlite(persister)
+    }
+}
+
+#[cfg(feature = "rusqlite")]
+impl WalletPersister for bdk_chain::rusqlite::Connection {
+    type Error = bdk_chain::rusqlite::Error;
+
+    fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error> {
+        let db_tx = persister.transaction()?;
+        let changeset = ChangeSet::from_sqlite(&db_tx)?;
+        db_tx.commit()?;
+        Ok(changeset)
+    }
+
+    fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> {
+        let db_tx = persister.transaction()?;
         changeset.persist_to_sqlite(&db_tx)?;
         db_tx.commit()
     }
 }
 
+/// Error for [`bdk_file_store`]'s implementation of [`WalletPersister`].
 #[cfg(feature = "file_store")]
-impl chain::PersistWith<bdk_file_store::Store<crate::ChangeSet>> for Wallet {
-    type CreateParams = crate::CreateParams;
-    type LoadParams = crate::LoadParams;
-    type CreateError = CreateWithPersistError<std::io::Error>;
-    type LoadError =
-        LoadWithPersistError<bdk_file_store::AggregateChangesetsError<crate::ChangeSet>>;
-    type PersistError = std::io::Error;
-
-    fn create(
-        db: &mut bdk_file_store::Store<crate::ChangeSet>,
-        params: Self::CreateParams,
-    ) -> Result<Self, Self::CreateError> {
-        let mut wallet =
-            Self::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
-        if let Some(changeset) = wallet.take_staged() {
-            db.append_changeset(&changeset)
-                .map_err(CreateWithPersistError::Persist)?;
+#[derive(Debug)]
+pub enum FileStoreError {
+    /// Error when loading from the store.
+    Load(bdk_file_store::AggregateChangesetsError<ChangeSet>),
+    /// Error when writing to the store.
+    Write(std::io::Error),
+}
+
+#[cfg(feature = "file_store")]
+impl core::fmt::Display for FileStoreError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        use core::fmt::Display;
+        match self {
+            FileStoreError::Load(e) => Display::fmt(e, f),
+            FileStoreError::Write(e) => Display::fmt(e, f),
         }
-        Ok(wallet)
     }
+}
+
+#[cfg(feature = "file_store")]
+impl std::error::Error for FileStoreError {}
+
+#[cfg(feature = "file_store")]
+impl WalletPersister for bdk_file_store::Store<ChangeSet> {
+    type Error = FileStoreError;
 
-    fn load(
-        db: &mut bdk_file_store::Store<crate::ChangeSet>,
-        params: Self::LoadParams,
-    ) -> Result<Option<Self>, Self::LoadError> {
-        let changeset = db
+    fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error> {
+        persister
             .aggregate_changesets()
-            .map_err(LoadWithPersistError::Persist)?
-            .unwrap_or_default();
-        Self::load_with_params(changeset, params).map_err(LoadWithPersistError::InvalidChangeSet)
+            .map(Option::unwrap_or_default)
+            .map_err(FileStoreError::Load)
     }
 
-    fn persist(
-        db: &mut bdk_file_store::Store<crate::ChangeSet>,
-        changeset: &<Self as chain::Staged>::ChangeSet,
-    ) -> Result<(), Self::PersistError> {
-        db.append_changeset(changeset)
+    fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> {
+        persister.append_changeset(changeset).map_err(FileStoreError::Write)
     }
 }
 
@@ -154,6 +329,8 @@ impl<E: fmt::Debug + fmt::Display> std::error::Error for LoadWithPersistError<E>
 pub enum CreateWithPersistError<E> {
     /// Error from persistence.
     Persist(E),
+    /// Persister already has wallet data.
+    DataAlreadyExists(ChangeSet),
     /// Occurs when the loaded changeset cannot construct [`Wallet`].
     Descriptor(DescriptorError),
 }
@@ -162,6 +339,11 @@ impl<E: fmt::Display> fmt::Display for CreateWithPersistError<E> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
             Self::Persist(err) => fmt::Display::fmt(err, f),
+            Self::DataAlreadyExists(changeset) => write!(
+                f,
+                "Cannot create wallet in persister which already contains wallet data: {:?}",
+                changeset
+            ),
             Self::Descriptor(err) => fmt::Display::fmt(&err, f),
         }
     }
index d41544a1d7e00d80125c1eff4af99b810621a2c1..32b7a0f77d51e23b0b957e7abaa29375d87969d5 100644 (file)
@@ -5,15 +5,15 @@ use std::str::FromStr;
 
 use anyhow::Context;
 use assert_matches::assert_matches;
+use bdk_chain::COINBASE_MATURITY;
 use bdk_chain::{BlockId, ConfirmationTime};
-use bdk_chain::{PersistWith, COINBASE_MATURITY};
 use bdk_wallet::coin_selection::{self, LargestFirstCoinSelection};
 use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor};
 use bdk_wallet::error::CreateTxError;
 use bdk_wallet::psbt::PsbtUtils;
 use bdk_wallet::signer::{SignOptions, SignerError};
 use bdk_wallet::tx_builder::AddForeignUtxoError;
-use bdk_wallet::{AddressInfo, Balance, CreateParams, LoadParams, Wallet};
+use bdk_wallet::{AddressInfo, Balance, ChangeSet, Wallet, WalletPersister};
 use bdk_wallet::{KeychainKind, LoadError, LoadMismatch, LoadWithPersistError};
 use bitcoin::constants::ChainHash;
 use bitcoin::hashes::Hash;
@@ -111,10 +111,8 @@ fn wallet_is_persisted() -> anyhow::Result<()> {
     where
         CreateDb: Fn(&Path) -> anyhow::Result<Db>,
         OpenDb: Fn(&Path) -> anyhow::Result<Db>,
-        Wallet: PersistWith<Db, CreateParams = CreateParams, LoadParams = LoadParams>,
-        <Wallet as PersistWith<Db>>::CreateError: std::error::Error + Send + Sync + 'static,
-        <Wallet as PersistWith<Db>>::LoadError: std::error::Error + Send + Sync + 'static,
-        <Wallet as PersistWith<Db>>::PersistError: std::error::Error + Send + Sync + 'static,
+        Db: WalletPersister,
+        Db::Error: std::error::Error + Send + Sync + 'static,
     {
         let temp_dir = tempfile::tempdir().expect("must create tempdir");
         let file_path = temp_dir.path().join(filename);
@@ -188,7 +186,7 @@ fn wallet_is_persisted() -> anyhow::Result<()> {
 
 #[test]
 fn wallet_load_checks() -> anyhow::Result<()> {
-    fn run<Db, CreateDb, OpenDb, LoadDbError>(
+    fn run<Db, CreateDb, OpenDb>(
         filename: &str,
         create_db: CreateDb,
         open_db: OpenDb,
@@ -196,15 +194,8 @@ fn wallet_load_checks() -> anyhow::Result<()> {
     where
         CreateDb: Fn(&Path) -> anyhow::Result<Db>,
         OpenDb: Fn(&Path) -> anyhow::Result<Db>,
-        Wallet: PersistWith<
-            Db,
-            CreateParams = CreateParams,
-            LoadParams = LoadParams,
-            LoadError = LoadWithPersistError<LoadDbError>,
-        >,
-        <Wallet as PersistWith<Db>>::CreateError: std::error::Error + Send + Sync + 'static,
-        <Wallet as PersistWith<Db>>::LoadError: std::error::Error + Send + Sync + 'static,
-        <Wallet as PersistWith<Db>>::PersistError: std::error::Error + Send + Sync + 'static,
+        Db: WalletPersister,
+        Db::Error: std::error::Error + Send + Sync + 'static,
     {
         let temp_dir = tempfile::tempdir().expect("must create tempdir");
         let file_path = temp_dir.path().join(filename);
@@ -258,8 +249,8 @@ fn wallet_load_checks() -> anyhow::Result<()> {
 
     run(
         "store.db",
-        |path| Ok(bdk_file_store::Store::create_new(DB_MAGIC, path)?),
-        |path| Ok(bdk_file_store::Store::open(DB_MAGIC, path)?),
+        |path| Ok(bdk_file_store::Store::<ChangeSet>::create_new(DB_MAGIC, path)?),
+        |path| Ok(bdk_file_store::Store::<ChangeSet>::open(DB_MAGIC, path)?),
     )?;
     run(
         "store.sqlite",
@@ -280,7 +271,7 @@ fn single_descriptor_wallet_persist_and_recover() {
     let mut db = rusqlite::Connection::open(db_path).unwrap();
 
     let desc = get_test_tr_single_sig_xprv();
-    let mut wallet = CreateParams::new_single(desc)
+    let mut wallet = Wallet::create_single(desc)
         .network(Network::Testnet)
         .create_wallet(&mut db)
         .unwrap();
@@ -4174,7 +4165,7 @@ fn test_insert_tx_balance_and_utxos() {
 #[test]
 fn single_descriptor_wallet_can_create_tx_and_receive_change() {
     // create single descriptor wallet and fund it
-    let mut wallet = CreateParams::new_single(get_test_tr_single_sig_xprv())
+    let mut wallet = Wallet::create_single(get_test_tr_single_sig_xprv())
         .network(Network::Testnet)
         .create_wallet_no_persist()
         .unwrap();