Skip to content

Commit d443fe7

Browse files
committed
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>
1 parent b4c31cd commit d443fe7

File tree

6 files changed

+72
-59
lines changed

6 files changed

+72
-59
lines changed

crates/bdk/src/error.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@
99
// You may not use this file except in accordance with one or both of these
1010
// licenses.
1111

12-
//! Errors
13-
//!
14-
//! This module defines the errors that can be thrown by [`crate`] functions.
15-
1612
use crate::bitcoin::Network;
1713
use crate::{descriptor, wallet};
1814
use alloc::{string::String, vec::Vec};
@@ -93,17 +89,7 @@ pub enum Error {
9389
Psbt(bitcoin::psbt::Error),
9490
}
9591

96-
/// Errors returned by `Wallet::calculate_fee`.
97-
#[derive(Debug)]
98-
pub enum CalculateFeeError {
99-
/// Missing `TxOut` for one of the inputs of the tx
100-
MissingTxOut,
101-
/// When the transaction is invalid according to the graph it has a negative fee
102-
NegativeFee(i64),
103-
}
104-
10592
/// Errors returned by miniscript when updating inconsistent PSBTs
106-
#[allow(missing_docs)] // TODO add docs
10793
#[derive(Debug, Clone)]
10894
pub enum MiniscriptPsbtError {
10995
Conversion(miniscript::descriptor::ConversionError),

crates/bdk/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extern crate bip39;
2929

3030
#[allow(unused_imports)]
3131
#[macro_use]
32-
pub mod error;
32+
pub(crate) mod error;
3333
pub mod descriptor;
3434
pub mod keys;
3535
pub mod psbt;

crates/bdk/src/wallet/mod.rs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use core::fmt;
4040
use core::ops::Deref;
4141
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
4242

43+
use bdk_chain::tx_graph::CalculateFeeError;
4344
#[allow(unused_imports)]
4445
use log::{debug, error, info, trace};
4546

@@ -66,7 +67,7 @@ use crate::descriptor::{
6667
calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
6768
ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
6869
};
69-
use crate::error::{CalculateFeeError, Error, MiniscriptPsbtError};
70+
use crate::error::{Error, MiniscriptPsbtError};
7071
use crate::psbt::PsbtUtils;
7172
use crate::signer::SignerError;
7273
use crate::types::*;
@@ -434,11 +435,7 @@ impl<D> Wallet<D> {
434435
///
435436
/// Note `tx` does not have to be in the graph for this to work.
436437
pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
437-
match self.indexed_graph.graph().calculate_fee(tx) {
438-
None => Err(CalculateFeeError::MissingTxOut),
439-
Some(fee) if fee < 0 => Err(CalculateFeeError::NegativeFee(fee)),
440-
Some(fee) => Ok(u64::try_from(fee).unwrap()),
441-
}
438+
self.indexed_graph.graph().calculate_fee(tx)
442439
}
443440

444441
/// Calculate the `FeeRate` for a given transaction.
@@ -1072,13 +1069,12 @@ impl<D> Wallet<D> {
10721069
return Err(Error::IrreplaceableTransaction);
10731070
}
10741071

1075-
let fee = graph.calculate_fee(&tx).ok_or(Error::FeeRateUnavailable)?;
1076-
if fee < 0 {
1077-
// It's available but it's wrong so let's say it's unavailable
1078-
return Err(Error::FeeRateUnavailable)?;
1079-
}
1080-
let fee = fee as u64;
1081-
let feerate = FeeRate::from_wu(fee, tx.weight());
1072+
let fee = self
1073+
.calculate_fee(&tx)
1074+
.map_err(|_| Error::FeeRateUnavailable)?;
1075+
let fee_rate = self
1076+
.calculate_fee_rate(&tx)
1077+
.map_err(|_| Error::FeeRateUnavailable)?;
10821078

10831079
// remove the inputs from the tx and process them
10841080
let original_txin = tx.input.drain(..).collect::<Vec<_>>();
@@ -1162,7 +1158,7 @@ impl<D> Wallet<D> {
11621158
utxos: original_utxos,
11631159
bumping_fee: Some(tx_builder::PreviousFee {
11641160
absolute: fee,
1165-
rate: feerate.as_sat_per_vb(),
1161+
rate: fee_rate.as_sat_per_vb(),
11661162
}),
11671163
..Default::default()
11681164
};

crates/bdk/tests/wallet.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use bdk::wallet::coin_selection::LargestFirstCoinSelection;
66
use bdk::wallet::AddressIndex::*;
77
use bdk::wallet::{AddressIndex, AddressInfo, Balance, Wallet};
88
use bdk::{Error, FeeRate, KeychainKind};
9+
use bdk_chain::tx_graph::CalculateFeeError;
910
use bdk_chain::COINBASE_MATURITY;
1011
use bdk_chain::{BlockId, ConfirmationTime};
1112
use bitcoin::hashes::Hash;
@@ -104,7 +105,7 @@ fn test_get_funded_wallet_sent_and_received() {
104105
fn test_get_funded_wallet_tx_fees() {
105106
let (wallet, _) = get_funded_wallet(get_test_wpkh());
106107
assert_eq!(wallet.get_balance().confirmed, 50000);
107-
let mut tx_fee_amounts: Vec<(Txid, Result<u64, bdk::error::CalculateFeeError>)> = wallet
108+
let mut tx_fee_amounts: Vec<(Txid, Result<u64, CalculateFeeError>)> = wallet
108109
.transactions()
109110
.map(|ct| {
110111
let fee = wallet.calculate_fee(ct.node.tx);
@@ -116,7 +117,7 @@ fn test_get_funded_wallet_tx_fees() {
116117
assert_eq!(tx_fee_amounts.len(), 2);
117118
assert_matches!(
118119
tx_fee_amounts.get(1),
119-
Some((_, Err(bdk::error::CalculateFeeError::MissingTxOut)))
120+
Some((_, Err(CalculateFeeError::MissingTxOut(_))))
120121
);
121122
assert_matches!(tx_fee_amounts.get(0), Some((_, Ok(1000))))
122123
}
@@ -125,7 +126,7 @@ fn test_get_funded_wallet_tx_fees() {
125126
fn test_get_funded_wallet_tx_fee_rate() {
126127
let (wallet, _) = get_funded_wallet(get_test_wpkh());
127128
assert_eq!(wallet.get_balance().confirmed, 50000);
128-
let mut tx_fee_rates: Vec<(Txid, Result<FeeRate, bdk::error::CalculateFeeError>)> = wallet
129+
let mut tx_fee_rates: Vec<(Txid, Result<FeeRate, CalculateFeeError>)> = wallet
129130
.transactions()
130131
.map(|ct| {
131132
let fee_rate = wallet.calculate_fee_rate(ct.node.tx);
@@ -137,7 +138,7 @@ fn test_get_funded_wallet_tx_fee_rate() {
137138
assert_eq!(tx_fee_rates.len(), 2);
138139
assert_matches!(
139140
tx_fee_rates.get(1),
140-
Some((_, Err(bdk::error::CalculateFeeError::MissingTxOut)))
141+
Some((_, Err(CalculateFeeError::MissingTxOut(_))))
141142
);
142143
assert_matches!(tx_fee_rates.get(0), Some((_, Ok(_))))
143144
}

crates/chain/src/tx_graph.rs

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ pub struct CanonicalTx<'a, T, A> {
135135
pub tx_node: TxNode<'a, T, A>,
136136
}
137137

138+
/// Errors returned by `TxGraph::calculate_fee`.
139+
#[derive(Debug, PartialEq, Eq)]
140+
pub enum CalculateFeeError {
141+
/// Missing `TxOut` for one or more of the inputs of the tx
142+
MissingTxOut(Vec<OutPoint>),
143+
/// When the transaction is invalid according to the graph it has a negative fee
144+
NegativeFee(i64),
145+
}
146+
138147
impl<A> TxGraph<A> {
139148
/// Iterate over all tx outputs known by [`TxGraph`].
140149
///
@@ -236,33 +245,46 @@ impl<A> TxGraph<A> {
236245
}
237246

238247
/// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
239-
/// Returns `Some(_)` if we have all the `TxOut`s being spent by `tx` in the graph (either as
240-
/// the full transactions or individual txouts). If the returned value is negative, then the
241-
/// transaction is invalid according to the graph.
242-
///
243-
/// Returns `None` if we're missing an input for the tx in the graph.
248+
/// Returns `OK(_)` if we have all the `TxOut`s being spent by `tx` in the graph (either as
249+
/// the full transactions or individual txouts).
244250
///
245251
/// Note `tx` does not have to be in the graph for this to work.
246-
pub fn calculate_fee(&self, tx: &Transaction) -> Option<i64> {
252+
pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
247253
if tx.is_coin_base() {
248-
return Some(0);
254+
return Ok(0);
249255
}
250-
let inputs_sum = tx
251-
.input
252-
.iter()
253-
.map(|txin| {
254-
self.get_txout(txin.previous_output)
255-
.map(|txout| txout.value as i64)
256-
})
257-
.sum::<Option<i64>>()?;
256+
let inputs_sum = tx.input.iter().fold(
257+
(0_u64, Vec::new()),
258+
|(mut sum, mut missing_outpoints), txin| match self.get_txout(txin.previous_output) {
259+
None => {
260+
missing_outpoints.push(txin.previous_output);
261+
(sum, missing_outpoints)
262+
}
263+
Some(txout) => {
264+
sum += txout.value;
265+
(sum, missing_outpoints)
266+
}
267+
},
268+
);
269+
270+
let inputs_sum = if inputs_sum.1.is_empty() {
271+
Ok(inputs_sum.0 as i64)
272+
} else {
273+
Err(CalculateFeeError::MissingTxOut(inputs_sum.1))
274+
}?;
258275

259276
let outputs_sum = tx
260277
.output
261278
.iter()
262279
.map(|txout| txout.value as i64)
263280
.sum::<i64>();
264281

265-
Some(inputs_sum - outputs_sum)
282+
let fee = inputs_sum - outputs_sum;
283+
if fee < 0 {
284+
Err(CalculateFeeError::NegativeFee(fee))
285+
} else {
286+
Ok(fee as u64)
287+
}
266288
}
267289

268290
/// The transactions spending from this output.

crates/chain/tests/test_tx_graph.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[macro_use]
22
mod common;
3+
use bdk_chain::tx_graph::CalculateFeeError;
34
use bdk_chain::{
45
collections::*,
56
local_chain::LocalChain,
@@ -453,22 +454,29 @@ fn test_calculate_fee() {
453454
}],
454455
};
455456

456-
assert_eq!(graph.calculate_fee(&tx), Some(100));
457+
assert_eq!(graph.calculate_fee(&tx), Ok(100));
457458

458459
tx.input.remove(2);
459460

460-
// fee would be negative
461-
assert_eq!(graph.calculate_fee(&tx), Some(-200));
461+
// fee would be negative, should return CalculateFeeError::NegativeFee
462+
assert_eq!(
463+
graph.calculate_fee(&tx),
464+
Err(CalculateFeeError::NegativeFee(-200))
465+
);
462466

463-
// If we have an unknown outpoint, fee should return None.
467+
// If we have an unknown outpoint, fee should return CalculateFeeError::MissingTxOut.
468+
let outpoint = OutPoint {
469+
txid: h!("unknown_txid"),
470+
vout: 0,
471+
};
464472
tx.input.push(TxIn {
465-
previous_output: OutPoint {
466-
txid: h!("unknown_txid"),
467-
vout: 0,
468-
},
473+
previous_output: outpoint,
469474
..Default::default()
470475
});
471-
assert_eq!(graph.calculate_fee(&tx), None);
476+
assert_eq!(
477+
graph.calculate_fee(&tx),
478+
Err(CalculateFeeError::MissingTxOut(vec!(outpoint)))
479+
);
472480
}
473481

474482
#[test]
@@ -485,7 +493,7 @@ fn test_calculate_fee_on_coinbase() {
485493

486494
let graph = TxGraph::<()>::default();
487495

488-
assert_eq!(graph.calculate_fee(&tx), Some(0));
496+
assert_eq!(graph.calculate_fee(&tx), Ok(0));
489497
}
490498

491499
#[test]

0 commit comments

Comments
 (0)