Skip to content

Commit

Permalink
refacor(wallet)!: Remove catch-all bdk::error::Error and replace with…
Browse files Browse the repository at this point in the history
… per function error enums

Add AddUtxoError and use as error type for TxBuilder::add_utxo
Add AddForeignUtxoError and use as error type for Wallet::add_utxo() and Wallet::add_utxos()
Add AllowShrinkingError and use as error type for TxBuilder::allow_shrinking
  • Loading branch information
notmandatory committed Oct 17, 2023
1 parent 836e893 commit 9c5e8cf
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 126 deletions.
7 changes: 3 additions & 4 deletions crates/bdk/examples/mnemonic_to_descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -14,21 +15,19 @@ 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<dyn Error>> {
fn main() -> Result<(), anyhow::Error> {
let secp = Secp256k1::new();

// In this example we are generating a 12 words mnemonic phrase
// but it is also possible generate 15, 18, 21 and 24 words
// 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);
Expand Down
188 changes: 104 additions & 84 deletions crates/bdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand All @@ -103,22 +56,6 @@ macro_rules! impl_error {
};
}

impl_error!(descriptor::error::Error, Descriptor);

impl From<crate::keys::KeyError> 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`]
///
Expand Down Expand Up @@ -330,25 +267,13 @@ impl<P: core::fmt::Display + core::fmt::Debug> 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,
}
Expand All @@ -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"),
Expand Down Expand Up @@ -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 {}
1 change: 0 additions & 1 deletion crates/bdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 13 additions & 15 deletions crates/bdk/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand All @@ -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::*;
Expand Down Expand Up @@ -1291,22 +1293,22 @@ impl<D> Wallet<D> {

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
.input
.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
Expand All @@ -1321,18 +1323,14 @@ impl<D> Wallet<D> {
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();

Expand Down Expand Up @@ -1499,7 +1497,7 @@ impl<D> Wallet<D> {
}

/// Return the spending policies for the wallet's descriptor
pub fn policies(&self, keychain: KeychainKind) -> Result<Option<Policy>, Error> {
pub fn policies(&self, keychain: KeychainKind) -> Result<Option<Policy>, DescriptorError> {
let signers = match keychain {
KeychainKind::External => &self.signers,
KeychainKind::Internal => &self.change_signers,
Expand Down Expand Up @@ -2054,7 +2052,7 @@ pub fn wallet_name_from_descriptor<T>(
change_descriptor: Option<T>,
network: Network,
secp: &SecpCtx,
) -> Result<String, Error>
) -> Result<String, DescriptorError>
where
T: IntoWalletDescriptor,
{
Expand Down
Loading

0 comments on commit 9c5e8cf

Please sign in to comment.