From a3bdf4c06a63533a92087a3b593f7ee3455a66da Mon Sep 17 00:00:00 2001
From: Steve Myers <steve@notmandatory.org>
Date: Mon, 16 Oct 2023 13:20:48 -0500
Subject: [PATCH] refactor(wallet)!: Add BuildFeeBumpError and use as error
 type for Wallet::build_fee_bump()

---
 crates/bdk/src/error.rs      | 56 +++++++++++++++++++++++++++++-------
 crates/bdk/src/wallet/mod.rs | 25 ++++++++--------
 2 files changed, 58 insertions(+), 23 deletions(-)

diff --git a/crates/bdk/src/error.rs b/crates/bdk/src/error.rs
index 7a164120f9..14157f2877 100644
--- a/crates/bdk/src/error.rs
+++ b/crates/bdk/src/error.rs
@@ -26,12 +26,12 @@ pub enum Error {
     Generic(String),
     /// 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
-    TransactionNotFound,
-    /// Happens when trying to bump a transaction that is already confirmed
-    TransactionConfirmed,
-    /// Trying to replace a tx that has a sequence >= `0xFFFFFFFE`
-    IrreplaceableTransaction,
+    // /// Thrown when a tx is not found in the internal database
+    // TransactionNotFound,
+    // /// Happens when trying to bump a transaction that is already confirmed
+    // TransactionConfirmed,
+    // /// Trying to replace a tx that has a sequence >= `0xFFFFFFFE`
+    // IrreplaceableTransaction,
     /// Node doesn't have data to estimate a fee rate
     FeeRateUnavailable,
     /// Error while working with [`keys`](crate::keys)
@@ -84,11 +84,6 @@ impl fmt::Display for Error {
         match self {
             Self::Generic(err) => write!(f, "Generic error: {}", err),
             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::FeeRateUnavailable => write!(f, "Fee rate unavailable"),
             Self::Key(err) => write!(f, "Key error: {}", err),
             Self::ChecksumMismatch => write!(f, "Descriptor checksum mismatch"),
@@ -144,6 +139,8 @@ impl_error!(bitcoin::psbt::Error, Psbt);
 
 #[derive(Debug)]
 /// Error returned from [`TxBuilder::finish`]
+///
+/// [`TxBuilder::finish`]: crate::wallet::tx_builder::TxBuilder::finish
 pub enum CreateTxError<P> {
     /// There was a problem with the descriptors passed in
     Descriptor(DescriptorError),
@@ -342,3 +339,40 @@ impl<P> From<coin_selection::Error> for CreateTxError<P> {
 
 #[cfg(feature = "std")]
 impl<P: core::fmt::Display + core::fmt::Debug> std::error::Error for CreateTxError<P> {}
+
+//
+
+#[derive(Debug)]
+/// Error returned from [`Wallet::build_fee_bump`]
+///
+/// [`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,
+    /// Thrown when a tx is not found in the internal database
+    TransactionNotFound,
+    /// Happens when trying to bump a transaction that is already confirmed
+    TransactionConfirmed,
+    /// Trying to replace a tx that has a sequence >= `0xFFFFFFFE`
+    IrreplaceableTransaction,
+    /// Node doesn't have data to estimate a fee rate
+    FeeRateUnavailable,
+}
+
+#[cfg(feature = "std")]
+impl fmt::Display for BuildFeeBumpError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            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::FeeRateUnavailable => write!(f, "Fee rate unavailable"),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for BuildFeeBumpError {}
diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs
index cc41addc6e..d32ba5d694 100644
--- a/crates/bdk/src/wallet/mod.rs
+++ b/crates/bdk/src/wallet/mod.rs
@@ -67,7 +67,7 @@ use crate::descriptor::{
     calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
     ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
 };
-use crate::error::{CreateTxError, Error, MiniscriptPsbtError};
+use crate::error::{BuildFeeBumpError, CreateTxError, Error, MiniscriptPsbtError};
 use crate::psbt::PsbtUtils;
 use crate::signer::SignerError;
 use crate::types::*;
@@ -1254,6 +1254,7 @@ impl<D> Wallet<D> {
     /// # use bdk::wallet::ChangeSet;
     /// # use bdk::error::CreateTxError;
     /// # use bdk_chain::PersistBackend;
+    /// # use anyhow::Error;
     /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
     /// # let mut wallet = doctest_wallet!();
     /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
@@ -1277,27 +1278,27 @@ impl<D> Wallet<D> {
     /// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
     /// let fee_bumped_tx = psbt.extract_tx();
     /// // broadcast fee_bumped_tx to replace original
-    /// # Ok::<(), bdk::Error>(())
+    /// # Ok::<(), anyhow::Error>(())
     /// ```
     // TODO: support for merging multiple transactions while bumping the fees
     pub fn build_fee_bump(
         &mut self,
         txid: Txid,
-    ) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
+    ) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, BuildFeeBumpError> {
         let graph = self.indexed_graph.graph();
         let txout_index = &self.indexed_graph.index;
         let chain_tip = self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default();
 
         let mut tx = graph
             .get_tx(txid)
-            .ok_or(Error::TransactionNotFound)?
+            .ok_or(BuildFeeBumpError::TransactionNotFound)?
             .clone();
 
         let pos = graph
             .get_chain_position(&self.chain, chain_tip, txid)
-            .ok_or(Error::TransactionNotFound)?;
+            .ok_or(BuildFeeBumpError::TransactionNotFound)?;
         if let ChainPosition::Confirmed(_) = pos {
-            return Err(Error::TransactionConfirmed);
+            return Err(BuildFeeBumpError::TransactionConfirmed);
         }
 
         if !tx
@@ -1305,29 +1306,29 @@ impl<D> Wallet<D> {
             .iter()
             .any(|txin| txin.sequence.to_consensus_u32() <= 0xFFFFFFFD)
         {
-            return Err(Error::IrreplaceableTransaction);
+            return Err(BuildFeeBumpError::IrreplaceableTransaction);
         }
 
         let fee = self
             .calculate_fee(&tx)
-            .map_err(|_| Error::FeeRateUnavailable)?;
+            .map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?;
         let fee_rate = self
             .calculate_fee_rate(&tx)
-            .map_err(|_| Error::FeeRateUnavailable)?;
+            .map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?;
 
         // remove the inputs from the tx and process them
         let original_txin = tx.input.drain(..).collect::<Vec<_>>();
         let original_utxos = original_txin
             .iter()
-            .map(|txin| -> Result<_, Error> {
+            .map(|txin| -> Result<_, BuildFeeBumpError> {
                 let prev_tx = graph
                     .get_tx(txin.previous_output.txid)
-                    .ok_or(Error::UnknownUtxo)?;
+                    .ok_or(BuildFeeBumpError::UnknownUtxo)?;
                 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(Error::UnknownUtxo)?
+                    .ok_or(BuildFeeBumpError::UnknownUtxo)?
                     .cloned()
                     .into();