Skip to content

Commit

Permalink
refactor(wallet)!: Add BuildFeeBumpError and use as error type for Wa…
Browse files Browse the repository at this point in the history
…llet::build_fee_bump()
  • Loading branch information
notmandatory committed Oct 16, 2023
1 parent 380b6ca commit 67f710f
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 23 deletions.
54 changes: 43 additions & 11 deletions crates/bdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -342,3 +337,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`]
///
/// [`TxBuilder::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 {}
25 changes: 13 additions & 12 deletions crates/bdk/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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();
Expand All @@ -1277,57 +1278,57 @@ 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
.input
.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();

Expand Down

0 comments on commit 67f710f

Please sign in to comment.