feat(tx_graph)!: change TxGraph::calculate_fee to return Result<u64,CalculateFeeError>
added - tx_graph::CalculateFeeError enum BREAKING CHANGES: changed - TxGraph::calculate_fee function to return Result<u64,CalculateFeeError> instead of Option<i64>
This commit is contained in:
		
							parent
							
								
									b4c31cd5ba
								
							
						
					
					
						commit
						d443fe7f66
					
				@ -9,10 +9,6 @@
 | 
				
			|||||||
// You may not use this file except in accordance with one or both of these
 | 
					// You may not use this file except in accordance with one or both of these
 | 
				
			||||||
// licenses.
 | 
					// licenses.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//! Errors
 | 
					 | 
				
			||||||
//!
 | 
					 | 
				
			||||||
//! This module defines the errors that can be thrown by [`crate`] functions.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::bitcoin::Network;
 | 
					use crate::bitcoin::Network;
 | 
				
			||||||
use crate::{descriptor, wallet};
 | 
					use crate::{descriptor, wallet};
 | 
				
			||||||
use alloc::{string::String, vec::Vec};
 | 
					use alloc::{string::String, vec::Vec};
 | 
				
			||||||
@ -93,17 +89,7 @@ pub enum Error {
 | 
				
			|||||||
    Psbt(bitcoin::psbt::Error),
 | 
					    Psbt(bitcoin::psbt::Error),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Errors returned by `Wallet::calculate_fee`.
 | 
					 | 
				
			||||||
#[derive(Debug)]
 | 
					 | 
				
			||||||
pub enum CalculateFeeError {
 | 
					 | 
				
			||||||
    /// Missing `TxOut` for one of the inputs of the tx
 | 
					 | 
				
			||||||
    MissingTxOut,
 | 
					 | 
				
			||||||
    /// When the transaction is invalid according to the graph it has a negative fee
 | 
					 | 
				
			||||||
    NegativeFee(i64),
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Errors returned by miniscript when updating inconsistent PSBTs
 | 
					/// Errors returned by miniscript when updating inconsistent PSBTs
 | 
				
			||||||
#[allow(missing_docs)] // TODO add docs
 | 
					 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
pub enum MiniscriptPsbtError {
 | 
					pub enum MiniscriptPsbtError {
 | 
				
			||||||
    Conversion(miniscript::descriptor::ConversionError),
 | 
					    Conversion(miniscript::descriptor::ConversionError),
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,7 @@ extern crate bip39;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[allow(unused_imports)]
 | 
					#[allow(unused_imports)]
 | 
				
			||||||
#[macro_use]
 | 
					#[macro_use]
 | 
				
			||||||
pub mod error;
 | 
					pub(crate) mod error;
 | 
				
			||||||
pub mod descriptor;
 | 
					pub mod descriptor;
 | 
				
			||||||
pub mod keys;
 | 
					pub mod keys;
 | 
				
			||||||
pub mod psbt;
 | 
					pub mod psbt;
 | 
				
			||||||
 | 
				
			|||||||
@ -40,6 +40,7 @@ use core::fmt;
 | 
				
			|||||||
use core::ops::Deref;
 | 
					use core::ops::Deref;
 | 
				
			||||||
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
 | 
					use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bdk_chain::tx_graph::CalculateFeeError;
 | 
				
			||||||
#[allow(unused_imports)]
 | 
					#[allow(unused_imports)]
 | 
				
			||||||
use log::{debug, error, info, trace};
 | 
					use log::{debug, error, info, trace};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -66,7 +67,7 @@ use crate::descriptor::{
 | 
				
			|||||||
    calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
 | 
					    calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
 | 
				
			||||||
    ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
 | 
					    ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use crate::error::{CalculateFeeError, Error, MiniscriptPsbtError};
 | 
					use crate::error::{Error, MiniscriptPsbtError};
 | 
				
			||||||
use crate::psbt::PsbtUtils;
 | 
					use crate::psbt::PsbtUtils;
 | 
				
			||||||
use crate::signer::SignerError;
 | 
					use crate::signer::SignerError;
 | 
				
			||||||
use crate::types::*;
 | 
					use crate::types::*;
 | 
				
			||||||
@ -434,11 +435,7 @@ impl<D> Wallet<D> {
 | 
				
			|||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Note `tx` does not have to be in the graph for this to work.
 | 
					    /// Note `tx` does not have to be in the graph for this to work.
 | 
				
			||||||
    pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
 | 
					    pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
 | 
				
			||||||
        match self.indexed_graph.graph().calculate_fee(tx) {
 | 
					        self.indexed_graph.graph().calculate_fee(tx)
 | 
				
			||||||
            None => Err(CalculateFeeError::MissingTxOut),
 | 
					 | 
				
			||||||
            Some(fee) if fee < 0 => Err(CalculateFeeError::NegativeFee(fee)),
 | 
					 | 
				
			||||||
            Some(fee) => Ok(u64::try_from(fee).unwrap()),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Calculate the `FeeRate` for a given transaction.
 | 
					    /// Calculate the `FeeRate` for a given transaction.
 | 
				
			||||||
@ -1072,13 +1069,12 @@ impl<D> Wallet<D> {
 | 
				
			|||||||
            return Err(Error::IrreplaceableTransaction);
 | 
					            return Err(Error::IrreplaceableTransaction);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let fee = graph.calculate_fee(&tx).ok_or(Error::FeeRateUnavailable)?;
 | 
					        let fee = self
 | 
				
			||||||
        if fee < 0 {
 | 
					            .calculate_fee(&tx)
 | 
				
			||||||
            // It's available but it's wrong so let's say it's unavailable
 | 
					            .map_err(|_| Error::FeeRateUnavailable)?;
 | 
				
			||||||
            return Err(Error::FeeRateUnavailable)?;
 | 
					        let fee_rate = self
 | 
				
			||||||
        }
 | 
					            .calculate_fee_rate(&tx)
 | 
				
			||||||
        let fee = fee as u64;
 | 
					            .map_err(|_| Error::FeeRateUnavailable)?;
 | 
				
			||||||
        let feerate = FeeRate::from_wu(fee, tx.weight());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // remove the inputs from the tx and process them
 | 
					        // remove the inputs from the tx and process them
 | 
				
			||||||
        let original_txin = tx.input.drain(..).collect::<Vec<_>>();
 | 
					        let original_txin = tx.input.drain(..).collect::<Vec<_>>();
 | 
				
			||||||
@ -1162,7 +1158,7 @@ impl<D> Wallet<D> {
 | 
				
			|||||||
            utxos: original_utxos,
 | 
					            utxos: original_utxos,
 | 
				
			||||||
            bumping_fee: Some(tx_builder::PreviousFee {
 | 
					            bumping_fee: Some(tx_builder::PreviousFee {
 | 
				
			||||||
                absolute: fee,
 | 
					                absolute: fee,
 | 
				
			||||||
                rate: feerate.as_sat_per_vb(),
 | 
					                rate: fee_rate.as_sat_per_vb(),
 | 
				
			||||||
            }),
 | 
					            }),
 | 
				
			||||||
            ..Default::default()
 | 
					            ..Default::default()
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ use bdk::wallet::coin_selection::LargestFirstCoinSelection;
 | 
				
			|||||||
use bdk::wallet::AddressIndex::*;
 | 
					use bdk::wallet::AddressIndex::*;
 | 
				
			||||||
use bdk::wallet::{AddressIndex, AddressInfo, Balance, Wallet};
 | 
					use bdk::wallet::{AddressIndex, AddressInfo, Balance, Wallet};
 | 
				
			||||||
use bdk::{Error, FeeRate, KeychainKind};
 | 
					use bdk::{Error, FeeRate, KeychainKind};
 | 
				
			||||||
 | 
					use bdk_chain::tx_graph::CalculateFeeError;
 | 
				
			||||||
use bdk_chain::COINBASE_MATURITY;
 | 
					use bdk_chain::COINBASE_MATURITY;
 | 
				
			||||||
use bdk_chain::{BlockId, ConfirmationTime};
 | 
					use bdk_chain::{BlockId, ConfirmationTime};
 | 
				
			||||||
use bitcoin::hashes::Hash;
 | 
					use bitcoin::hashes::Hash;
 | 
				
			||||||
@ -104,7 +105,7 @@ fn test_get_funded_wallet_sent_and_received() {
 | 
				
			|||||||
fn test_get_funded_wallet_tx_fees() {
 | 
					fn test_get_funded_wallet_tx_fees() {
 | 
				
			||||||
    let (wallet, _) = get_funded_wallet(get_test_wpkh());
 | 
					    let (wallet, _) = get_funded_wallet(get_test_wpkh());
 | 
				
			||||||
    assert_eq!(wallet.get_balance().confirmed, 50000);
 | 
					    assert_eq!(wallet.get_balance().confirmed, 50000);
 | 
				
			||||||
    let mut tx_fee_amounts: Vec<(Txid, Result<u64, bdk::error::CalculateFeeError>)> = wallet
 | 
					    let mut tx_fee_amounts: Vec<(Txid, Result<u64, CalculateFeeError>)> = wallet
 | 
				
			||||||
        .transactions()
 | 
					        .transactions()
 | 
				
			||||||
        .map(|ct| {
 | 
					        .map(|ct| {
 | 
				
			||||||
            let fee = wallet.calculate_fee(ct.node.tx);
 | 
					            let fee = wallet.calculate_fee(ct.node.tx);
 | 
				
			||||||
@ -116,7 +117,7 @@ fn test_get_funded_wallet_tx_fees() {
 | 
				
			|||||||
    assert_eq!(tx_fee_amounts.len(), 2);
 | 
					    assert_eq!(tx_fee_amounts.len(), 2);
 | 
				
			||||||
    assert_matches!(
 | 
					    assert_matches!(
 | 
				
			||||||
        tx_fee_amounts.get(1),
 | 
					        tx_fee_amounts.get(1),
 | 
				
			||||||
        Some((_, Err(bdk::error::CalculateFeeError::MissingTxOut)))
 | 
					        Some((_, Err(CalculateFeeError::MissingTxOut(_))))
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    assert_matches!(tx_fee_amounts.get(0), Some((_, Ok(1000))))
 | 
					    assert_matches!(tx_fee_amounts.get(0), Some((_, Ok(1000))))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -125,7 +126,7 @@ fn test_get_funded_wallet_tx_fees() {
 | 
				
			|||||||
fn test_get_funded_wallet_tx_fee_rate() {
 | 
					fn test_get_funded_wallet_tx_fee_rate() {
 | 
				
			||||||
    let (wallet, _) = get_funded_wallet(get_test_wpkh());
 | 
					    let (wallet, _) = get_funded_wallet(get_test_wpkh());
 | 
				
			||||||
    assert_eq!(wallet.get_balance().confirmed, 50000);
 | 
					    assert_eq!(wallet.get_balance().confirmed, 50000);
 | 
				
			||||||
    let mut tx_fee_rates: Vec<(Txid, Result<FeeRate, bdk::error::CalculateFeeError>)> = wallet
 | 
					    let mut tx_fee_rates: Vec<(Txid, Result<FeeRate, CalculateFeeError>)> = wallet
 | 
				
			||||||
        .transactions()
 | 
					        .transactions()
 | 
				
			||||||
        .map(|ct| {
 | 
					        .map(|ct| {
 | 
				
			||||||
            let fee_rate = wallet.calculate_fee_rate(ct.node.tx);
 | 
					            let fee_rate = wallet.calculate_fee_rate(ct.node.tx);
 | 
				
			||||||
@ -137,7 +138,7 @@ fn test_get_funded_wallet_tx_fee_rate() {
 | 
				
			|||||||
    assert_eq!(tx_fee_rates.len(), 2);
 | 
					    assert_eq!(tx_fee_rates.len(), 2);
 | 
				
			||||||
    assert_matches!(
 | 
					    assert_matches!(
 | 
				
			||||||
        tx_fee_rates.get(1),
 | 
					        tx_fee_rates.get(1),
 | 
				
			||||||
        Some((_, Err(bdk::error::CalculateFeeError::MissingTxOut)))
 | 
					        Some((_, Err(CalculateFeeError::MissingTxOut(_))))
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    assert_matches!(tx_fee_rates.get(0), Some((_, Ok(_))))
 | 
					    assert_matches!(tx_fee_rates.get(0), Some((_, Ok(_))))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -135,6 +135,15 @@ pub struct CanonicalTx<'a, T, A> {
 | 
				
			|||||||
    pub tx_node: TxNode<'a, T, A>,
 | 
					    pub tx_node: TxNode<'a, T, A>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Errors returned by `TxGraph::calculate_fee`.
 | 
				
			||||||
 | 
					#[derive(Debug, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum CalculateFeeError {
 | 
				
			||||||
 | 
					    /// Missing `TxOut` for one or more of the inputs of the tx
 | 
				
			||||||
 | 
					    MissingTxOut(Vec<OutPoint>),
 | 
				
			||||||
 | 
					    /// When the transaction is invalid according to the graph it has a negative fee
 | 
				
			||||||
 | 
					    NegativeFee(i64),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<A> TxGraph<A> {
 | 
					impl<A> TxGraph<A> {
 | 
				
			||||||
    /// Iterate over all tx outputs known by [`TxGraph`].
 | 
					    /// Iterate over all tx outputs known by [`TxGraph`].
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
@ -236,25 +245,33 @@ impl<A> TxGraph<A> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
 | 
					    /// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
 | 
				
			||||||
    /// Returns `Some(_)` if we have all the `TxOut`s being spent by `tx` in the graph (either as
 | 
					    /// Returns `OK(_)` if we have all the `TxOut`s being spent by `tx` in the graph (either as
 | 
				
			||||||
    /// the full transactions or individual txouts). If the returned value is negative, then the
 | 
					    /// the full transactions or individual txouts).
 | 
				
			||||||
    /// transaction is invalid according to the graph.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Returns `None` if we're missing an input for the tx in the graph.
 | 
					 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Note `tx` does not have to be in the graph for this to work.
 | 
					    /// Note `tx` does not have to be in the graph for this to work.
 | 
				
			||||||
    pub fn calculate_fee(&self, tx: &Transaction) -> Option<i64> {
 | 
					    pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
 | 
				
			||||||
        if tx.is_coin_base() {
 | 
					        if tx.is_coin_base() {
 | 
				
			||||||
            return Some(0);
 | 
					            return Ok(0);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let inputs_sum = tx
 | 
					        let inputs_sum = tx.input.iter().fold(
 | 
				
			||||||
            .input
 | 
					            (0_u64, Vec::new()),
 | 
				
			||||||
            .iter()
 | 
					            |(mut sum, mut missing_outpoints), txin| match self.get_txout(txin.previous_output) {
 | 
				
			||||||
            .map(|txin| {
 | 
					                None => {
 | 
				
			||||||
                self.get_txout(txin.previous_output)
 | 
					                    missing_outpoints.push(txin.previous_output);
 | 
				
			||||||
                    .map(|txout| txout.value as i64)
 | 
					                    (sum, missing_outpoints)
 | 
				
			||||||
            })
 | 
					                }
 | 
				
			||||||
            .sum::<Option<i64>>()?;
 | 
					                Some(txout) => {
 | 
				
			||||||
 | 
					                    sum += txout.value;
 | 
				
			||||||
 | 
					                    (sum, missing_outpoints)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let inputs_sum = if inputs_sum.1.is_empty() {
 | 
				
			||||||
 | 
					            Ok(inputs_sum.0 as i64)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Err(CalculateFeeError::MissingTxOut(inputs_sum.1))
 | 
				
			||||||
 | 
					        }?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let outputs_sum = tx
 | 
					        let outputs_sum = tx
 | 
				
			||||||
            .output
 | 
					            .output
 | 
				
			||||||
@ -262,7 +279,12 @@ impl<A> TxGraph<A> {
 | 
				
			|||||||
            .map(|txout| txout.value as i64)
 | 
					            .map(|txout| txout.value as i64)
 | 
				
			||||||
            .sum::<i64>();
 | 
					            .sum::<i64>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Some(inputs_sum - outputs_sum)
 | 
					        let fee = inputs_sum - outputs_sum;
 | 
				
			||||||
 | 
					        if fee < 0 {
 | 
				
			||||||
 | 
					            Err(CalculateFeeError::NegativeFee(fee))
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Ok(fee as u64)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// The transactions spending from this output.
 | 
					    /// The transactions spending from this output.
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
#[macro_use]
 | 
					#[macro_use]
 | 
				
			||||||
mod common;
 | 
					mod common;
 | 
				
			||||||
 | 
					use bdk_chain::tx_graph::CalculateFeeError;
 | 
				
			||||||
use bdk_chain::{
 | 
					use bdk_chain::{
 | 
				
			||||||
    collections::*,
 | 
					    collections::*,
 | 
				
			||||||
    local_chain::LocalChain,
 | 
					    local_chain::LocalChain,
 | 
				
			||||||
@ -453,22 +454,29 @@ fn test_calculate_fee() {
 | 
				
			|||||||
        }],
 | 
					        }],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert_eq!(graph.calculate_fee(&tx), Some(100));
 | 
					    assert_eq!(graph.calculate_fee(&tx), Ok(100));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tx.input.remove(2);
 | 
					    tx.input.remove(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // fee would be negative
 | 
					    // fee would be negative, should return CalculateFeeError::NegativeFee
 | 
				
			||||||
    assert_eq!(graph.calculate_fee(&tx), Some(-200));
 | 
					    assert_eq!(
 | 
				
			||||||
 | 
					        graph.calculate_fee(&tx),
 | 
				
			||||||
 | 
					        Err(CalculateFeeError::NegativeFee(-200))
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // If we have an unknown outpoint, fee should return None.
 | 
					    // If we have an unknown outpoint, fee should return CalculateFeeError::MissingTxOut.
 | 
				
			||||||
 | 
					    let outpoint = OutPoint {
 | 
				
			||||||
 | 
					        txid: h!("unknown_txid"),
 | 
				
			||||||
 | 
					        vout: 0,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
    tx.input.push(TxIn {
 | 
					    tx.input.push(TxIn {
 | 
				
			||||||
        previous_output: OutPoint {
 | 
					        previous_output: outpoint,
 | 
				
			||||||
            txid: h!("unknown_txid"),
 | 
					 | 
				
			||||||
            vout: 0,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        ..Default::default()
 | 
					        ..Default::default()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    assert_eq!(graph.calculate_fee(&tx), None);
 | 
					    assert_eq!(
 | 
				
			||||||
 | 
					        graph.calculate_fee(&tx),
 | 
				
			||||||
 | 
					        Err(CalculateFeeError::MissingTxOut(vec!(outpoint)))
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
@ -485,7 +493,7 @@ fn test_calculate_fee_on_coinbase() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let graph = TxGraph::<()>::default();
 | 
					    let graph = TxGraph::<()>::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert_eq!(graph.calculate_fee(&tx), Some(0));
 | 
					    assert_eq!(graph.calculate_fee(&tx), Ok(0));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user