diff --git a/crates/chain/src/persist.rs b/crates/chain/src/persist.rs index cdaf6d5e6..5f7b37e4c 100644 --- a/crates/chain/src/persist.rs +++ b/crates/chain/src/persist.rs @@ -57,6 +57,7 @@ pub trait PersistAsyncWith: Sized { } /// Represents a persisted `T`. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Persisted { inner: T, } diff --git a/crates/wallet/src/descriptor/error.rs b/crates/wallet/src/descriptor/error.rs index f83fb24d2..e018b5352 100644 --- a/crates/wallet/src/descriptor/error.rs +++ b/crates/wallet/src/descriptor/error.rs @@ -13,7 +13,7 @@ use core::fmt; /// Errors related to the parsing and usage of descriptors -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Error { /// Invalid HD Key path, such as having a wildcard but a length != 1 InvalidHdKeyPath, diff --git a/crates/wallet/src/keys/mod.rs b/crates/wallet/src/keys/mod.rs index 907deb7ba..d9c241ff0 100644 --- a/crates/wallet/src/keys/mod.rs +++ b/crates/wallet/src/keys/mod.rs @@ -935,7 +935,7 @@ impl IntoDescriptorKey for PrivateKey { } /// Errors thrown while working with [`keys`](crate::keys) -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum KeyError { /// The key cannot exist in the given script context InvalidScriptContext, diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 71468e4e5..d2d7c0ad0 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -184,7 +184,7 @@ impl fmt::Display for AddressInfo { } /// The error type when loading a [`Wallet`] from a [`ChangeSet`]. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum LoadError { /// There was a problem with the passed-in descriptor(s). Descriptor(crate::descriptor::DescriptorError), @@ -216,7 +216,7 @@ impl fmt::Display for LoadError { impl std::error::Error for LoadError {} /// Represents a mismatch with what is loaded and what is expected from [`LoadParams`]. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum LoadMismatch { /// Network does not match. Network { @@ -243,6 +243,18 @@ pub enum LoadMismatch { }, } +impl From for LoadError { + fn from(mismatch: LoadMismatch) -> Self { + Self::Mismatch(mismatch) + } +} + +impl From for LoadWithPersistError { + fn from(mismatch: LoadMismatch) -> Self { + Self::InvalidChangeSet(LoadError::Mismatch(mismatch)) + } +} + /// An error that may occur when applying a block to [`Wallet`]. #[derive(Debug)] pub enum ApplyBlockError { diff --git a/crates/wallet/src/wallet/persisted.rs b/crates/wallet/src/wallet/persisted.rs index 1a0450e26..70d9f27cc 100644 --- a/crates/wallet/src/wallet/persisted.rs +++ b/crates/wallet/src/wallet/persisted.rs @@ -138,7 +138,7 @@ impl chain::PersistWith> for Wallet { } /// Error type for [`PersistedWallet::load`]. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum LoadWithPersistError { /// Error from persistence. Persist(E), diff --git a/crates/wallet/tests/wallet.rs b/crates/wallet/tests/wallet.rs index 94d4b07e1..5a709f1c6 100644 --- a/crates/wallet/tests/wallet.rs +++ b/crates/wallet/tests/wallet.rs @@ -13,8 +13,9 @@ 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::KeychainKind; use bdk_wallet::{AddressInfo, Balance, CreateParams, LoadParams, Wallet}; +use bdk_wallet::{KeychainKind, LoadError, LoadMismatch, LoadWithPersistError}; +use bitcoin::constants::ChainHash; use bitcoin::hashes::Hash; use bitcoin::key::Secp256k1; use bitcoin::psbt; @@ -177,6 +178,93 @@ fn wallet_is_persisted() -> anyhow::Result<()> { Ok(()) } +#[test] +fn wallet_load_checks() -> anyhow::Result<()> { + fn run( + filename: &str, + create_db: CreateDb, + open_db: OpenDb, + ) -> anyhow::Result<()> + where + CreateDb: Fn(&Path) -> anyhow::Result, + OpenDb: Fn(&Path) -> anyhow::Result, + Wallet: PersistWith< + Db, + CreateParams = CreateParams, + LoadParams = LoadParams, + LoadError = LoadWithPersistError, + >, + >::CreateError: std::error::Error + Send + Sync + 'static, + >::LoadError: std::error::Error + Send + Sync + 'static, + >::PersistError: std::error::Error + Send + Sync + 'static, + { + let temp_dir = tempfile::tempdir().expect("must create tempdir"); + let file_path = temp_dir.path().join(filename); + let network = Network::Testnet; + let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_with_change_desc(); + + // create new wallet + let _ = Wallet::create(external_desc, internal_desc) + .network(network) + .create_wallet(&mut create_db(&file_path)?)?; + + assert_matches!( + Wallet::load() + .network(Network::Regtest) + .load_wallet(&mut open_db(&file_path)?), + Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch( + LoadMismatch::Network { + loaded: Network::Testnet, + expected: Network::Regtest, + } + ))), + "unexpected network check result: Regtest (check) is not Testnet (loaded)", + ); + assert_matches!( + Wallet::load() + .network(Network::Bitcoin) + .load_wallet(&mut open_db(&file_path)?), + Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch( + LoadMismatch::Network { + loaded: Network::Testnet, + expected: Network::Bitcoin, + } + ))), + "unexpected network check result: Bitcoin (check) is not Testnet (loaded)", + ); + let mainnet_hash = BlockHash::from_byte_array(ChainHash::BITCOIN.to_bytes()); + assert_matches!( + Wallet::load().genesis_hash(mainnet_hash).load_wallet(&mut open_db(&file_path)?), + Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(LoadMismatch::Genesis { .. }))), + "unexpected genesis hash check result: mainnet hash (check) is not testnet hash (loaded)", + ); + assert_matches!( + Wallet::load() + .descriptors(internal_desc, external_desc) + .load_wallet(&mut open_db(&file_path)?), + Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch( + LoadMismatch::Descriptor { .. } + ))), + "unexpected descriptors check result", + ); + + Ok(()) + } + + run( + "store.db", + |path| Ok(bdk_file_store::Store::create_new(DB_MAGIC, path)?), + |path| Ok(bdk_file_store::Store::open(DB_MAGIC, path)?), + )?; + run( + "store.sqlite", + |path| Ok(bdk_chain::sqlite::Connection::open(path)?), + |path| Ok(bdk_chain::sqlite::Connection::open(path)?), + )?; + + Ok(()) +} + #[test] fn test_error_external_and_internal_are_the_same() { // identical descriptors should fail to create wallet