]> Untitled Git - bdk/commitdiff
feat(tx_graph)!: change TxGraph::calculate_fee to return Result<u64,CalculateFeeError>
authorSteve Myers <steve@notmandatory.org>
Tue, 1 Aug 2023 17:42:37 +0000 (12:42 -0500)
committerSteve Myers <steve@notmandatory.org>
Wed, 30 Aug 2023 16:55:17 +0000 (11:55 -0500)
added
- tx_graph::CalculateFeeError enum

BREAKING CHANGES:

changed
- TxGraph::calculate_fee function to return Result<u64,CalculateFeeError> instead of Option<i64>

crates/bdk/src/error.rs
crates/bdk/src/lib.rs
crates/bdk/src/wallet/mod.rs
crates/bdk/tests/wallet.rs
crates/chain/src/tx_graph.rs
crates/chain/tests/test_tx_graph.rs

index f0e33fea63ad139af64396b776521f032386303d..fcb5a6f7b17ba159e15cedb46c3ae38e9aee18f8 100644 (file)
@@ -9,10 +9,6 @@
 // You may not use this file except in accordance with one or both of these
 // licenses.
 
-//! Errors
-//!
-//! This module defines the errors that can be thrown by [`crate`] functions.
-
 use crate::bitcoin::Network;
 use crate::{descriptor, wallet};
 use alloc::{string::String, vec::Vec};
@@ -93,17 +89,7 @@ pub enum 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
-#[allow(missing_docs)] // TODO add docs
 #[derive(Debug, Clone)]
 pub enum MiniscriptPsbtError {
     Conversion(miniscript::descriptor::ConversionError),
index 93ed400b1af2a034a01f7b2757ad55e5cf30e554..012a868a6179229a25bed09d8be0bb839a46f0c3 100644 (file)
@@ -29,7 +29,7 @@ extern crate bip39;
 
 #[allow(unused_imports)]
 #[macro_use]
-pub mod error;
+pub(crate) mod error;
 pub mod descriptor;
 pub mod keys;
 pub mod psbt;
index c939db1c8bd94cf227e253264a16df3ecd4313c6..e5095a1b44a787ce5dde7af0e87e1a68f91d972e 100644 (file)
@@ -40,6 +40,7 @@ use core::fmt;
 use core::ops::Deref;
 use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
 
+use bdk_chain::tx_graph::CalculateFeeError;
 #[allow(unused_imports)]
 use log::{debug, error, info, trace};
 
@@ -66,7 +67,7 @@ use crate::descriptor::{
     calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
     ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
 };
-use crate::error::{CalculateFeeError, Error, MiniscriptPsbtError};
+use crate::error::{Error, MiniscriptPsbtError};
 use crate::psbt::PsbtUtils;
 use crate::signer::SignerError;
 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.
     pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
-        match 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()),
-        }
+        self.indexed_graph.graph().calculate_fee(tx)
     }
 
     /// Calculate the `FeeRate` for a given transaction.
@@ -1072,13 +1069,12 @@ impl<D> Wallet<D> {
             return Err(Error::IrreplaceableTransaction);
         }
 
-        let fee = graph.calculate_fee(&tx).ok_or(Error::FeeRateUnavailable)?;
-        if fee < 0 {
-            // It's available but it's wrong so let's say it's unavailable
-            return Err(Error::FeeRateUnavailable)?;
-        }
-        let fee = fee as u64;
-        let feerate = FeeRate::from_wu(fee, tx.weight());
+        let fee = self
+            .calculate_fee(&tx)
+            .map_err(|_| Error::FeeRateUnavailable)?;
+        let fee_rate = self
+            .calculate_fee_rate(&tx)
+            .map_err(|_| Error::FeeRateUnavailable)?;
 
         // remove the inputs from the tx and process them
         let original_txin = tx.input.drain(..).collect::<Vec<_>>();
@@ -1162,7 +1158,7 @@ impl<D> Wallet<D> {
             utxos: original_utxos,
             bumping_fee: Some(tx_builder::PreviousFee {
                 absolute: fee,
-                rate: feerate.as_sat_per_vb(),
+                rate: fee_rate.as_sat_per_vb(),
             }),
             ..Default::default()
         };
index 8ef921e12b6af14905854c4f885b332979531862..906900e46e35815455f538556216675a57c28832 100644 (file)
@@ -6,6 +6,7 @@ use bdk::wallet::coin_selection::LargestFirstCoinSelection;
 use bdk::wallet::AddressIndex::*;
 use bdk::wallet::{AddressIndex, AddressInfo, Balance, Wallet};
 use bdk::{Error, FeeRate, KeychainKind};
+use bdk_chain::tx_graph::CalculateFeeError;
 use bdk_chain::COINBASE_MATURITY;
 use bdk_chain::{BlockId, ConfirmationTime};
 use bitcoin::hashes::Hash;
@@ -104,7 +105,7 @@ fn test_get_funded_wallet_sent_and_received() {
 fn test_get_funded_wallet_tx_fees() {
     let (wallet, _) = get_funded_wallet(get_test_wpkh());
     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()
         .map(|ct| {
             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_matches!(
         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))))
 }
@@ -125,7 +126,7 @@ fn test_get_funded_wallet_tx_fees() {
 fn test_get_funded_wallet_tx_fee_rate() {
     let (wallet, _) = get_funded_wallet(get_test_wpkh());
     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()
         .map(|ct| {
             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_matches!(
         tx_fee_rates.get(1),
-        Some((_, Err(bdk::error::CalculateFeeError::MissingTxOut)))
+        Some((_, Err(CalculateFeeError::MissingTxOut(_))))
     );
     assert_matches!(tx_fee_rates.get(0), Some((_, Ok(_))))
 }
index adb84ca2212ac497324e8ccfc362efc259cecf10..1572cd9a5001efa43b1d1ad0dd309d85dd74f215 100644 (file)
@@ -135,6 +135,15 @@ pub struct CanonicalTx<'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> {
     /// 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.
-    /// Returns `Some(_)` 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
-    /// transaction is invalid according to the graph.
-    ///
-    /// Returns `None` if we're missing an input for the tx in the graph.
+    /// Returns `OK(_)` if we have all the `TxOut`s being spent by `tx` in the graph (either as
+    /// the full transactions or individual txouts).
     ///
     /// 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() {
-            return Some(0);
+            return Ok(0);
         }
-        let inputs_sum = tx
-            .input
-            .iter()
-            .map(|txin| {
-                self.get_txout(txin.previous_output)
-                    .map(|txout| txout.value as i64)
-            })
-            .sum::<Option<i64>>()?;
+        let inputs_sum = tx.input.iter().fold(
+            (0_u64, Vec::new()),
+            |(mut sum, mut missing_outpoints), txin| match self.get_txout(txin.previous_output) {
+                None => {
+                    missing_outpoints.push(txin.previous_output);
+                    (sum, missing_outpoints)
+                }
+                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
             .output
@@ -262,7 +279,12 @@ impl<A> TxGraph<A> {
             .map(|txout| txout.value as 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.
index 26475f762997aedeb88c9962a905740651096672..4c68f510821ae257feda8b17616a489af6dbcc96 100644 (file)
@@ -1,5 +1,6 @@
 #[macro_use]
 mod common;
+use bdk_chain::tx_graph::CalculateFeeError;
 use bdk_chain::{
     collections::*,
     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);
 
-    // fee would be negative
-    assert_eq!(graph.calculate_fee(&tx), Some(-200));
+    // fee would be negative, should return CalculateFeeError::NegativeFee
+    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 {
-        previous_output: OutPoint {
-            txid: h!("unknown_txid"),
-            vout: 0,
-        },
+        previous_output: outpoint,
         ..Default::default()
     });
-    assert_eq!(graph.calculate_fee(&tx), None);
+    assert_eq!(
+        graph.calculate_fee(&tx),
+        Err(CalculateFeeError::MissingTxOut(vec!(outpoint)))
+    );
 }
 
 #[test]
@@ -485,7 +493,7 @@ fn test_calculate_fee_on_coinbase() {
 
     let graph = TxGraph::<()>::default();
 
-    assert_eq!(graph.calculate_fee(&tx), Some(0));
+    assert_eq!(graph.calculate_fee(&tx), Ok(0));
 }
 
 #[test]