diff --git a/crates/bdk/src/error.rs b/crates/bdk/src/error.rs index fcb5a6f7b1..105e642a04 100644 --- a/crates/bdk/src/error.rs +++ b/crates/bdk/src/error.rs @@ -20,25 +20,6 @@ use core::fmt; pub enum Error { /// Generic error Generic(String), - /// Cannot build a tx without recipients - NoRecipients, - /// `manually_selected_only` option is selected but no utxo has been passed - NoUtxosSelected, - /// Output created is under the dust limit, 546 satoshis - OutputBelowDustLimit(usize), - /// Wallet's UTXO set is not enough to cover recipient's requested plus fee - InsufficientFunds { - /// Sats needed for some transaction - needed: u64, - /// Sats available for spending - available: u64, - }, - /// Branch and bound coin selection possible attempts with sufficiently big UTXO set could grow - /// exponentially, thus a limit is set, and when hit, this error is thrown - BnBTotalTriesExceeded, - /// Branch and bound coin selection tries to avoid needing a change by finding the right inputs for - /// the desired outputs plus fee, if there is not such combination this error is thrown - BnBNoExactMatch, /// Happens when trying to spend an UTXO that is not in the internal database UnknownUtxo, /// Thrown when a tx is not found in the internal database @@ -47,32 +28,12 @@ pub enum Error { TransactionConfirmed, /// Trying to replace a tx that has a sequence >= `0xFFFFFFFE` IrreplaceableTransaction, - /// When bumping a tx the fee rate requested is lower than required - FeeRateTooLow { - /// Required fee rate (satoshi/vbyte) - required: crate::types::FeeRate, - }, - /// When bumping a tx the absolute fee requested is lower than replaced tx absolute fee - FeeTooLow { - /// Required fee absolute value (satoshi) - required: u64, - }, /// Node doesn't have data to estimate a fee rate FeeRateUnavailable, - /// In order to use the [`TxBuilder::add_global_xpubs`] option every extended - /// key in the descriptor must either be a master key itself (having depth = 0) or have an - /// explicit origin provided - /// - /// [`TxBuilder::add_global_xpubs`]: crate::wallet::tx_builder::TxBuilder::add_global_xpubs - MissingKeyOrigin(String), /// Error while working with [`keys`](crate::keys) Key(crate::keys::KeyError), /// Descriptor checksum mismatch ChecksumMismatch, - /// Spending policy is not compatible with this [`KeychainKind`](crate::types::KeychainKind) - SpendingPolicyRequired(crate::types::KeychainKind), - /// Error while extracting and manipulating policies - InvalidPolicyPathError(crate::descriptor::policy::PolicyError), /// Signing error Signer(crate::wallet::signer::SignerError), /// Requested outpoint doesn't exist in the tx (vout greater than available outputs) @@ -115,40 +76,15 @@ 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::NoRecipients => write!(f, "Cannot build tx without recipients"), - Self::NoUtxosSelected => write!(f, "No UTXO selected"), - Self::OutputBelowDustLimit(limit) => { - write!(f, "Output below the dust limit: {}", limit) - } - Self::InsufficientFunds { needed, available } => write!( - f, - "Insufficient funds: {} sat available of {} sat needed", - available, needed - ), - Self::BnBTotalTriesExceeded => { - write!(f, "Branch and bound coin selection: total tries exceeded") - } - Self::BnBNoExactMatch => write!(f, "Branch and bound coin selection: not exact match"), Self::UnknownUtxo => write!(f, "UTXO not found in the internal database"), Self::TransactionNotFound => { write!(f, "Transaction not found in the internal database") } Self::TransactionConfirmed => write!(f, "Transaction already confirmed"), Self::IrreplaceableTransaction => write!(f, "Transaction can't be replaced"), - Self::FeeRateTooLow { required } => write!( - f, - "Fee rate too low: required {} sat/vbyte", - required.as_sat_per_vb() - ), - Self::FeeTooLow { required } => write!(f, "Fee to low: required {} sat", required), Self::FeeRateUnavailable => write!(f, "Fee rate unavailable"), - Self::MissingKeyOrigin(err) => write!(f, "Missing key origin: {}", err), Self::Key(err) => write!(f, "Key error: {}", err), Self::ChecksumMismatch => write!(f, "Descriptor checksum mismatch"), - Self::SpendingPolicyRequired(keychain_kind) => { - write!(f, "Spending policy required: {:?}", keychain_kind) - } - Self::InvalidPolicyPathError(err) => write!(f, "Invalid policy path: {}", err), Self::Signer(err) => write!(f, "Signer error: {}", err), Self::InvalidOutpoint(outpoint) => write!( f, @@ -181,7 +117,6 @@ macro_rules! impl_error { } impl_error!(descriptor::error::Error, Descriptor); -impl_error!(descriptor::policy::PolicyError, InvalidPolicyPathError); impl_error!(wallet::signer::SignerError, Signer); impl From for Error { diff --git a/crates/bdk/src/wallet/coin_selection.rs b/crates/bdk/src/wallet/coin_selection.rs index 10309a6602..0ce38242fd 100644 --- a/crates/bdk/src/wallet/coin_selection.rs +++ b/crates/bdk/src/wallet/coin_selection.rs @@ -26,7 +26,7 @@ //! ``` //! # use std::str::FromStr; //! # use bitcoin::*; -//! # use bdk::wallet::{self, ChangeSet, coin_selection::*, CreateTxError}; +//! # use bdk::wallet::{self, ChangeSet, coin_selection::*, coin_selection, CreateTxError}; //! # use bdk_chain::PersistBackend; //! # use bdk::*; //! # use bdk::wallet::coin_selection::decide_change; @@ -42,7 +42,7 @@ //! fee_rate: bdk::FeeRate, //! target_amount: u64, //! drain_script: &Script, -//! ) -> Result { +//! ) -> Result { //! let mut selected_amount = 0; //! let mut additional_weight = Weight::ZERO; //! let all_utxos_selected = required_utxos @@ -62,7 +62,7 @@ //! let additional_fees = fee_rate.fee_wu(additional_weight); //! let amount_needed_with_fees = additional_fees + target_amount; //! if selected_amount < amount_needed_with_fees { -//! return Err(bdk::Error::InsufficientFunds { +//! return Err(coin_selection::Error::InsufficientFunds { //! needed: amount_needed_with_fees, //! available: selected_amount, //! }); @@ -100,14 +100,16 @@ use crate::types::FeeRate; use crate::wallet::utils::IsDust; +use crate::Utxo; use crate::WeightedUtxo; -use crate::{error::Error, Utxo}; use alloc::vec::Vec; use bitcoin::consensus::encode::serialize; use bitcoin::{Script, Weight}; use core::convert::TryInto; +use core::fmt; +use core::fmt::Formatter; use rand::seq::SliceRandom; /// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not @@ -118,6 +120,43 @@ pub type DefaultCoinSelectionAlgorithm = BranchAndBoundCoinSelection; // prev_txid (32 bytes) + prev_vout (4 bytes) + sequence (4 bytes) pub(crate) const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4) * 4; +/// Errors that can be thrown by the [`coin_selection`](crate::wallet::coin_selection) module +#[derive(Debug)] +pub enum Error { + /// Wallet's UTXO set is not enough to cover recipient's requested plus fee + InsufficientFunds { + /// Sats needed for some transaction + needed: u64, + /// Sats available for spending + available: u64, + }, + /// Branch and bound coin selection tries to avoid needing a change by finding the right inputs for + /// the desired outputs plus fee, if there is not such combination this error is thrown + BnBNoExactMatch, + /// Branch and bound coin selection possible attempts with sufficiently big UTXO set could grow + /// exponentially, thus a limit is set, and when hit, this error is thrown + BnBTotalTriesExceeded, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::InsufficientFunds { needed, available } => write!( + f, + "Insufficient funds: {} sat available of {} sat needed", + available, needed + ), + Self::BnBTotalTriesExceeded => { + write!(f, "Branch and bound coin selection: total tries exceeded") + } + Self::BnBNoExactMatch => write!(f, "Branch and bound coin selection: not exact match"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + #[derive(Debug)] /// Remaining amount after performing coin selection pub enum Excess { diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 5c8308b24d..6ce7301482 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -293,8 +293,69 @@ pub enum CreateTxError

{ Persist(P), /// There was a problem while extracting and manipulating policies Policy(PolicyError), - /// TODO: replace this with specific error types - Bdk(Error), + /// Spending policy is not compatible with this [`KeychainKind`](crate::types::KeychainKind) + SpendingPolicyRequired(KeychainKind), + /// Requested invalid transaction version '0' + Version0, + /// Requested transaction version `1`, but at least `2` is needed to use OP_CSV + Version1Csv, + /// Requested `LockTime` is less than is required to spend from this script + LockTime { + /// Requested `LockTime` + requested: LockTime, + /// Required `LockTime` + required: LockTime, + }, + /// Cannot enable RBF with a `Sequence` >= 0xFFFFFFFE + RbfSequence, + /// Cannot enable RBF with `Sequence` given a required OP_CSV + RbfSequenceCsv { + /// Given RBF `Sequence` + rbf: Sequence, + /// Required OP_CSV `Sequence` + csv: Sequence, + }, + /// When bumping a tx the absolute fee requested is lower than replaced tx absolute fee + FeeTooLow { + /// Required fee absolute value (satoshi) + required: u64, + }, + /// When bumping a tx the fee rate requested is lower than required + FeeRateTooLow { + /// Required fee rate (satoshi/vbyte) + required: FeeRate, + }, + /// `manually_selected_only` option is selected but no utxo has been passed + NoUtxosSelected, + /// Output created is under the dust limit, 546 satoshis + OutputBelowDustLimit(usize), + /// The `change_policy` was set but the wallet does not have a change_descriptor + ChangePolicyDescriptor, + /// There was an error with coin selection + CoinSelection(coin_selection::Error), + /// Wallet's UTXO set is not enough to cover recipient's requested plus fee + InsufficientFunds { + /// Sats needed for some transaction + needed: u64, + /// Sats available for spending + available: u64, + }, + /// Cannot build a tx without recipients + NoRecipients, + /// Partially signed bitcoin transaction error + Psbt(psbt::Error), + /// In order to use the [`TxBuilder::add_global_xpubs`] option every extended + /// key in the descriptor must either be a master key itself (having depth = 0) or have an + /// explicit origin provided + /// + /// [`TxBuilder::add_global_xpubs`]: crate::wallet::tx_builder::TxBuilder::add_global_xpubs + MissingKeyOrigin(String), + /// Happens when trying to spend an UTXO that is not in the internal database + UnknownUtxo, + /// Missing non_witness_utxo on foreign utxo for given `OutPoint` + MissingNonWitnessUtxo(OutPoint), + /// Miniscript PSBT error + MiniscriptPsbt(MiniscriptPsbtError), } #[cfg(feature = "std")] @@ -312,8 +373,81 @@ where e ) } - Self::Bdk(e) => e.fmt(f), Self::Policy(e) => e.fmt(f), + CreateTxError::SpendingPolicyRequired(keychain_kind) => { + write!(f, "Spending policy required: {:?}", keychain_kind) + } + CreateTxError::Version0 => { + write!(f, "Invalid version `0`") + } + CreateTxError::Version1Csv => { + write!( + f, + "TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV" + ) + } + CreateTxError::LockTime { + requested, + required, + } => { + write!(f, "TxBuilder requested timelock of `{:?}`, but at least `{:?}` is required to spend from this script", required, requested) + } + CreateTxError::RbfSequence => { + write!(f, "Cannot enable RBF with a nSequence >= 0xFFFFFFFE") + } + CreateTxError::RbfSequenceCsv { rbf, csv } => { + write!( + f, + "Cannot enable RBF with nSequence `{:?}` given a required OP_CSV of `{:?}`", + rbf, csv + ) + } + CreateTxError::FeeTooLow { required } => { + write!(f, "Fee to low: required {} sat", required) + } + CreateTxError::FeeRateTooLow { required } => { + write!( + f, + "Fee rate too low: required {} sat/vbyte", + required.as_sat_per_vb() + ) + } + CreateTxError::NoUtxosSelected => { + write!(f, "No UTXO selected") + } + CreateTxError::OutputBelowDustLimit(limit) => { + write!(f, "Output below the dust limit: {}", limit) + } + CreateTxError::ChangePolicyDescriptor => { + write!( + f, + "The `change_policy` can be set only if the wallet has a change_descriptor" + ) + } + CreateTxError::CoinSelection(e) => e.fmt(f), + CreateTxError::InsufficientFunds { needed, available } => { + write!( + f, + "Insufficient funds: {} sat available of {} sat needed", + available, needed + ) + } + CreateTxError::NoRecipients => { + write!(f, "Cannot build tx without recipients") + } + CreateTxError::Psbt(e) => e.fmt(f), + CreateTxError::MissingKeyOrigin(err) => { + write!(f, "Missing key origin: {}", err) + } + CreateTxError::UnknownUtxo => { + write!(f, "UTXO not found in the internal database") + } + CreateTxError::MissingNonWitnessUtxo(outpoint) => { + write!(f, "Missing non_witness_utxo on foreign utxo {}", outpoint) + } + CreateTxError::MiniscriptPsbt(err) => { + write!(f, "Miniscript PSBT error: {}", err) + } } } } @@ -330,9 +464,21 @@ impl

From for CreateTxError

{ } } -impl

From for CreateTxError

{ - fn from(err: Error) -> Self { - CreateTxError::Bdk(err) +impl

From for CreateTxError

{ + fn from(err: MiniscriptPsbtError) -> Self { + CreateTxError::MiniscriptPsbt(err) + } +} + +impl

From for CreateTxError

{ + fn from(err: psbt::Error) -> Self { + CreateTxError::Psbt(err) + } +} + +impl

From for CreateTxError

{ + fn from(err: coin_selection::Error) -> Self { + CreateTxError::CoinSelection(err) } } @@ -959,7 +1105,7 @@ impl Wallet { let internal_policy = internal_descriptor .as_ref() .map(|desc| { - Ok::<_, Error>( + Ok::<_, CreateTxError>( desc.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)? .unwrap(), ) @@ -972,7 +1118,9 @@ impl Wallet { && external_policy.requires_path() && params.external_policy_path.is_none() { - return Err(Error::SpendingPolicyRequired(KeychainKind::External).into()); + return Err(CreateTxError::SpendingPolicyRequired( + KeychainKind::External, + )); }; // Same for the internal_policy path, if present if let Some(internal_policy) = &internal_policy { @@ -980,7 +1128,9 @@ impl Wallet { && internal_policy.requires_path() && params.internal_policy_path.is_none() { - return Err(Error::SpendingPolicyRequired(KeychainKind::Internal).into()); + return Err(CreateTxError::SpendingPolicyRequired( + KeychainKind::Internal, + )); }; } @@ -992,7 +1142,7 @@ impl Wallet { )?; let internal_requirements = internal_policy .map(|policy| { - Ok::<_, Error>( + Ok::<_, CreateTxError>( policy.get_condition( params .internal_policy_path @@ -1008,15 +1158,9 @@ impl Wallet { debug!("Policy requirements: {:?}", requirements); let version = match params.version { - Some(tx_builder::Version(0)) => { - return Err(Error::Generic("Invalid version `0`".into()).into()) - } + Some(tx_builder::Version(0)) => return Err(CreateTxError::Version0), Some(tx_builder::Version(1)) if requirements.csv.is_some() => { - return Err(Error::Generic( - "TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV" - .into(), - ) - .into()) + return Err(CreateTxError::Version1Csv) } Some(tx_builder::Version(x)) => x, None if requirements.csv.is_some() => 2, @@ -1056,9 +1200,19 @@ impl Wallet { // Specific nLockTime required and we have no constraints, so just set to that value Some(x) if requirements.timelock.is_none() => x, // Specific nLockTime required and it's compatible with the constraints - Some(x) if requirements.timelock.unwrap().is_same_unit(x) && x >= requirements.timelock.unwrap() => x, + Some(x) + if requirements.timelock.unwrap().is_same_unit(x) + && x >= requirements.timelock.unwrap() => + { + x + } // Invalid nLockTime required - Some(x) => return Err(Error::Generic(format!("TxBuilder requested timelock of `{:?}`, but at least `{:?}` is required to spend from this script", x, requirements.timelock.unwrap())).into()) + Some(x) => { + return Err(CreateTxError::LockTime { + requested: x, + required: requirements.timelock.unwrap(), + }) + } }; let n_sequence = match (params.rbf, requirements.csv) { @@ -1076,20 +1230,13 @@ impl Wallet { // RBF with a specific value but that value is too high (Some(tx_builder::RbfValue::Value(rbf)), _) if !rbf.is_rbf() => { - return Err(Error::Generic( - "Cannot enable RBF with a nSequence >= 0xFFFFFFFE".into(), - ) - .into()) + return Err(CreateTxError::RbfSequence) } // RBF with a specific value requested, but the value is incompatible with CSV (Some(tx_builder::RbfValue::Value(rbf)), Some(csv)) if !check_nsequence_rbf(rbf, csv) => { - return Err(Error::Generic(format!( - "Cannot enable RBF with nSequence `{:?}` given a required OP_CSV of `{:?}`", - rbf, csv - )) - .into()) + return Err(CreateTxError::RbfSequenceCsv { rbf, csv }) } // RBF enabled with the default value with CSV also enabled. CSV takes precedence @@ -1108,10 +1255,9 @@ impl Wallet { FeePolicy::FeeAmount(fee) => { if let Some(previous_fee) = params.bumping_fee { if *fee < previous_fee.absolute { - return Err(Error::FeeTooLow { + return Err(CreateTxError::FeeTooLow { required: previous_fee.absolute, - } - .into()); + }); } } (FeeRate::from_sat_per_vb(0.0), *fee) @@ -1120,10 +1266,9 @@ impl Wallet { if let Some(previous_fee) = params.bumping_fee { let required_feerate = FeeRate::from_sat_per_vb(previous_fee.rate + 1.0); if *rate < required_feerate { - return Err(Error::FeeRateTooLow { + return Err(CreateTxError::FeeRateTooLow { required: required_feerate, - } - .into()); + }); } } (*rate, 0) @@ -1138,7 +1283,7 @@ impl Wallet { }; if params.manually_selected_only && params.utxos.is_empty() { - return Err(Error::NoUtxosSelected.into()); + return Err(CreateTxError::NoUtxosSelected); } // we keep it as a float while we accumulate it, and only round it at the end @@ -1152,7 +1297,7 @@ impl Wallet { && value.is_dust(script_pubkey) && !script_pubkey.is_provably_unspendable() { - return Err(Error::OutputBelowDustLimit(index).into()); + return Err(CreateTxError::OutputBelowDustLimit(index)); } if self.is_mine(script_pubkey) { @@ -1185,10 +1330,7 @@ impl Wallet { if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed && internal_descriptor.is_none() { - return Err(Error::Generic( - "The `change_policy` can be set only if the wallet has a change_descriptor".into(), - ) - .into()); + return Err(CreateTxError::ChangePolicyDescriptor); } let (required_utxos, optional_utxos) = self.preselect_utxos( @@ -1255,14 +1397,13 @@ impl Wallet { change_fee, } = excess { - return Err(Error::InsufficientFunds { + return Err(CreateTxError::InsufficientFunds { needed: *dust_threshold, available: remaining_amount.saturating_sub(*change_fee), - } - .into()); + }); } } else { - return Err(Error::NoRecipients.into()); + return Err(CreateTxError::NoRecipients); } } @@ -1319,7 +1460,7 @@ impl Wallet { /// builder /// .add_recipient(to_address.script_pubkey(), 50_000) /// .enable_rbf(); - /// builder.finish()? + /// builder.finish().expect("psbt") /// }; /// let _ = wallet.sign(&mut psbt, SignOptions::default())?; /// let tx = psbt.extract_tx(); @@ -1334,7 +1475,7 @@ impl Wallet { /// let _ = wallet.sign(&mut psbt, SignOptions::default())?; /// let fee_bumped_tx = psbt.extract_tx(); /// // broadcast fee_bumped_tx to replace original - /// # Ok::<(), CreateTxError<<() as PersistBackend>::WriteError>>(()) + /// # Ok::<(), bdk::Error>(()) /// ``` // TODO: support for merging multiple transactions while bumping the fees pub fn build_fee_bump( @@ -1489,11 +1630,11 @@ impl Wallet { /// let mut psbt = { /// let mut builder = wallet.build_tx(); /// builder.add_recipient(to_address.script_pubkey(), 50_000); - /// builder.finish()? + /// builder.finish().expect("psbt") /// }; /// let finalized = wallet.sign(&mut psbt, SignOptions::default())?; /// assert!(finalized, "we should have signed all the inputs"); - /// # Ok::<(), CreateTxError<<() as PersistBackend>::WriteError>>(()) + /// # Ok::<(), bdk::Error>(()) pub fn sign( &self, psbt: &mut psbt::PartiallySignedTransaction, @@ -1838,7 +1979,10 @@ impl Wallet { tx: Transaction, selected: Vec, params: TxParams, - ) -> Result { + ) -> Result> + where + D: PersistBackend, + { let mut psbt = psbt::PartiallySignedTransaction::from_unsigned_tx(tx)?; if params.add_global_xpubs { @@ -1854,7 +1998,7 @@ impl Wallet { None if xpub.xkey.depth == 0 => { (xpub.root_fingerprint(&self.secp), vec![].into()) } - _ => return Err(Error::MissingKeyOrigin(xpub.xkey.to_string())), + _ => return Err(CreateTxError::MissingKeyOrigin(xpub.xkey.to_string())), }; psbt.xpub.insert(xpub.xkey, origin); @@ -1879,7 +2023,7 @@ impl Wallet { match self.get_psbt_input(utxo, params.sighash, params.only_witness_utxo) { Ok(psbt_input) => psbt_input, Err(e) => match e { - Error::UnknownUtxo => psbt::Input { + CreateTxError::UnknownUtxo => psbt::Input { sighash_type: params.sighash, ..psbt::Input::default() }, @@ -1900,10 +2044,7 @@ impl Wallet { && !params.only_witness_utxo && foreign_psbt_input.non_witness_utxo.is_none() { - return Err(Error::Generic(format!( - "Missing non_witness_utxo on foreign utxo {}", - outpoint - ))); + return Err(CreateTxError::MissingNonWitnessUtxo(outpoint)); } *psbt_input = *foreign_psbt_input; } @@ -1921,14 +2062,17 @@ impl Wallet { utxo: LocalUtxo, sighash_type: Option, only_witness_utxo: bool, - ) -> Result { + ) -> Result> + where + D: PersistBackend, + { // Try to find the prev_script in our db to figure out if this is internal or external, // and the derivation index let &(keychain, child) = self .indexed_graph .index .index_of_spk(&utxo.txout.script_pubkey) - .ok_or(Error::UnknownUtxo)?; + .ok_or(CreateTxError::UnknownUtxo)?; let mut psbt_input = psbt::Input { sighash_type, @@ -1959,7 +2103,7 @@ impl Wallet { fn update_psbt_with_descriptor( &self, psbt: &mut psbt::PartiallySignedTransaction, - ) -> Result<(), Error> { + ) -> Result<(), MiniscriptPsbtError> { // We need to borrow `psbt` mutably within the loops, so we have to allocate a vec for all // the input utxos and outputs // diff --git a/crates/bdk/tests/wallet.rs b/crates/bdk/tests/wallet.rs index 0ade431be0..a271b2bc49 100644 --- a/crates/bdk/tests/wallet.rs +++ b/crates/bdk/tests/wallet.rs @@ -3299,10 +3299,12 @@ fn test_spend_coinbase() { .current_height(confirmation_height); assert!(matches!( builder.finish(), - Err(CreateTxError::Bdk(Error::InsufficientFunds { - needed: _, - available: 0 - })) + Err(CreateTxError::CoinSelection( + coin_selection::Error::InsufficientFunds { + needed: _, + available: 0 + } + )) )); // Still unspendable... @@ -3312,10 +3314,12 @@ fn test_spend_coinbase() { .current_height(not_yet_mature_time); assert_matches!( builder.finish(), - Err(CreateTxError::Bdk(Error::InsufficientFunds { - needed: _, - available: 0 - })) + Err(CreateTxError::CoinSelection( + coin_selection::Error::InsufficientFunds { + needed: _, + available: 0 + } + )) ); wallet @@ -3353,7 +3357,7 @@ fn test_allow_dust_limit() { assert_matches!( builder.finish(), - Err(CreateTxError::Bdk(Error::OutputBelowDustLimit(0))) + Err(CreateTxError::OutputBelowDustLimit(0)) ); let mut builder = wallet.build_tx();