diff --git a/crates/bdk/src/error.rs b/crates/bdk/src/error.rs index 105e642a04..7a164120f9 100644 --- a/crates/bdk/src/error.rs +++ b/crates/bdk/src/error.rs @@ -9,13 +9,17 @@ // You may not use this file except in accordance with one or both of these // licenses. -use crate::bitcoin::Network; -use crate::{descriptor, wallet}; -use alloc::{string::String, vec::Vec}; -use bitcoin::{OutPoint, Txid}; +//! Errors that can be thrown by the [`Wallet`](crate::wallet::Wallet) + +use crate::descriptor::policy::PolicyError; +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}; use core::fmt; -/// Errors that can be thrown by the [`Wallet`](crate::wallet::Wallet) +/// Old catch-all errors enum that can be thrown by the [`Wallet`](crate::wallet::Wallet) #[derive(Debug)] pub enum Error { /// Generic error @@ -53,8 +57,11 @@ pub enum Error { /// Errors returned by miniscript when updating inconsistent PSBTs #[derive(Debug, Clone)] pub enum MiniscriptPsbtError { + /// Descriptor key conversion error Conversion(miniscript::descriptor::ConversionError), + /// Return error type for PsbtExt::update_input_with_descriptor UtxoUpdate(miniscript::psbt::UtxoUpdateError), + /// Return error type for PsbtExt::update_output_with_descriptor OutputUpdate(miniscript::psbt::OutputUpdateError), } @@ -134,3 +141,204 @@ impl_error!(miniscript::Error, Miniscript); impl_error!(MiniscriptPsbtError, MiniscriptPsbt); impl_error!(bitcoin::bip32::Error, Bip32); impl_error!(bitcoin::psbt::Error, Psbt); + +#[derive(Debug)] +/// Error returned from [`TxBuilder::finish`] +pub enum CreateTxError

{ + /// There was a problem with the descriptors passed in + Descriptor(DescriptorError), + /// We were unable to write wallet data to the persistence backend + Persist(P), + /// There was a problem while extracting and manipulating policies + Policy(PolicyError), + /// 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: absolute::LockTime, + /// Required `LockTime` + required: absolute::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")] +impl

fmt::Display for CreateTxError

+where + P: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Descriptor(e) => e.fmt(f), + Self::Persist(e) => { + write!( + f, + "failed to write wallet data to persistence backend: {}", + e + ) + } + 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) + } + } + } +} + +impl

From for CreateTxError

{ + fn from(err: descriptor::error::Error) -> Self { + CreateTxError::Descriptor(err) + } +} + +impl

From for CreateTxError

{ + fn from(err: PolicyError) -> Self { + CreateTxError::Policy(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) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for CreateTxError

{} diff --git a/crates/bdk/src/lib.rs b/crates/bdk/src/lib.rs index 012a868a61..3b68395976 100644 --- a/crates/bdk/src/lib.rs +++ b/crates/bdk/src/lib.rs @@ -27,9 +27,8 @@ extern crate serde_json; #[cfg(feature = "keys-bip39")] extern crate bip39; -#[allow(unused_imports)] #[macro_use] -pub(crate) mod error; +pub mod error; pub mod descriptor; pub mod keys; pub mod psbt; diff --git a/crates/bdk/src/wallet/coin_selection.rs b/crates/bdk/src/wallet/coin_selection.rs index 0ce38242fd..7803c55091 100644 --- a/crates/bdk/src/wallet/coin_selection.rs +++ b/crates/bdk/src/wallet/coin_selection.rs @@ -26,7 +26,8 @@ //! ``` //! # use std::str::FromStr; //! # use bitcoin::*; -//! # use bdk::wallet::{self, ChangeSet, coin_selection::*, coin_selection, CreateTxError}; +//! # use bdk::wallet::{self, ChangeSet, coin_selection::*, coin_selection}; +//! # use bdk::error::CreateTxError; //! # use bdk_chain::PersistBackend; //! # use bdk::*; //! # use bdk::wallet::coin_selection::decide_change; diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 58c5f1002c..cc41addc6e 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -56,19 +56,18 @@ pub mod hardwaresigner; pub use utils::IsDust; -use crate::descriptor; #[allow(deprecated)] use coin_selection::DefaultCoinSelectionAlgorithm; use signer::{SignOptions, SignerOrdering, SignersContainer, TransactionSigner}; use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams}; use utils::{check_nsequence_rbf, After, Older, SecpCtx}; -use crate::descriptor::policy::{BuildSatisfaction, PolicyError}; +use crate::descriptor::policy::BuildSatisfaction; use crate::descriptor::{ - calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorError, - DescriptorMeta, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils, + calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta, + ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils, }; -use crate::error::{Error, MiniscriptPsbtError}; +use crate::error::{CreateTxError, Error, MiniscriptPsbtError}; use crate::psbt::PsbtUtils; use crate::signer::SignerError; use crate::types::*; @@ -284,207 +283,6 @@ pub enum InsertTxError { #[cfg(feature = "std")] impl std::error::Error for NewError

{} -#[derive(Debug)] -/// Error returned from [`TxBuilder::finish`] -pub enum CreateTxError

{ - /// There was a problem with the descriptors passed in - Descriptor(DescriptorError), - /// We were unable to write wallet data to the persistence backend - Persist(P), - /// There was a problem while extracting and manipulating policies - Policy(PolicyError), - /// 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: absolute::LockTime, - /// Required `LockTime` - required: absolute::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")] -impl

fmt::Display for CreateTxError

-where - P: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Descriptor(e) => e.fmt(f), - Self::Persist(e) => { - write!( - f, - "failed to write wallet data to persistence backend: {}", - e - ) - } - 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) - } - } - } -} - -impl

From for CreateTxError

{ - fn from(err: descriptor::error::Error) -> Self { - CreateTxError::Descriptor(err) - } -} - -impl

From for CreateTxError

{ - fn from(err: PolicyError) -> Self { - CreateTxError::Policy(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) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for CreateTxError

{} - impl Wallet { /// Create a wallet from a `descriptor` (and an optional `change_descriptor`) and load related /// transaction data from `db`. @@ -1053,7 +851,8 @@ impl Wallet { /// # use std::str::FromStr; /// # use bitcoin::*; /// # use bdk::*; - /// # use bdk::wallet::{ChangeSet,CreateTxError}; + /// # use bdk::wallet::ChangeSet; + /// # use bdk::error::CreateTxError; /// # use bdk_chain::PersistBackend; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); @@ -1452,7 +1251,8 @@ impl Wallet { /// # use std::str::FromStr; /// # use bitcoin::*; /// # use bdk::*; - /// # use bdk::wallet::{ChangeSet, CreateTxError}; + /// # use bdk::wallet::ChangeSet; + /// # use bdk::error::CreateTxError; /// # use bdk_chain::PersistBackend; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); @@ -1624,7 +1424,8 @@ impl Wallet { /// # use std::str::FromStr; /// # use bitcoin::*; /// # use bdk::*; - /// # use bdk::wallet::{ChangeSet, CreateTxError}; + /// # use bdk::wallet::ChangeSet; + /// # use bdk::error::CreateTxError; /// # use bdk_chain::PersistBackend; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); diff --git a/crates/bdk/src/wallet/tx_builder.rs b/crates/bdk/src/wallet/tx_builder.rs index 367e0be247..2b6a911707 100644 --- a/crates/bdk/src/wallet/tx_builder.rs +++ b/crates/bdk/src/wallet/tx_builder.rs @@ -17,7 +17,8 @@ //! # use std::str::FromStr; //! # use bitcoin::*; //! # use bdk::*; -//! # use bdk::wallet::{ChangeSet, CreateTxError}; +//! # use bdk::wallet::ChangeSet; +//! # use bdk::error::CreateTxError; //! # use bdk::wallet::tx_builder::CreateTx; //! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); //! # use bdk_chain::PersistBackend; @@ -81,7 +82,8 @@ impl TxBuilderContext for BumpFee {} /// # use bdk::wallet::tx_builder::*; /// # use bitcoin::*; /// # use core::str::FromStr; -/// # use bdk::wallet::{ChangeSet, CreateTxError}; +/// # use bdk::wallet::ChangeSet; +/// # use bdk::error::CreateTxError; /// # use bdk_chain::PersistBackend; /// # let mut wallet = doctest_wallet!(); /// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); @@ -644,7 +646,8 @@ impl<'a, D, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> { /// # use std::str::FromStr; /// # use bitcoin::*; /// # use bdk::*; - /// # use bdk::wallet::{ChangeSet, CreateTxError}; + /// # use bdk::wallet::ChangeSet; + /// # use bdk::error::CreateTxError; /// # use bdk::wallet::tx_builder::CreateTx; /// # use bdk_chain::PersistBackend; /// # let to_address = diff --git a/crates/bdk/tests/wallet.rs b/crates/bdk/tests/wallet.rs index d0cee40597..5aa44a55c9 100644 --- a/crates/bdk/tests/wallet.rs +++ b/crates/bdk/tests/wallet.rs @@ -1,10 +1,11 @@ use assert_matches::assert_matches; use bdk::descriptor::calc_checksum; +use bdk::error::CreateTxError; use bdk::psbt::PsbtUtils; use bdk::signer::{SignOptions, SignerError}; use bdk::wallet::coin_selection::{self, LargestFirstCoinSelection}; use bdk::wallet::AddressIndex::*; -use bdk::wallet::{AddressIndex, AddressInfo, Balance, CreateTxError, Wallet}; +use bdk::wallet::{AddressIndex, AddressInfo, Balance, Wallet}; use bdk::{FeeRate, KeychainKind}; use bdk_chain::COINBASE_MATURITY; use bdk_chain::{BlockId, ConfirmationTime};