]> Untitled Git - bdk/commitdiff
feat(wallet)!: add `NonEmptyDatabase` variant to `NewError`
author志宇 <hello@evanlinjin.me>
Tue, 2 Jan 2024 05:03:19 +0000 (13:03 +0800)
committer志宇 <hello@evanlinjin.me>
Sat, 6 Jan 2024 05:19:03 +0000 (13:19 +0800)
`NewError` is the error type when constructing a wallet with
`Wallet::new`. We want this to return an error when the database already
contains data (in which case, the caller should use `load` or
`new_or_load`).

crates/bdk/src/wallet/mod.rs
crates/bdk/tests/wallet.rs

index 873af72b249ac8a2961e6326bf771ee4be09eaa6..c554a63f12eed725910e0de542d2d4fa20ae3610 100644 (file)
@@ -237,6 +237,7 @@ impl Wallet {
         network: Network,
     ) -> Result<Self, DescriptorError> {
         Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e {
+            NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"),
             NewError::Descriptor(e) => e,
             NewError::Write(_) => unreachable!("mock-write must always succeed"),
         })
@@ -251,6 +252,7 @@ impl Wallet {
     ) -> Result<Self, crate::descriptor::DescriptorError> {
         Self::new_with_genesis_hash(descriptor, change_descriptor, (), network, genesis_hash)
             .map_err(|e| match e {
+                NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"),
                 NewError::Descriptor(e) => e,
                 NewError::Write(_) => unreachable!("mock-write must always succeed"),
             })
@@ -288,6 +290,8 @@ where
 /// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash
 #[derive(Debug)]
 pub enum NewError<W> {
+    /// Database already has data.
+    NonEmptyDatabase,
     /// There was problem with the passed-in descriptor(s).
     Descriptor(crate::descriptor::DescriptorError),
     /// We were unable to write the wallet's data to the persistence backend.
@@ -300,6 +304,10 @@ where
 {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
+            NewError::NonEmptyDatabase => write!(
+                f,
+                "database already has data - use `load` or `new_or_load` methods instead"
+            ),
             NewError::Descriptor(e) => e.fmt(f),
             NewError::Write(e) => e.fmt(f),
         }
@@ -446,13 +454,18 @@ impl<D> Wallet<D> {
     pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
         descriptor: E,
         change_descriptor: Option<E>,
-        db: D,
+        mut db: D,
         network: Network,
         genesis_hash: BlockHash,
     ) -> Result<Self, NewError<D::WriteError>>
     where
         D: PersistBackend<ChangeSet>,
     {
+        if let Ok(changeset) = db.load_from_persistence() {
+            if changeset.is_some() {
+                return Err(NewError::NonEmptyDatabase);
+            }
+        }
         let secp = Secp256k1::new();
         let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
         let mut index = KeychainTxOutIndex::<KeychainKind>::default();
@@ -615,6 +628,9 @@ impl<D> Wallet<D> {
                 genesis_hash,
             )
             .map_err(|e| match e {
+                NewError::NonEmptyDatabase => {
+                    unreachable!("database is already checked to have no data")
+                }
                 NewError::Descriptor(e) => NewOrLoadError::Descriptor(e),
                 NewError::Write(e) => NewOrLoadError::Write(e),
             }),
index 6efb0d7918cfa67f17e016e1d952033b41b4b6e9..b5aa642c80d6dcdc13c7913fe96c6b34176ae214 100644 (file)
@@ -7,8 +7,8 @@ use bdk::signer::{SignOptions, SignerError};
 use bdk::wallet::coin_selection::{self, LargestFirstCoinSelection};
 use bdk::wallet::error::CreateTxError;
 use bdk::wallet::tx_builder::AddForeignUtxoError;
-use bdk::wallet::AddressIndex::*;
 use bdk::wallet::{AddressIndex, AddressInfo, Balance, Wallet};
+use bdk::wallet::{AddressIndex::*, NewError};
 use bdk::{FeeRate, KeychainKind};
 use bdk_chain::COINBASE_MATURITY;
 use bdk_chain::{BlockId, ConfirmationTime};
@@ -92,6 +92,13 @@ fn load_recovers_wallet() {
             wallet_spk_index.last_revealed_indices()
         );
     }
+
+    // `new` can only be called on empty db
+    {
+        let db = bdk_file_store::Store::open(DB_MAGIC, &file_path).expect("must recover db");
+        let result = Wallet::new(get_test_tr_single_sig_xprv(), None, db, Network::Testnet);
+        assert!(matches!(result, Err(NewError::NonEmptyDatabase)));
+    }
 }
 
 #[test]