From c57e0a74870b1ffcb708c117053f242262d7d622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 2 Jan 2024 13:03:19 +0800 Subject: [PATCH] feat(wallet)!: add `NonEmptyDatabase` variant to `NewError` `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 | 18 +++++++++++++++++- crates/bdk/tests/wallet.rs | 9 ++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 873af72b24..c554a63f12 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -237,6 +237,7 @@ impl Wallet { network: Network, ) -> Result { 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::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 { + /// 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 Wallet { pub fn new_with_genesis_hash( descriptor: E, change_descriptor: Option, - db: D, + mut db: D, network: Network, genesis_hash: BlockHash, ) -> Result> where D: PersistBackend, { + 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::::default(); @@ -615,6 +628,9 @@ impl Wallet { 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), }), diff --git a/crates/bdk/tests/wallet.rs b/crates/bdk/tests/wallet.rs index 6efb0d7918..b5aa642c80 100644 --- a/crates/bdk/tests/wallet.rs +++ b/crates/bdk/tests/wallet.rs @@ -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]