diff --git a/crates/bdk/examples/mnemonic_to_descriptors.rs b/crates/bdk/examples/mnemonic_to_descriptors.rs index 7d2dd6013f..4e1d5061d4 100644 --- a/crates/bdk/examples/mnemonic_to_descriptors.rs +++ b/crates/bdk/examples/mnemonic_to_descriptors.rs @@ -6,6 +6,7 @@ // You may not use this file except in accordance with one or both of these // licenses. +use anyhow::anyhow; use bdk::bitcoin::bip32::DerivationPath; use bdk::bitcoin::secp256k1::Secp256k1; use bdk::bitcoin::Network; @@ -14,13 +15,11 @@ use bdk::descriptor::IntoWalletDescriptor; use bdk::keys::bip39::{Language, Mnemonic, WordCount}; use bdk::keys::{GeneratableKey, GeneratedKey}; use bdk::miniscript::Tap; -use bdk::Error as BDK_Error; -use std::error::Error; use std::str::FromStr; /// This example demonstrates how to generate a mnemonic phrase /// using BDK and use that to generate a descriptor string. -fn main() -> Result<(), Box> { +fn main() -> Result<(), anyhow::Error> { let secp = Secp256k1::new(); // In this example we are generating a 12 words mnemonic phrase @@ -28,7 +27,7 @@ fn main() -> Result<(), Box> { // using their respective `WordCount` variant. let mnemonic: GeneratedKey<_, Tap> = Mnemonic::generate((WordCount::Words12, Language::English)) - .map_err(|_| BDK_Error::Generic("Mnemonic generation error".to_string()))?; + .map_err(|_| anyhow!("Mnemonic generation error"))?; println!("Mnemonic phrase: {}", *mnemonic); let mnemonic_with_passphrase = (mnemonic, None); diff --git a/crates/bdk/src/error.rs b/crates/bdk/src/error.rs index 07d30e736c..66229b5044 100644 --- a/crates/bdk/src/error.rs +++ b/crates/bdk/src/error.rs @@ -16,32 +16,9 @@ use crate::descriptor::DescriptorError; use crate::wallet::coin_selection; use crate::{descriptor, wallet, FeeRate, KeychainKind}; use alloc::string::String; -use bitcoin::{absolute, psbt, OutPoint, Sequence, Txid}; +use bitcoin::{absolute, psbt, OutPoint, ScriptBuf, Sequence, Txid}; use core::fmt; -/// Old catch-all errors enum that can be thrown by the [`Wallet`](crate::wallet::Wallet) -#[derive(Debug)] -pub enum Error { - /// Generic error - Generic(String), - /// Happens when trying to spend an UTXO that is not in the internal database - UnknownUtxo, - /// Node doesn't have data to estimate a fee rate - FeeRateUnavailable, - /// Error while working with [`keys`](crate::keys) - Key(crate::keys::KeyError), - /// Descriptor checksum mismatch - ChecksumMismatch, - /// Requested outpoint doesn't exist in the tx (vout greater than available outputs) - InvalidOutpoint(OutPoint), - /// Error related to the parsing and usage of descriptors - Descriptor(crate::descriptor::error::Error), - /// Miniscript error - Miniscript(miniscript::Error), - /// BIP32 error - Bip32(bitcoin::bip32::Error), -} - /// Errors returned by miniscript when updating inconsistent PSBTs #[derive(Debug, Clone)] pub enum MiniscriptPsbtError { @@ -66,30 +43,6 @@ impl fmt::Display for MiniscriptPsbtError { #[cfg(feature = "std")] impl std::error::Error for MiniscriptPsbtError {} -#[cfg(feature = "std")] -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Generic(err) => write!(f, "Generic error: {}", err), - Self::UnknownUtxo => write!(f, "UTXO not found in the internal database"), - Self::FeeRateUnavailable => write!(f, "Fee rate unavailable"), - Self::Key(err) => write!(f, "Key error: {}", err), - Self::ChecksumMismatch => write!(f, "Descriptor checksum mismatch"), - Self::InvalidOutpoint(outpoint) => write!( - f, - "Requested outpoint doesn't exist in the tx: {}", - outpoint - ), - Self::Descriptor(err) => write!(f, "Descriptor error: {}", err), - Self::Miniscript(err) => write!(f, "Miniscript error: {}", err), - Self::Bip32(err) => write!(f, "BIP32 error: {}", err), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error {} - macro_rules! impl_error { ( $from:ty, $to:ident ) => { impl_error!($from, $to, Error); @@ -103,22 +56,6 @@ macro_rules! impl_error { }; } -impl_error!(descriptor::error::Error, Descriptor); - -impl From for Error { - fn from(key_error: crate::keys::KeyError) -> Error { - match key_error { - crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner), - crate::keys::KeyError::Bip32(inner) => Error::Bip32(inner), - crate::keys::KeyError::InvalidChecksum => Error::ChecksumMismatch, - e => Error::Key(e), - } - } -} - -impl_error!(miniscript::Error, Miniscript); -impl_error!(bitcoin::bip32::Error, Bip32); - #[derive(Debug)] /// Error returned from [`TxBuilder::finish`] /// @@ -330,25 +267,13 @@ impl std::error::Error for CreateTxErr /// [`Wallet::build_fee_bump`]: wallet::Wallet::build_fee_bump pub enum BuildFeeBumpError { /// Happens when trying to spend an UTXO that is not in the internal database - UnknownUtxo { - /// The outpoint of the missing UTXO - outpoint: OutPoint, - }, + UnknownUtxo(OutPoint), /// Thrown when a tx is not found in the internal database - TransactionNotFound { - /// The txid of the missing transaction - txid: Txid, - }, + TransactionNotFound(Txid), /// Happens when trying to bump a transaction that is already confirmed - TransactionConfirmed { - /// The txid of the already confirmed transaction - txid: Txid, - }, + TransactionConfirmed(Txid), /// Trying to replace a tx that has a sequence >= `0xFFFFFFFE` - IrreplaceableTransaction { - /// The txid of the irreplaceable transaction - txid: Txid, - }, + IrreplaceableTransaction(Txid), /// Node doesn't have data to estimate a fee rate FeeRateUnavailable, } @@ -357,22 +282,22 @@ pub enum BuildFeeBumpError { impl fmt::Display for BuildFeeBumpError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::UnknownUtxo { outpoint } => write!( + Self::UnknownUtxo(outpoint) => write!( f, "UTXO not found in the internal database with txid: {}, vout: {}", outpoint.txid, outpoint.vout ), - Self::TransactionNotFound { txid } => { + Self::TransactionNotFound(txid) => { write!( f, "Transaction not found in the internal database with txid: {}", txid ) } - Self::TransactionConfirmed { txid } => { + Self::TransactionConfirmed(txid) => { write!(f, "Transaction already confirmed with txid: {}", txid) } - Self::IrreplaceableTransaction { txid } => { + Self::IrreplaceableTransaction(txid) => { write!(f, "Transaction can't be replaced with txid: {}", txid) } Self::FeeRateUnavailable => write!(f, "Fee rate unavailable"), @@ -409,3 +334,98 @@ impl fmt::Display for SignError { #[cfg(feature = "std")] impl std::error::Error for SignError {} + +#[derive(Debug)] +/// Error returned from [`TxBuilder::add_utxo`] and [`TxBuilder::add_utxos`] +/// +/// [`TxBuilder::add_utxo`]: wallet::tx_builder::TxBuilder::add_utxo +/// [`TxBuilder::add_utxos`]: wallet::tx_builder::TxBuilder::add_utxos +pub enum AddUtxoError { + /// Happens when trying to spend an UTXO that is not in the internal database + UnknownUtxo(OutPoint), +} + +#[cfg(feature = "std")] +impl fmt::Display for AddUtxoError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnknownUtxo(outpoint) => write!( + f, + "UTXO not found in the internal database for txid: {} with vout: {}", + outpoint.txid, outpoint.vout + ), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for AddUtxoError {} + +#[derive(Debug)] +/// Error returned from [`TxBuilder::add_utxo`] and [`TxBuilder::add_utxos`] +/// +/// [`TxBuilder::add_utxo`]: wallet::tx_builder::TxBuilder::add_utxo +/// [`TxBuilder::add_utxos`]: wallet::tx_builder::TxBuilder::add_utxos +pub enum AddForeignUtxoError { + /// Foreign utxo outpoint txid does not match PSBT input txid + InvalidTxid { + /// PSBT input txid + input_txid: Txid, + /// Foreign UTXO outpoint + foreign_utxo: OutPoint, + }, + /// Requested outpoint doesn't exist in the tx (vout greater than available outputs) + InvalidOutpoint(OutPoint), + /// Foreign utxo missing witness_utxo or non_witness_utxo + MissingUtxo, +} + +#[cfg(feature = "std")] +impl fmt::Display for AddForeignUtxoError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidTxid { + input_txid, + foreign_utxo, + } => write!( + f, + "Foreign UTXO outpoint txid: {} does not match PSBT input txid: {}", + foreign_utxo.txid, input_txid, + ), + Self::InvalidOutpoint(outpoint) => write!( + f, + "Requested outpoint doesn't exist for txid: {} with vout: {}", + outpoint.txid, outpoint.vout, + ), + Self::MissingUtxo => write!(f, "Foreign utxo missing witness_utxo or non_witness_utxo"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for AddForeignUtxoError {} + +#[derive(Debug)] +/// Error returned from [`TxBuilder::allow_shrinking`] +/// +/// [`TxBuilder::allow_shrinking`]: wallet::tx_builder::TxBuilder::allow_shrinking +pub enum AllowShrinkingError { + /// Script/PubKey was not in the original transaction + MissingScriptPubKey(ScriptBuf), +} + +#[cfg(feature = "std")] +impl fmt::Display for AllowShrinkingError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::MissingScriptPubKey(script_buf) => write!( + f, + "Script/PubKey was not in the original transaction: {}", + script_buf, + ), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for AllowShrinkingError {} diff --git a/crates/bdk/src/lib.rs b/crates/bdk/src/lib.rs index 3b68395976..e5f1146a2b 100644 --- a/crates/bdk/src/lib.rs +++ b/crates/bdk/src/lib.rs @@ -37,7 +37,6 @@ pub mod wallet; pub use descriptor::template; pub use descriptor::HdKeyPaths; -pub use error::Error; pub use types::*; pub use wallet::signer; pub use wallet::signer::SignOptions; diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 3c12152342..9d006b440d 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -38,6 +38,7 @@ use bitcoin::{ }; use core::fmt; use core::ops::Deref; +use descriptor::error::Error as DescriptorError; use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier}; use bdk_chain::tx_graph::CalculateFeeError; @@ -56,6 +57,7 @@ pub mod hardwaresigner; pub use utils::IsDust; +use crate::descriptor; #[allow(deprecated)] use coin_selection::DefaultCoinSelectionAlgorithm; use signer::{SignOptions, SignerOrdering, SignersContainer, TransactionSigner}; @@ -67,7 +69,7 @@ use crate::descriptor::{ calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils, }; -use crate::error::{BuildFeeBumpError, CreateTxError, Error, MiniscriptPsbtError, SignError}; +use crate::error::{BuildFeeBumpError, CreateTxError, MiniscriptPsbtError, SignError}; use crate::psbt::PsbtUtils; use crate::signer::SignerError; use crate::types::*; @@ -1291,14 +1293,14 @@ impl Wallet { let mut tx = graph .get_tx(txid) - .ok_or(BuildFeeBumpError::TransactionNotFound { txid })? + .ok_or(BuildFeeBumpError::TransactionNotFound(txid))? .clone(); let pos = graph .get_chain_position(&self.chain, chain_tip, txid) - .ok_or(BuildFeeBumpError::TransactionNotFound { txid })?; + .ok_or(BuildFeeBumpError::TransactionNotFound(txid))?; if let ChainPosition::Confirmed(_) = pos { - return Err(BuildFeeBumpError::TransactionConfirmed { txid }); + return Err(BuildFeeBumpError::TransactionConfirmed(txid)); } if !tx @@ -1306,7 +1308,7 @@ impl Wallet { .iter() .any(|txin| txin.sequence.to_consensus_u32() <= 0xFFFFFFFD) { - return Err(BuildFeeBumpError::IrreplaceableTransaction { txid: tx.txid() }); + return Err(BuildFeeBumpError::IrreplaceableTransaction(tx.txid())); } let fee = self @@ -1321,18 +1323,14 @@ impl Wallet { let original_utxos = original_txin .iter() .map(|txin| -> Result<_, BuildFeeBumpError> { - let prev_tx = graph.get_tx(txin.previous_output.txid).ok_or( - BuildFeeBumpError::UnknownUtxo { - outpoint: txin.previous_output, - }, - )?; + let prev_tx = graph + .get_tx(txin.previous_output.txid) + .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output))?; let txout = &prev_tx.output[txin.previous_output.vout as usize]; let confirmation_time: ConfirmationTime = graph .get_chain_position(&self.chain, chain_tip, txin.previous_output.txid) - .ok_or(BuildFeeBumpError::UnknownUtxo { - outpoint: txin.previous_output, - })? + .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output))? .cloned() .into(); @@ -1499,7 +1497,7 @@ impl Wallet { } /// Return the spending policies for the wallet's descriptor - pub fn policies(&self, keychain: KeychainKind) -> Result, Error> { + pub fn policies(&self, keychain: KeychainKind) -> Result, DescriptorError> { let signers = match keychain { KeychainKind::External => &self.signers, KeychainKind::Internal => &self.change_signers, @@ -2054,7 +2052,7 @@ pub fn wallet_name_from_descriptor( change_descriptor: Option, network: Network, secp: &SecpCtx, -) -> Result +) -> Result where T: IntoWalletDescriptor, { diff --git a/crates/bdk/src/wallet/tx_builder.rs b/crates/bdk/src/wallet/tx_builder.rs index 2b6a911707..533cac2bca 100644 --- a/crates/bdk/src/wallet/tx_builder.rs +++ b/crates/bdk/src/wallet/tx_builder.rs @@ -51,9 +51,11 @@ use bitcoin::{absolute, script::PushBytes, OutPoint, ScriptBuf, Sequence, Transa use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; use super::ChangeSet; +use crate::error::{AddForeignUtxoError, AddUtxoError, AllowShrinkingError}; use crate::types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo}; use crate::wallet::CreateTxError; -use crate::{Error, Utxo, Wallet}; +use crate::{Utxo, Wallet}; + /// Context in which the [`TxBuilder`] is valid pub trait TxBuilderContext: core::fmt::Debug + Default + Clone {} @@ -292,12 +294,16 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D, /// /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in /// the "utxos" and the "unspendable" list, it will be spent. - pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, Error> { + pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, AddUtxoError> { { let wallet = self.wallet.borrow(); let utxos = outpoints .iter() - .map(|outpoint| wallet.get_utxo(*outpoint).ok_or(Error::UnknownUtxo)) + .map(|outpoint| { + wallet + .get_utxo(*outpoint) + .ok_or(AddUtxoError::UnknownUtxo(*outpoint)) + }) .collect::, _>>()?; for utxo in utxos { @@ -318,7 +324,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D, /// /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in /// the "utxos" and the "unspendable" list, it will be spent. - pub fn add_utxo(&mut self, outpoint: OutPoint) -> Result<&mut Self, Error> { + pub fn add_utxo(&mut self, outpoint: OutPoint) -> Result<&mut Self, AddUtxoError> { self.add_utxos(&[outpoint]) } @@ -373,23 +379,22 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D, outpoint: OutPoint, psbt_input: psbt::Input, satisfaction_weight: usize, - ) -> Result<&mut Self, Error> { + ) -> Result<&mut Self, AddForeignUtxoError> { if psbt_input.witness_utxo.is_none() { match psbt_input.non_witness_utxo.as_ref() { Some(tx) => { if tx.txid() != outpoint.txid { - return Err(Error::Generic( - "Foreign utxo outpoint does not match PSBT input".into(), - )); + return Err(AddForeignUtxoError::InvalidTxid { + input_txid: tx.txid(), + foreign_utxo: outpoint, + }); } if tx.output.len() <= outpoint.vout as usize { - return Err(Error::InvalidOutpoint(outpoint)); + return Err(AddForeignUtxoError::InvalidOutpoint(outpoint)); } } None => { - return Err(Error::Generic( - "Foreign utxo missing witness_utxo or non_witness_utxo".into(), - )) + return Err(AddForeignUtxoError::MissingUtxo); } } } @@ -690,7 +695,10 @@ impl<'a, D> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> { /// /// Returns an `Err` if `script_pubkey` can't be found among the recipients of the /// transaction we are bumping. - pub fn allow_shrinking(&mut self, script_pubkey: ScriptBuf) -> Result<&mut Self, Error> { + pub fn allow_shrinking( + &mut self, + script_pubkey: ScriptBuf, + ) -> Result<&mut Self, AllowShrinkingError> { match self .params .recipients @@ -702,10 +710,7 @@ impl<'a, D> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> { self.params.drain_to = Some(script_pubkey); Ok(self) } - None => Err(Error::Generic(format!( - "{} was not in the original transaction", - script_pubkey - ))), + None => Err(AllowShrinkingError::MissingScriptPubKey(script_pubkey)), } } } diff --git a/crates/bdk/tests/wallet.rs b/crates/bdk/tests/wallet.rs index 7e56c1f21a..3e57b8534a 100644 --- a/crates/bdk/tests/wallet.rs +++ b/crates/bdk/tests/wallet.rs @@ -1,6 +1,6 @@ use assert_matches::assert_matches; use bdk::descriptor::calc_checksum; -use bdk::error::{CreateTxError, SignError}; +use bdk::error::{AddForeignUtxoError, CreateTxError, SignError}; use bdk::psbt::PsbtUtils; use bdk::signer::{SignOptions, SignerError}; use bdk::wallet::coin_selection::{self, LargestFirstCoinSelection}; @@ -1137,7 +1137,6 @@ fn test_calculate_fee_with_missing_foreign_utxo() { } #[test] -#[should_panic(expected = "Generic(\"Foreign utxo missing witness_utxo or non_witness_utxo\")")] fn test_add_foreign_utxo_invalid_psbt_input() { let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let outpoint = wallet.list_unspent().next().expect("must exist").outpoint; @@ -1148,9 +1147,9 @@ fn test_add_foreign_utxo_invalid_psbt_input() { .unwrap(); let mut builder = wallet.build_tx(); - builder - .add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction) - .unwrap(); + let result = + builder.add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction); + assert!(matches!(result, Err(AddForeignUtxoError::MissingUtxo))); } #[test]