From 1667496ee88a3d19bd815afb8f0a639a11aefb85 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Wed, 26 Jul 2023 19:46:40 -0500 Subject: [PATCH] feat(wallet)!: remove TransactionDetails from bdk::Wallet API Added - spk_txout_index::TotalSentReceived struct for tx total sent and received amounts - Wallet::total_send_received and SpkTxOutIndex::total_sent_received functions - Wallet::calculate_fee and Wallet::calculate_fee_rate functions - Wallet::error::CalculateFeeError BREAKING CHANGES: Removed - TransactionDetails struct Changed - Wallet::get_tx now returns CanonicalTx instead of TransactionDetails - TxBuilder::finish now returns only a PartiallySignedTransaction --- crates/bdk/src/error.rs | 14 + crates/bdk/src/keys/mod.rs | 2 +- crates/bdk/src/lib.rs | 2 +- crates/bdk/src/types.rs | 38 +- crates/bdk/src/wallet/coin_selection.rs | 2 +- crates/bdk/src/wallet/mod.rs | 131 ++--- crates/bdk/src/wallet/tx_builder.rs | 15 +- crates/bdk/tests/common.rs | 70 ++- crates/bdk/tests/psbt.rs | 16 +- crates/bdk/tests/wallet.rs | 469 +++++++++++------- crates/chain/src/spk_txout_index.rs | 38 ++ example-crates/wallet_electrum/src/main.rs | 2 +- example-crates/wallet_esplora/src/main.rs | 2 +- .../wallet_esplora_async/src/main.rs | 2 +- 14 files changed, 462 insertions(+), 341 deletions(-) diff --git a/crates/bdk/src/error.rs b/crates/bdk/src/error.rs index 22817a3ef7..9f4f6a2383 100644 --- a/crates/bdk/src/error.rs +++ b/crates/bdk/src/error.rs @@ -9,6 +9,10 @@ // 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}; @@ -89,7 +93,17 @@ pub enum Error { Psbt(bitcoin::util::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), diff --git a/crates/bdk/src/keys/mod.rs b/crates/bdk/src/keys/mod.rs index b4cfb6dee3..80eaf4fb2d 100644 --- a/crates/bdk/src/keys/mod.rs +++ b/crates/bdk/src/keys/mod.rs @@ -754,7 +754,7 @@ fn expand_multi_keys, Ctx: ScriptContext>( let (key_map, valid_networks) = key_maps_networks.into_iter().fold( (KeyMap::default(), any_network()), |(mut keys_acc, net_acc), (key, net)| { - keys_acc.extend(key.into_iter()); + keys_acc.extend(key); let net_acc = merge_networks(&net_acc, &net); (keys_acc, net_acc) diff --git a/crates/bdk/src/lib.rs b/crates/bdk/src/lib.rs index 012a868a61..93ed400b1a 100644 --- a/crates/bdk/src/lib.rs +++ b/crates/bdk/src/lib.rs @@ -29,7 +29,7 @@ extern crate bip39; #[allow(unused_imports)] #[macro_use] -pub(crate) mod error; +pub mod error; pub mod descriptor; pub mod keys; pub mod psbt; diff --git a/crates/bdk/src/types.rs b/crates/bdk/src/types.rs index e21bef9066..eafe794dd9 100644 --- a/crates/bdk/src/types.rs +++ b/crates/bdk/src/types.rs @@ -14,8 +14,8 @@ use core::convert::AsRef; use core::ops::Sub; use bdk_chain::ConfirmationTime; -use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut}; -use bitcoin::{hash_types::Txid, util::psbt}; +use bitcoin::blockdata::transaction::{OutPoint, TxOut}; +use bitcoin::util::psbt; use serde::{Deserialize, Serialize}; @@ -234,40 +234,6 @@ impl Utxo { } } -/// A wallet transaction -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct TransactionDetails { - /// Optional transaction - pub transaction: Option, - /// Transaction id - pub txid: Txid, - /// Received value (sats) - /// Sum of owned outputs of this transaction. - pub received: u64, - /// Sent value (sats) - /// Sum of owned inputs of this transaction. - pub sent: u64, - /// Fee value in sats if it was available. - pub fee: Option, - /// If the transaction is confirmed, contains height and Unix timestamp of the block containing the - /// transaction, unconfirmed transaction contains `None`. - pub confirmation_time: ConfirmationTime, -} - -impl PartialOrd for TransactionDetails { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for TransactionDetails { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.confirmation_time - .cmp(&other.confirmation_time) - .then_with(|| self.txid.cmp(&other.txid)) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/bdk/src/wallet/coin_selection.rs b/crates/bdk/src/wallet/coin_selection.rs index e7927cabb3..49a7502daf 100644 --- a/crates/bdk/src/wallet/coin_selection.rs +++ b/crates/bdk/src/wallet/coin_selection.rs @@ -81,7 +81,7 @@ //! // create wallet, sync, ... //! //! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); -//! let (psbt, details) = { +//! let psbt = { //! let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything); //! builder.add_recipient(to_address.script_pubkey(), 50_000); //! builder.finish()? diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index f2f717d9fe..86b39ecfe3 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -26,7 +26,7 @@ use bdk_chain::{ local_chain::{self, LocalChain, UpdateNotConnectedError}, tx_graph::{CanonicalTx, TxGraph}, Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, - IndexedTxGraph, Persist, PersistBackend, + IndexedTxGraph, Persist, PersistBackend, TotalSentReceived, }; use bitcoin::consensus::encode::serialize; use bitcoin::secp256k1::Secp256k1; @@ -65,7 +65,7 @@ use crate::descriptor::{ calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils, }; -use crate::error::{Error, MiniscriptPsbtError}; +use crate::error::{CalculateFeeError, Error, MiniscriptPsbtError}; use crate::psbt::PsbtUtils; use crate::signer::SignerError; use crate::types::*; @@ -427,27 +427,49 @@ impl Wallet { .next() } - /// Return a single transactions made and received by the wallet + /// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction. /// - /// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if - /// `include_raw` is `true`. - pub fn get_tx(&self, txid: Txid, include_raw: bool) -> Option { + /// Note `tx` does not have to be in the graph for this to work. + pub fn calculate_fee(&self, tx: &Transaction) -> Result { + 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()), + } + } + + /// Calculate the `FeeRate` for a given transaction. + /// + /// Note `tx` does not have to be in the graph for this to work. + pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result { + self.calculate_fee(tx).map(|fee| { + let weight = tx.weight(); + FeeRate::from_wu(fee, weight) + }) + } + + /// Return `TotalSentReceived` for a `Transaction` in relation to the `Wallet` and it's + /// descriptors. + pub fn total_sent_received(&self, tx: &Transaction) -> TotalSentReceived { + self.indexed_graph.index.total_sent_received(tx) + } + + /// Return a single `CanonicalTx` made and received by the wallet or `None` if it doesn't + /// exist in the wallet + pub fn get_tx( + &self, + txid: Txid, + ) -> Option> { let graph = self.indexed_graph.graph(); - let canonical_tx = CanonicalTx { + Some(CanonicalTx { observed_as: graph.get_chain_position( &self.chain, self.chain.tip().unwrap_or_default(), txid, )?, node: graph.get_tx_node(txid)?, - }; - - Some(new_tx_details( - &self.indexed_graph, - canonical_tx, - include_raw, - )) + }) } /// Add a new checkpoint to the wallet's internal view of the chain. @@ -599,7 +621,7 @@ impl Wallet { /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); - /// let (psbt, details) = { + /// let psbt = { /// let mut builder = wallet.build_tx(); /// builder /// .add_recipient(to_address.script_pubkey(), 50_000); @@ -624,7 +646,7 @@ impl Wallet { &mut self, coin_selection: Cs, params: TxParams, - ) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error> + ) -> Result where D: PersistBackend, { @@ -969,20 +991,8 @@ impl Wallet { // sort input/outputs according to the chosen algorithm params.ordering.sort_tx(&mut tx); - let txid = tx.txid(); - let sent = coin_selection.local_selected_amount(); let psbt = self.complete_transaction(tx, coin_selection.selected, params)?; - - let transaction_details = TransactionDetails { - transaction: None, - txid, - confirmation_time: ConfirmationTime::Unconfirmed { last_seen: 0 }, - received, - sent, - fee: Some(fee_amount), - }; - - Ok((psbt, transaction_details)) + Ok(psbt) } /// Bump the fee of a transaction previously created with this wallet. @@ -1001,7 +1011,7 @@ impl Wallet { /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); - /// let (mut psbt, _) = { + /// let mut psbt = { /// let mut builder = wallet.build_tx(); /// builder /// .add_recipient(to_address.script_pubkey(), 50_000) @@ -1011,7 +1021,7 @@ impl Wallet { /// let _ = wallet.sign(&mut psbt, SignOptions::default())?; /// let tx = psbt.extract_tx(); /// // broadcast tx but it's taking too long to confirm so we want to bump the fee - /// let (mut psbt, _) = { + /// let mut psbt = { /// let mut builder = wallet.build_fee_bump(tx.txid())?; /// builder /// .fee_rate(FeeRate::from_sat_per_vb(5.0)); @@ -1171,7 +1181,7 @@ impl Wallet { /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); - /// let (mut psbt, _) = { + /// let mut psbt = { /// let mut builder = wallet.build_tx(); /// builder.add_recipient(to_address.script_pubkey(), 50_000); /// builder.finish()? @@ -1721,7 +1731,7 @@ impl Wallet { Ok(changed) } - /// Commits all curently [`staged`] changed to the persistence backend returning and error when + /// Commits all currently [`staged`] changed to the persistence backend returning and error when /// this fails. /// /// This returns whether the `update` resulted in any changes. @@ -1812,61 +1822,6 @@ fn new_local_utxo( } } -fn new_tx_details( - indexed_graph: &IndexedTxGraph>, - canonical_tx: CanonicalTx<'_, Transaction, ConfirmationTimeAnchor>, - include_raw: bool, -) -> TransactionDetails { - let graph = indexed_graph.graph(); - let index = &indexed_graph.index; - let tx = canonical_tx.node.tx; - - let received = tx - .output - .iter() - .map(|txout| { - if index.index_of_spk(&txout.script_pubkey).is_some() { - txout.value - } else { - 0 - } - }) - .sum(); - - let sent = tx - .input - .iter() - .map(|txin| { - if let Some((_, txout)) = index.txout(txin.previous_output) { - txout.value - } else { - 0 - } - }) - .sum(); - - let inputs = tx - .input - .iter() - .map(|txin| { - graph - .get_txout(txin.previous_output) - .map(|txout| txout.value) - }) - .sum::>(); - let outputs = tx.output.iter().map(|txout| txout.value).sum(); - let fee = inputs.map(|inputs| inputs.saturating_sub(outputs)); - - TransactionDetails { - transaction: if include_raw { Some(tx.clone()) } else { None }, - txid: canonical_tx.node.txid, - received, - sent, - fee, - confirmation_time: canonical_tx.observed_as.cloned().into(), - } -} - #[macro_export] #[doc(hidden)] /// Macro for getting a wallet for use in a doctest diff --git a/crates/bdk/src/wallet/tx_builder.rs b/crates/bdk/src/wallet/tx_builder.rs index 165f01f25f..b43880fb76 100644 --- a/crates/bdk/src/wallet/tx_builder.rs +++ b/crates/bdk/src/wallet/tx_builder.rs @@ -32,7 +32,7 @@ //! .do_not_spend_change() //! // Turn on RBF signaling //! .enable_rbf(); -//! let (psbt, tx_details) = tx_builder.finish()?; +//! let psbt = tx_builder.finish()?; //! # Ok::<(), bdk::Error>(()) //! ``` @@ -48,10 +48,7 @@ use bitcoin::{LockTime, OutPoint, Script, Sequence, Transaction}; use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; use super::ChangeSet; -use crate::{ - types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo}, - TransactionDetails, -}; +use crate::types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo}; use crate::{Error, Utxo, Wallet}; /// Context in which the [`TxBuilder`] is valid pub trait TxBuilderContext: core::fmt::Debug + Default + Clone {} @@ -85,7 +82,7 @@ impl TxBuilderContext for BumpFee {} /// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); /// # let addr2 = addr1.clone(); /// // chaining -/// let (psbt1, details) = { +/// let psbt1 = { /// let mut builder = wallet.build_tx(); /// builder /// .ordering(TxOrdering::Untouched) @@ -95,7 +92,7 @@ impl TxBuilderContext for BumpFee {} /// }; /// /// // non-chaining -/// let (psbt2, details) = { +/// let psbt2 = { /// let mut builder = wallet.build_tx(); /// builder.ordering(TxOrdering::Untouched); /// for addr in &[addr1, addr2] { @@ -527,7 +524,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D, /// Returns the [`BIP174`] "PSBT" and summary details about the transaction. /// /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki - pub fn finish(self) -> Result<(Psbt, TransactionDetails), Error> + pub fn finish(self) -> Result where D: PersistBackend, { @@ -637,7 +634,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> { /// .drain_to(to_address.script_pubkey()) /// .fee_rate(FeeRate::from_sat_per_vb(5.0)) /// .enable_rbf(); - /// let (psbt, tx_details) = tx_builder.finish()?; + /// let psbt = tx_builder.finish()?; /// # Ok::<(), bdk::Error>(()) /// ``` /// diff --git a/crates/bdk/tests/common.rs b/crates/bdk/tests/common.rs index de94670321..7acb6b6a1f 100644 --- a/crates/bdk/tests/common.rs +++ b/crates/bdk/tests/common.rs @@ -1,8 +1,11 @@ #![allow(unused)] -use bdk::{wallet::AddressIndex, Wallet}; + +use bdk::{wallet::AddressIndex, KeychainKind, LocalUtxo, Wallet}; +use bdk_chain::indexed_tx_graph::Indexer; use bdk_chain::{BlockId, ConfirmationTime}; use bitcoin::hashes::Hash; -use bitcoin::{BlockHash, Network, Transaction, TxOut}; +use bitcoin::{Address, BlockHash, Network, OutPoint, Transaction, TxIn, TxOut, Txid}; +use std::str::FromStr; /// Return a fake wallet that appears to be funded for testing. pub fn get_funded_wallet_with_change( @@ -10,35 +13,84 @@ pub fn get_funded_wallet_with_change( change: Option<&str>, ) -> (Wallet, bitcoin::Txid) { let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Regtest).unwrap(); - let address = wallet.get_address(AddressIndex::New).address; + let change_address = wallet.get_address(AddressIndex::New).address; + let sendto_address = + Address::from_str("tb1qeua3n9t076zntxj64cz7qywwtwxd0lwvmtcmtd").expect("address"); - let tx = Transaction { + let tx0 = Transaction { version: 1, lock_time: bitcoin::PackedLockTime(0), - input: vec![], + input: vec![TxIn { + previous_output: OutPoint { + txid: Txid::all_zeros(), + vout: 0, + }, + script_sig: Default::default(), + sequence: Default::default(), + witness: Default::default(), + }], output: vec![TxOut { - value: 50_000, - script_pubkey: address.script_pubkey(), + value: 76_000, + script_pubkey: change_address.script_pubkey(), }], }; + let tx1 = Transaction { + version: 1, + lock_time: bitcoin::PackedLockTime(0), + input: vec![TxIn { + previous_output: OutPoint { + txid: tx0.txid(), + vout: 0, + }, + script_sig: Default::default(), + sequence: Default::default(), + witness: Default::default(), + }], + output: vec![ + TxOut { + value: 50_000, + script_pubkey: change_address.script_pubkey(), + }, + TxOut { + value: 25_000, + script_pubkey: sendto_address.script_pubkey(), + }, + ], + }; + wallet .insert_checkpoint(BlockId { height: 1_000, hash: BlockHash::all_zeros(), }) .unwrap(); + wallet + .insert_checkpoint(BlockId { + height: 2_000, + hash: BlockHash::all_zeros(), + }) + .unwrap(); wallet .insert_tx( - tx.clone(), + tx0, ConfirmationTime::Confirmed { height: 1_000, time: 100, }, ) .unwrap(); + wallet + .insert_tx( + tx1.clone(), + ConfirmationTime::Confirmed { + height: 2_000, + time: 200, + }, + ) + .unwrap(); - (wallet, tx.txid()) + (wallet, tx1.txid()) } pub fn get_funded_wallet(descriptor: &str) -> (Wallet, bitcoin::Txid) { diff --git a/crates/bdk/tests/psbt.rs b/crates/bdk/tests/psbt.rs index 8d399f5fea..aae8528ea7 100644 --- a/crates/bdk/tests/psbt.rs +++ b/crates/bdk/tests/psbt.rs @@ -18,7 +18,7 @@ fn test_psbt_malformed_psbt_input_legacy() { let send_to = wallet.get_address(AddressIndex::New); let mut builder = wallet.build_tx(); builder.add_recipient(send_to.script_pubkey(), 10_000); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); psbt.inputs.push(psbt_bip.inputs[0].clone()); let options = SignOptions { trust_witness_utxo: true, @@ -35,7 +35,7 @@ fn test_psbt_malformed_psbt_input_segwit() { let send_to = wallet.get_address(AddressIndex::New); let mut builder = wallet.build_tx(); builder.add_recipient(send_to.script_pubkey(), 10_000); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); psbt.inputs.push(psbt_bip.inputs[1].clone()); let options = SignOptions { trust_witness_utxo: true, @@ -51,7 +51,7 @@ fn test_psbt_malformed_tx_input() { let send_to = wallet.get_address(AddressIndex::New); let mut builder = wallet.build_tx(); builder.add_recipient(send_to.script_pubkey(), 10_000); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); psbt.unsigned_tx.input.push(TxIn::default()); let options = SignOptions { trust_witness_utxo: true, @@ -67,7 +67,7 @@ fn test_psbt_sign_with_finalized() { let send_to = wallet.get_address(AddressIndex::New); let mut builder = wallet.build_tx(); builder.add_recipient(send_to.script_pubkey(), 10_000); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); // add a finalized input psbt.inputs.push(psbt_bip.inputs[0].clone()); @@ -89,7 +89,7 @@ fn test_psbt_fee_rate_with_witness_utxo() { let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let fee_amount = psbt.fee_amount(); assert!(fee_amount.is_some()); @@ -114,7 +114,7 @@ fn test_psbt_fee_rate_with_nonwitness_utxo() { let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let fee_amount = psbt.fee_amount(); assert!(fee_amount.is_some()); let unfinalized_fee_rate = psbt.fee_rate().unwrap(); @@ -138,7 +138,7 @@ fn test_psbt_fee_rate_with_missing_txout() { let mut builder = wpkh_wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); - let (mut wpkh_psbt, _) = builder.finish().unwrap(); + let mut wpkh_psbt = builder.finish().unwrap(); wpkh_psbt.inputs[0].witness_utxo = None; wpkh_psbt.inputs[0].non_witness_utxo = None; @@ -150,7 +150,7 @@ fn test_psbt_fee_rate_with_missing_txout() { let mut builder = pkh_wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); - let (mut pkh_psbt, _) = builder.finish().unwrap(); + let mut pkh_psbt = builder.finish().unwrap(); pkh_psbt.inputs[0].non_witness_utxo = None; assert!(pkh_psbt.fee_amount().is_none()); diff --git a/crates/bdk/tests/wallet.rs b/crates/bdk/tests/wallet.rs index 282a74fcb9..7651bcc11b 100644 --- a/crates/bdk/tests/wallet.rs +++ b/crates/bdk/tests/wallet.rs @@ -1,23 +1,21 @@ use assert_matches::assert_matches; use bdk::descriptor::calc_checksum; +use bdk::psbt::PsbtUtils; use bdk::signer::{SignOptions, SignerError}; use bdk::wallet::coin_selection::LargestFirstCoinSelection; use bdk::wallet::AddressIndex::*; use bdk::wallet::{AddressIndex, AddressInfo, Balance, Wallet}; -use bdk::Error; -use bdk::FeeRate; -use bdk::KeychainKind; -use bdk_chain::BlockId; -use bdk_chain::ConfirmationTime; -use bdk_chain::COINBASE_MATURITY; +use bdk::{Error, FeeRate, KeychainKind}; +use bdk_chain::{BlockId, ConfirmationTime}; +use bdk_chain::{TotalSentReceived, COINBASE_MATURITY}; use bitcoin::hashes::Hash; -use bitcoin::BlockHash; use bitcoin::Script; use bitcoin::{util::psbt, Network}; use bitcoin::{ Address, EcdsaSighashType, LockTime, OutPoint, PackedLockTime, SchnorrSighashType, Sequence, Transaction, TxIn, TxOut, }; +use bitcoin::{BlockHash, Txid}; use core::str::FromStr; mod common; @@ -84,6 +82,71 @@ fn test_get_funded_wallet_balance() { assert_eq!(wallet.get_balance().confirmed, 50000); } +#[test] +fn test_get_funded_wallet_total_sent_received() { + let (wallet, _) = get_funded_wallet(get_test_wpkh()); + assert_eq!(wallet.get_balance().confirmed, 50000); + let mut tx_amounts: Vec<(Txid, TotalSentReceived)> = wallet + .transactions() + .map(|ct| (ct.node.txid, wallet.total_sent_received(ct.node.tx))) + .collect(); + tx_amounts.sort_by(|a1, a2| a1.0.cmp(&a2.0)); + + assert_eq!(tx_amounts.len(), 2); + assert_matches!( + tx_amounts.get(1), + Some(( + _, + TotalSentReceived { + received: 50_000, + sent: 76_000, + } + )) + ) +} + +#[test] +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)> = wallet + .transactions() + .map(|ct| { + let fee = wallet.calculate_fee(ct.node.tx); + (ct.node.txid, fee) + }) + .collect(); + tx_fee_amounts.sort_by(|a1, a2| a1.0.cmp(&a2.0)); + + assert_eq!(tx_fee_amounts.len(), 2); + assert_matches!( + tx_fee_amounts.get(0), + Some((_, Err(bdk::error::CalculateFeeError::MissingTxOut))) + ); + assert_matches!(tx_fee_amounts.get(1), Some((_, Ok(1000)))) +} + +#[test] +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)> = wallet + .transactions() + .map(|ct| { + let fee_rate = wallet.calculate_fee_rate(ct.node.tx); + (ct.node.txid, fee_rate) + }) + .collect(); + tx_fee_rates.sort_by(|a1, a2| a1.0.cmp(&a2.0)); + + assert_eq!(tx_fee_rates.len(), 2); + assert_matches!( + tx_fee_rates.get(0), + Some((_, Err(bdk::error::CalculateFeeError::MissingTxOut))) + ); + assert_matches!(tx_fee_rates.get(1), Some((_, Ok(_)))) +} + macro_rules! assert_fee_rate { ($psbt:expr, $fees:expr, $fee_rate:expr $( ,@dust_change $( $dust_change:expr )* )* $( ,@add_signature $( $add_signature:expr )* )* ) => ({ let psbt = $psbt.clone(); @@ -191,7 +254,7 @@ fn test_create_tx_custom_version() { builder .add_recipient(addr.script_pubkey(), 25_000) .version(42); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.unsigned_tx.version, 42); } @@ -203,11 +266,11 @@ fn test_create_tx_default_locktime_is_last_sync_height() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); // Since we never synced the wallet we don't have a last_sync_height // we could use to try to prevent fee sniping. We default to 0. - assert_eq!(psbt.unsigned_tx.lock_time.0, 1_000); + assert_eq!(psbt.unsigned_tx.lock_time.0, 2_000); } #[test] @@ -217,7 +280,7 @@ fn test_create_tx_fee_sniping_locktime_last_sync() { let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); // If there's no current_height we're left with using the last sync height assert_eq!( @@ -232,7 +295,7 @@ fn test_create_tx_default_locktime_cltv() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.unsigned_tx.lock_time.0, 100_000); } @@ -246,7 +309,7 @@ fn test_create_tx_custom_locktime() { .add_recipient(addr.script_pubkey(), 25_000) .current_height(630_001) .nlocktime(LockTime::from_height(630_000).unwrap()); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); // When we explicitly specify a nlocktime // we don't try any fee sniping prevention trick @@ -262,7 +325,7 @@ fn test_create_tx_custom_locktime_compatible_with_cltv() { builder .add_recipient(addr.script_pubkey(), 25_000) .nlocktime(LockTime::from_height(630_000).unwrap()); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.unsigned_tx.lock_time.0, 630_000); } @@ -287,7 +350,7 @@ fn test_create_tx_no_rbf_csv() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6)); } @@ -300,7 +363,7 @@ fn test_create_tx_with_default_rbf_csv() { builder .add_recipient(addr.script_pubkey(), 25_000) .enable_rbf(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); // When CSV is enabled it takes precedence over the rbf value (unless forced by the user). // It will be set to the OP_CSV value, in this case 6 assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6)); @@ -326,7 +389,7 @@ fn test_create_tx_no_rbf_cltv() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE)); } @@ -351,7 +414,7 @@ fn test_create_tx_custom_rbf_sequence() { builder .add_recipient(addr.script_pubkey(), 25_000) .enable_rbf_with_sequence(Sequence(0xDEADBEEF)); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xDEADBEEF)); } @@ -362,7 +425,7 @@ fn test_create_tx_default_sequence() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE)); } @@ -387,13 +450,11 @@ fn test_create_tx_drain_wallet_and_drain_to() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); assert_eq!(psbt.unsigned_tx.output.len(), 1); - assert_eq!( - psbt.unsigned_tx.output[0].value, - 50_000 - details.fee.unwrap_or(0) - ); + assert_eq!(psbt.unsigned_tx.output[0].value, 50_000 - fee.unwrap_or(0)); } #[test] @@ -406,7 +467,8 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() { .add_recipient(addr.script_pubkey(), 20_000) .drain_to(drain_addr.script_pubkey()) .drain_wallet(); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); let outputs = psbt.unsigned_tx.output; assert_eq!(outputs.len(), 2); @@ -419,7 +481,7 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() { .find(|x| x.script_pubkey == drain_addr.script_pubkey()) .unwrap(); assert_eq!(main_output.value, 20_000,); - assert_eq!(drain_output.value, 30_000 - details.fee.unwrap_or(0)); + assert_eq!(drain_output.value, 30_000 - fee.unwrap_or(0)); } #[test] @@ -436,13 +498,11 @@ fn test_create_tx_drain_to_and_utxos() { .drain_to(addr.script_pubkey()) .add_utxos(&utxos) .unwrap(); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); assert_eq!(psbt.unsigned_tx.output.len(), 1); - assert_eq!( - psbt.unsigned_tx.output[0].value, - 50_000 - details.fee.unwrap_or(0) - ); + assert_eq!(psbt.unsigned_tx.output[0].value, 50_000 - fee.unwrap_or(0)); } #[test] @@ -461,9 +521,10 @@ fn test_create_tx_default_fee_rate() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); - assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::default(), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::default(), @add_signature); } #[test] @@ -474,9 +535,10 @@ fn test_create_tx_custom_fee_rate() { builder .add_recipient(addr.script_pubkey(), 25_000) .fee_rate(FeeRate::from_sat_per_vb(5.0)); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); - assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature); } #[test] @@ -488,14 +550,12 @@ fn test_create_tx_absolute_fee() { .drain_to(addr.script_pubkey()) .drain_wallet() .fee_absolute(100); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); - assert_eq!(details.fee.unwrap_or(0), 100); + assert_eq!(fee.unwrap_or(0), 100); assert_eq!(psbt.unsigned_tx.output.len(), 1); - assert_eq!( - psbt.unsigned_tx.output[0].value, - 50_000 - details.fee.unwrap_or(0) - ); + assert_eq!(psbt.unsigned_tx.output[0].value, 50_000 - fee.unwrap_or(0)); } #[test] @@ -507,14 +567,12 @@ fn test_create_tx_absolute_zero_fee() { .drain_to(addr.script_pubkey()) .drain_wallet() .fee_absolute(0); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); - assert_eq!(details.fee.unwrap_or(0), 0); + assert_eq!(fee.unwrap_or(0), 0); assert_eq!(psbt.unsigned_tx.output.len(), 1); - assert_eq!( - psbt.unsigned_tx.output[0].value, - 50_000 - details.fee.unwrap_or(0) - ); + assert_eq!(psbt.unsigned_tx.output[0].value, 50_000 - fee.unwrap_or(0)); } #[test] @@ -527,7 +585,7 @@ fn test_create_tx_absolute_high_fee() { .drain_to(addr.script_pubkey()) .drain_wallet() .fee_absolute(60_000); - let (_psbt, _details) = builder.finish().unwrap(); + let _ = builder.finish().unwrap(); } #[test] @@ -540,14 +598,12 @@ fn test_create_tx_add_change() { builder .add_recipient(addr.script_pubkey(), 25_000) .ordering(TxOrdering::Untouched); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); assert_eq!(psbt.unsigned_tx.output.len(), 2); assert_eq!(psbt.unsigned_tx.output[0].value, 25_000); - assert_eq!( - psbt.unsigned_tx.output[1].value, - 25_000 - details.fee.unwrap_or(0) - ); + assert_eq!(psbt.unsigned_tx.output[1].value, 25_000 - fee.unwrap_or(0)); } #[test] @@ -556,11 +612,12 @@ fn test_create_tx_skip_change_dust() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 49_800); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); assert_eq!(psbt.unsigned_tx.output.len(), 1); assert_eq!(psbt.unsigned_tx.output[0].value, 49_800); - assert_eq!(details.fee.unwrap_or(0), 200); + assert_eq!(fee.unwrap_or(0), 200); } #[test] @@ -586,13 +643,11 @@ fn test_create_tx_ordering_respected() { .add_recipient(addr.script_pubkey(), 30_000) .add_recipient(addr.script_pubkey(), 10_000) .ordering(bdk::wallet::tx_builder::TxOrdering::Bip69Lexicographic); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); assert_eq!(psbt.unsigned_tx.output.len(), 3); - assert_eq!( - psbt.unsigned_tx.output[0].value, - 10_000 - details.fee.unwrap_or(0) - ); + assert_eq!(psbt.unsigned_tx.output[0].value, 10_000 - fee.unwrap_or(0)); assert_eq!(psbt.unsigned_tx.output[1].value, 10_000); assert_eq!(psbt.unsigned_tx.output[2].value, 30_000); } @@ -603,7 +658,7 @@ fn test_create_tx_default_sighash() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 30_000); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.inputs[0].sighash_type, None); } @@ -616,7 +671,7 @@ fn test_create_tx_custom_sighash() { builder .add_recipient(addr.script_pubkey(), 30_000) .sighash(bitcoin::EcdsaSighashType::Single.into()); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!( psbt.inputs[0].sighash_type, @@ -633,7 +688,7 @@ fn test_create_tx_input_hd_keypaths() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.inputs[0].bip32_derivation.len(), 1); assert_eq!( @@ -655,7 +710,7 @@ fn test_create_tx_output_hd_keypaths() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.outputs[0].bip32_derivation.len(), 1); let expected_derivation_path = format!("m/44'/0'/0'/0/{}", addr.index); @@ -677,7 +732,7 @@ fn test_create_tx_set_redeem_script_p2sh() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!( psbt.inputs[0].redeem_script, @@ -700,7 +755,7 @@ fn test_create_tx_set_witness_script_p2wsh() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.inputs[0].redeem_script, None); assert_eq!( @@ -723,7 +778,7 @@ fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let script = Script::from( Vec::::from_hex( @@ -743,7 +798,7 @@ fn test_create_tx_non_witness_utxo() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert!(psbt.inputs[0].non_witness_utxo.is_some()); assert!(psbt.inputs[0].witness_utxo.is_none()); @@ -759,7 +814,7 @@ fn test_create_tx_only_witness_utxo() { .drain_to(addr.script_pubkey()) .only_witness_utxo() .drain_wallet(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert!(psbt.inputs[0].non_witness_utxo.is_none()); assert!(psbt.inputs[0].witness_utxo.is_some()); @@ -772,7 +827,7 @@ fn test_create_tx_shwpkh_has_witness_utxo() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert!(psbt.inputs[0].witness_utxo.is_some()); } @@ -784,7 +839,7 @@ fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert!(psbt.inputs[0].non_witness_utxo.is_some()); assert!(psbt.inputs[0].witness_utxo.is_some()); @@ -818,7 +873,8 @@ fn test_create_tx_add_utxo() { vout: 0, }) .unwrap(); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let details = wallet.total_sent_received(&psbt.clone().extract_tx()); assert_eq!( psbt.unsigned_tx.input.len(), @@ -901,7 +957,7 @@ fn test_create_tx_policy_path_no_csv() { builder .add_recipient(addr.script_pubkey(), 30_000) .policy_path(path, KeychainKind::External); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFF)); } @@ -920,7 +976,7 @@ fn test_create_tx_policy_path_use_csv() { builder .add_recipient(addr.script_pubkey(), 30_000) .policy_path(path, KeychainKind::External); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(144)); } @@ -939,7 +995,7 @@ fn test_create_tx_policy_path_ignored_subtree_with_csv() { builder .add_recipient(addr.script_pubkey(), 30_000) .policy_path(path, KeychainKind::External); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE)); } @@ -955,7 +1011,7 @@ fn test_create_tx_global_xpubs_with_origin() { builder .add_recipient(addr.script_pubkey(), 25_000) .add_global_xpubs(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let key = bip32::ExtendedPubKey::from_str("tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3").unwrap(); let fingerprint = bip32::Fingerprint::from_hex("73756c7f").unwrap(); @@ -989,11 +1045,13 @@ fn test_add_foreign_utxo() { .only_witness_utxo() .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction) .unwrap(); - let (mut psbt, details) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); + let details = wallet1.total_sent_received(&psbt.clone().extract_tx()); assert_eq!( details.sent - details.received, - 10_000 + details.fee.unwrap_or(0), + 10_000 + fee.unwrap_or(0), "we should have only net spent ~10_000" ); @@ -1055,8 +1113,8 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() { get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); let utxo2 = wallet2.list_unspent().next().unwrap(); - let tx1 = wallet1.get_tx(txid1, true).unwrap().transaction.unwrap(); - let tx2 = wallet2.get_tx(txid2, true).unwrap().transaction.unwrap(); + let tx1 = wallet1.get_tx(txid1).unwrap().node.tx.clone(); + let tx2 = wallet2.get_tx(txid2).unwrap().node.tx.clone(); let satisfaction_weight = wallet2 .get_descriptor_for_keychain(KeychainKind::External) @@ -1141,9 +1199,9 @@ fn test_add_foreign_utxo_only_witness_utxo() { { let mut builder = builder.clone(); - let tx2 = wallet2.get_tx(txid2, true).unwrap().transaction.unwrap(); + let tx2 = wallet2.get_tx(txid2).unwrap().node.tx; let psbt_input = psbt::Input { - non_witness_utxo: Some(tx2), + non_witness_utxo: Some(tx2.clone()), ..Default::default() }; builder @@ -1191,7 +1249,7 @@ fn test_create_tx_global_xpubs_master_without_origin() { builder .add_recipient(addr.script_pubkey(), 25_000) .add_global_xpubs(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let key = bip32::ExtendedPubKey::from_str("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap(); let fingerprint = bip32::Fingerprint::from_hex("997a323b").unwrap(); @@ -1210,7 +1268,7 @@ fn test_bump_fee_irreplaceable_tx() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let tx = psbt.extract_tx(); let txid = tx.txid(); @@ -1227,7 +1285,7 @@ fn test_bump_fee_confirmed_tx() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let tx = psbt.extract_tx(); let txid = tx.txid(); @@ -1254,7 +1312,7 @@ fn test_bump_fee_low_fee_rate() { builder .add_recipient(addr.script_pubkey(), 25_000) .enable_rbf(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let tx = psbt.extract_tx(); let txid = tx.txid(); @@ -1277,7 +1335,7 @@ fn test_bump_fee_low_abs() { builder .add_recipient(addr.script_pubkey(), 25_000) .enable_rbf(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let tx = psbt.extract_tx(); let txid = tx.txid(); @@ -1300,7 +1358,7 @@ fn test_bump_fee_zero_abs() { builder .add_recipient(addr.script_pubkey(), 25_000) .enable_rbf(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let tx = psbt.extract_tx(); let txid = tx.txid(); @@ -1321,7 +1379,10 @@ fn test_bump_fee_reduce_change() { builder .add_recipient(addr.script_pubkey(), 25_000) .enable_rbf(); - let (psbt, original_details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let original_details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let original_fee = psbt.fee_amount(); + let tx = psbt.extract_tx(); let txid = tx.txid(); wallet @@ -1330,14 +1391,16 @@ fn test_bump_fee_reduce_change() { let mut builder = wallet.build_fee_bump(txid).unwrap(); builder.fee_rate(FeeRate::from_sat_per_vb(2.5)).enable_rbf(); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let fee = psbt.fee_amount(); assert_eq!(details.sent, original_details.sent); assert_eq!( - details.received + details.fee.unwrap_or(0), - original_details.received + original_details.fee.unwrap_or(0) + details.received + fee.unwrap_or(0), + original_details.received + original_fee.unwrap_or(0) ); - assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0)); + assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0)); let tx = &psbt.unsigned_tx; assert_eq!(tx.output.len(), 2); @@ -1358,23 +1421,25 @@ fn test_bump_fee_reduce_change() { details.received ); - assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature); let mut builder = wallet.build_fee_bump(txid).unwrap(); builder.fee_absolute(200); builder.enable_rbf(); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let fee = psbt.fee_amount(); assert_eq!(details.sent, original_details.sent); assert_eq!( - details.received + details.fee.unwrap_or(0), - original_details.received + original_details.fee.unwrap_or(0) + details.received + fee.unwrap_or(0), + original_details.received + original_fee.unwrap_or(0) ); assert!( - details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0), + fee.unwrap_or(0) > original_fee.unwrap_or(0), "{} > {}", - details.fee.unwrap_or(0), - original_details.fee.unwrap_or(0) + fee.unwrap_or(0), + original_fee.unwrap_or(0) ); let tx = &psbt.unsigned_tx; @@ -1396,7 +1461,7 @@ fn test_bump_fee_reduce_change() { details.received ); - assert_eq!(details.fee.unwrap_or(0), 200); + assert_eq!(fee.unwrap_or(0), 200); } #[test] @@ -1408,8 +1473,10 @@ fn test_bump_fee_reduce_single_recipient() { .drain_to(addr.script_pubkey()) .drain_wallet() .enable_rbf(); - let (psbt, original_details) = builder.finish().unwrap(); - let tx = psbt.extract_tx(); + let psbt = builder.finish().unwrap(); + let tx = psbt.clone().extract_tx(); + let original_details = wallet.total_sent_received(&tx); + let original_fee = psbt.fee_amount(); let txid = tx.txid(); wallet .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 }) @@ -1420,16 +1487,18 @@ fn test_bump_fee_reduce_single_recipient() { .fee_rate(FeeRate::from_sat_per_vb(2.5)) .allow_shrinking(addr.script_pubkey()) .unwrap(); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let fee = psbt.fee_amount(); assert_eq!(details.sent, original_details.sent); - assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0)); + assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0)); let tx = &psbt.unsigned_tx; assert_eq!(tx.output.len(), 1); - assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent); + assert_eq!(tx.output[0].value + fee.unwrap_or(0), details.sent); - assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature); } #[test] @@ -1441,8 +1510,10 @@ fn test_bump_fee_absolute_reduce_single_recipient() { .drain_to(addr.script_pubkey()) .drain_wallet() .enable_rbf(); - let (psbt, original_details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let original_fee = psbt.fee_amount(); let tx = psbt.extract_tx(); + let original_details = wallet.total_sent_received(&tx); let txid = tx.txid(); wallet .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 }) @@ -1453,16 +1524,18 @@ fn test_bump_fee_absolute_reduce_single_recipient() { .allow_shrinking(addr.script_pubkey()) .unwrap() .fee_absolute(300); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let tx = &psbt.unsigned_tx; + let details = wallet.total_sent_received(tx); + let fee = psbt.fee_amount(); assert_eq!(details.sent, original_details.sent); - assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0)); + assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0)); - let tx = &psbt.unsigned_tx; assert_eq!(tx.output.len(), 1); - assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent); + assert_eq!(tx.output[0].value + fee.unwrap_or(0), details.sent); - assert_eq!(details.fee.unwrap_or(0), 300); + assert_eq!(fee.unwrap_or(0), 300); } #[test] @@ -1499,8 +1572,10 @@ fn test_bump_fee_drain_wallet() { .unwrap() .manually_selected_only() .enable_rbf(); - let (psbt, original_details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let tx = psbt.extract_tx(); + let original_details = wallet.total_sent_received(&tx); + let txid = tx.txid(); wallet .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 }) @@ -1515,7 +1590,8 @@ fn test_bump_fee_drain_wallet() { .allow_shrinking(addr.script_pubkey()) .unwrap() .fee_rate(FeeRate::from_sat_per_vb(5.0)); - let (_, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let details = wallet.total_sent_received(&psbt.extract_tx()); assert_eq!(details.sent, 75_000); } @@ -1562,8 +1638,9 @@ fn test_bump_fee_remove_output_manually_selected_only() { .unwrap() .manually_selected_only() .enable_rbf(); - let (psbt, original_details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let tx = psbt.extract_tx(); + let original_details = wallet.total_sent_received(&tx); let txid = tx.txid(); wallet .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 }) @@ -1603,8 +1680,9 @@ fn test_bump_fee_add_input() { builder .add_recipient(addr.script_pubkey(), 45_000) .enable_rbf(); - let (psbt, original_details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let tx = psbt.extract_tx(); + let original_details = wallet.total_sent_received(&tx); let txid = tx.txid(); wallet .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 }) @@ -1612,10 +1690,11 @@ fn test_bump_fee_add_input() { let mut builder = wallet.build_fee_bump(txid).unwrap(); builder.fee_rate(FeeRate::from_sat_per_vb(50.0)); - let (psbt, details) = builder.finish().unwrap(); - + let psbt = builder.finish().unwrap(); + let details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let fee = psbt.fee_amount(); assert_eq!(details.sent, original_details.sent + 25_000); - assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000); + assert_eq!(fee.unwrap_or(0) + details.received, 30_000); let tx = &psbt.unsigned_tx; assert_eq!(tx.input.len(), 2); @@ -1637,7 +1716,7 @@ fn test_bump_fee_add_input() { details.received ); - assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @add_signature); } #[test] @@ -1649,8 +1728,9 @@ fn test_bump_fee_absolute_add_input() { builder .add_recipient(addr.script_pubkey(), 45_000) .enable_rbf(); - let (psbt, original_details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let tx = psbt.extract_tx(); + let original_details = wallet.total_sent_received(&tx); let txid = tx.txid(); wallet .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 }) @@ -1658,10 +1738,12 @@ fn test_bump_fee_absolute_add_input() { let mut builder = wallet.build_fee_bump(txid).unwrap(); builder.fee_absolute(6_000); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let fee = psbt.fee_amount(); assert_eq!(details.sent, original_details.sent + 25_000); - assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000); + assert_eq!(fee.unwrap_or(0) + details.received, 30_000); let tx = &psbt.unsigned_tx; assert_eq!(tx.input.len(), 2); @@ -1683,7 +1765,7 @@ fn test_bump_fee_absolute_add_input() { details.received ); - assert_eq!(details.fee.unwrap_or(0), 6_000); + assert_eq!(fee.unwrap_or(0), 6_000); } #[test] @@ -1700,7 +1782,9 @@ fn test_bump_fee_no_change_add_input_and_change() { .unwrap() .manually_selected_only() .enable_rbf(); - let (psbt, original_details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let original_details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let original_fee = psbt.fee_amount(); let tx = psbt.extract_tx(); let txid = tx.txid(); @@ -1712,13 +1796,15 @@ fn test_bump_fee_no_change_add_input_and_change() { // extra input and a change output, and leave the original output untouched let mut builder = wallet.build_fee_bump(txid).unwrap(); builder.fee_rate(FeeRate::from_sat_per_vb(50.0)); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let fee = psbt.fee_amount(); - let original_send_all_amount = original_details.sent - original_details.fee.unwrap_or(0); + let original_send_all_amount = original_details.sent - original_fee.unwrap_or(0); assert_eq!(details.sent, original_details.sent + 50_000); assert_eq!( details.received, - 75_000 - original_send_all_amount - details.fee.unwrap_or(0) + 75_000 - original_send_all_amount - fee.unwrap_or(0) ); let tx = &psbt.unsigned_tx; @@ -1738,10 +1824,10 @@ fn test_bump_fee_no_change_add_input_and_change() { .find(|txout| txout.script_pubkey != addr.script_pubkey()) .unwrap() .value, - 75_000 - original_send_all_amount - details.fee.unwrap_or(0) + 75_000 - original_send_all_amount - fee.unwrap_or(0) ); - assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @add_signature); } #[test] @@ -1753,7 +1839,10 @@ fn test_bump_fee_add_input_change_dust() { builder .add_recipient(addr.script_pubkey(), 45_000) .enable_rbf(); - let (psbt, original_details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let original_details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let original_fee = psbt.fee_amount(); + let mut tx = psbt.extract_tx(); for txin in &mut tx.input { txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // to get realisitc weight @@ -1781,15 +1870,14 @@ fn test_bump_fee_add_input_change_dust() { // We use epsilon here to avoid asking for a slightly too high feerate let fee_abs = 50_000 + 25_000 - 45_000 - 10; builder.fee_rate(FeeRate::from_wu(fee_abs, new_tx_weight)); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let fee = psbt.fee_amount(); - assert_eq!( - original_details.received, - 5_000 - original_details.fee.unwrap_or(0) - ); + assert_eq!(original_details.received, 5_000 - original_fee.unwrap_or(0)); assert_eq!(details.sent, original_details.sent + 25_000); - assert_eq!(details.fee.unwrap_or(0), 30_000); + assert_eq!(fee.unwrap_or(0), 30_000); assert_eq!(details.received, 0); let tx = &psbt.unsigned_tx; @@ -1804,7 +1892,7 @@ fn test_bump_fee_add_input_change_dust() { 45_000 ); - assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(140.0), @dust_change, @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(140.0), @dust_change, @add_signature); } #[test] @@ -1817,8 +1905,9 @@ fn test_bump_fee_force_add_input() { builder .add_recipient(addr.script_pubkey(), 45_000) .enable_rbf(); - let (psbt, original_details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let mut tx = psbt.extract_tx(); + let original_details = wallet.total_sent_received(&tx); let txid = tx.txid(); for txin in &mut tx.input { txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature @@ -1833,10 +1922,12 @@ fn test_bump_fee_force_add_input() { .add_utxo(incoming_op) .unwrap() .fee_rate(FeeRate::from_sat_per_vb(5.0)); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let fee = psbt.fee_amount(); assert_eq!(details.sent, original_details.sent + 25_000); - assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000); + assert_eq!(fee.unwrap_or(0) + details.received, 30_000); let tx = &psbt.unsigned_tx; assert_eq!(tx.input.len(), 2); @@ -1858,7 +1949,7 @@ fn test_bump_fee_force_add_input() { details.received ); - assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature); } #[test] @@ -1871,8 +1962,9 @@ fn test_bump_fee_absolute_force_add_input() { builder .add_recipient(addr.script_pubkey(), 45_000) .enable_rbf(); - let (psbt, original_details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let mut tx = psbt.extract_tx(); + let original_details = wallet.total_sent_received(&tx); let txid = tx.txid(); // skip saving the new utxos, we know they can't be used anyways for txin in &mut tx.input { @@ -1886,10 +1978,12 @@ fn test_bump_fee_absolute_force_add_input() { // the addition of an extra input with `add_utxo()` let mut builder = wallet.build_fee_bump(txid).unwrap(); builder.add_utxo(incoming_op).unwrap().fee_absolute(250); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let details = wallet.total_sent_received(&psbt.clone().extract_tx()); + let fee = psbt.fee_amount(); assert_eq!(details.sent, original_details.sent + 25_000); - assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000); + assert_eq!(fee.unwrap_or(0) + details.received, 30_000); let tx = &psbt.unsigned_tx; assert_eq!(tx.input.len(), 2); @@ -1911,7 +2005,7 @@ fn test_bump_fee_absolute_force_add_input() { details.received ); - assert_eq!(details.fee.unwrap_or(0), 250); + assert_eq!(fee.unwrap_or(0), 250); } #[test] @@ -1930,7 +2024,7 @@ fn test_bump_fee_unconfirmed_inputs_only() { .drain_wallet() .drain_to(addr.script_pubkey()) .enable_rbf(); - let (psbt, __details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); // Now we receive one transaction with 0 confirmations. We won't be able to use that for // fee bumping, as it's still unconfirmed! receive_output( @@ -1968,7 +2062,7 @@ fn test_bump_fee_unconfirmed_input() { .drain_wallet() .drain_to(addr.script_pubkey()) .enable_rbf(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let mut tx = psbt.extract_tx(); let txid = tx.txid(); for txin in &mut tx.input { @@ -2008,10 +2102,11 @@ fn test_fee_amount_negative_drain_val() { .unwrap() .enable_rbf() .fee_rate(fee_rate); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); - assert!(psbt.inputs.len() == 1); - assert_fee_rate!(psbt, details.fee.unwrap_or(0), fee_rate, @add_signature); + assert_eq!(psbt.inputs.len(), 1); + assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate, @add_signature); } #[test] @@ -2020,7 +2115,7 @@ fn test_sign_single_xprv() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized); @@ -2035,7 +2130,7 @@ fn test_sign_single_xprv_with_master_fingerprint_and_path() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized); @@ -2050,7 +2145,7 @@ fn test_sign_single_xprv_bip44_path() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized); @@ -2065,7 +2160,7 @@ fn test_sign_single_xprv_sh_wpkh() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized); @@ -2081,7 +2176,7 @@ fn test_sign_single_wif() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); assert!(finalized); @@ -2096,7 +2191,7 @@ fn test_sign_single_xprv_no_hd_keypaths() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); psbt.inputs[0].bip32_derivation.clear(); assert_eq!(psbt.inputs[0].bip32_derivation.len(), 0); @@ -2116,7 +2211,7 @@ fn test_include_output_redeem_witness_script() { builder .add_recipient(addr.script_pubkey(), 45_000) .include_output_redeem_witness_script(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); // p2sh-p2wsh transaction should contain both witness and redeem scripts assert!(psbt @@ -2133,7 +2228,7 @@ fn test_signing_only_one_of_multiple_inputs() { builder .add_recipient(addr.script_pubkey(), 45_000) .include_output_redeem_witness_script(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); // add another input to the psbt that is at least passable. let dud_input = bitcoin::util::psbt::Input { @@ -2177,7 +2272,7 @@ fn test_remove_partial_sigs_after_finalize_sign_option() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap().0; + let mut psbt = builder.finish().unwrap(); assert!(wallet .sign( @@ -2207,7 +2302,7 @@ fn test_try_finalize_sign_option() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap().0; + let mut psbt = builder.finish().unwrap(); let finalized = wallet .sign( @@ -2244,7 +2339,7 @@ fn test_sign_nonstandard_sighash() { .drain_to(addr.script_pubkey()) .sighash(sighash.into()) .drain_wallet(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let result = wallet.sign(&mut psbt, Default::default()); assert!( @@ -2500,7 +2595,7 @@ fn test_taproot_psbt_populate_tap_key_origins() { let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!( psbt.inputs[0] @@ -2541,7 +2636,7 @@ fn test_taproot_psbt_populate_tap_key_origins_repeated_key() { builder .add_recipient(addr.script_pubkey(), 25_000) .policy_path(path, KeychainKind::External); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); let mut input_key_origins = psbt.inputs[0] .tap_key_origins @@ -2603,7 +2698,7 @@ fn test_taproot_psbt_input_tap_tree() { let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); assert_eq!( psbt.inputs[0].tap_merkle_root, @@ -2645,7 +2740,7 @@ fn test_taproot_sign_missing_witness_utxo() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let witness_utxo = psbt.inputs[0].witness_utxo.take(); let result = wallet.sign( @@ -2685,10 +2780,10 @@ fn test_taproot_sign_using_non_witness_utxo() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); psbt.inputs[0].witness_utxo = None; - psbt.inputs[0].non_witness_utxo = wallet.get_tx(prev_txid, true).unwrap().transaction; + psbt.inputs[0].non_witness_utxo = Some(wallet.get_tx(prev_txid).unwrap().node.tx.clone()); assert!( psbt.inputs[0].non_witness_utxo.is_some(), "Previous tx should be present in the database" @@ -2725,11 +2820,13 @@ fn test_taproot_foreign_utxo() { .add_recipient(addr.script_pubkey(), 60_000) .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction) .unwrap(); - let (psbt, details) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); + let details = wallet1.total_sent_received(&psbt.clone().extract_tx()); + let fee = psbt.fee_amount(); assert_eq!( details.sent - details.received, - 10_000 + details.fee.unwrap_or(0), + 10_000 + fee.unwrap_or(0), "we should have only net spent ~10_000" ); @@ -2747,7 +2844,7 @@ fn test_spend_from_wallet(mut wallet: Wallet) { let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); assert!( wallet.sign(&mut psbt, Default::default()).unwrap(), @@ -2771,7 +2868,7 @@ fn test_taproot_no_key_spend() { let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); assert!( wallet @@ -2806,7 +2903,7 @@ fn test_taproot_script_spend_sign_all_leaves() { let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); assert!( wallet @@ -2837,7 +2934,7 @@ fn test_taproot_script_spend_sign_include_some_leaves() { let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let mut script_leaves: Vec<_> = psbt.inputs[0] .tap_scripts .clone() @@ -2877,7 +2974,7 @@ fn test_taproot_script_spend_sign_exclude_some_leaves() { let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let mut script_leaves: Vec<_> = psbt.inputs[0] .tap_scripts .clone() @@ -2915,7 +3012,7 @@ fn test_taproot_script_spend_sign_no_leaves() { let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); wallet .sign( @@ -2938,7 +3035,7 @@ fn test_taproot_sign_derive_index_from_psbt() { let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 25_000); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); // re-create the wallet with an empty db let wallet_empty = @@ -2961,7 +3058,7 @@ fn test_taproot_sign_explicit_sighash_all() { .drain_to(addr.script_pubkey()) .sighash(SchnorrSighashType::All.into()) .drain_wallet(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let result = wallet.sign(&mut psbt, Default::default()); assert!( @@ -2981,7 +3078,7 @@ fn test_taproot_sign_non_default_sighash() { .drain_to(addr.script_pubkey()) .sighash(sighash.into()) .drain_wallet(); - let (mut psbt, _) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); let witness_utxo = psbt.inputs[0].witness_utxo.take(); @@ -3174,7 +3271,8 @@ fn test_fee_rate_sign_no_grinding_high_r() { .drain_wallet() .fee_rate(fee_rate) .add_data(&data); - let (mut psbt, details) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); let (op_return_vout, _) = psbt .unsigned_tx .output @@ -3220,7 +3318,7 @@ fn test_fee_rate_sign_no_grinding_high_r() { ) .unwrap(); // ...and checking that everything is fine - assert_fee_rate!(psbt, details.fee.unwrap_or(0), fee_rate); + assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate); } #[test] @@ -3237,7 +3335,8 @@ fn test_fee_rate_sign_grinding_low_r() { .drain_to(addr.script_pubkey()) .drain_wallet() .fee_rate(fee_rate); - let (mut psbt, details) = builder.finish().unwrap(); + let mut psbt = builder.finish().unwrap(); + let fee = psbt.fee_amount(); wallet .sign( @@ -3253,7 +3352,7 @@ fn test_fee_rate_sign_grinding_low_r() { let key = psbt.inputs[0].partial_sigs.keys().next().unwrap(); let sig_len = psbt.inputs[0].partial_sigs[key].sig.serialize_der().len(); assert_eq!(sig_len, 70); - assert_fee_rate!(psbt, details.fee.unwrap_or(0), fee_rate); + assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate); } // #[cfg(feature = "test-hardware-signer")] @@ -3315,7 +3414,7 @@ fn test_tx_cancellation() { let mut builder = $wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), 10_000); - let (psbt, _) = builder.finish().unwrap(); + let psbt = builder.finish().unwrap(); psbt }}; diff --git a/crates/chain/src/spk_txout_index.rs b/crates/chain/src/spk_txout_index.rs index 31fd7883c6..ce7d9a965d 100644 --- a/crates/chain/src/spk_txout_index.rs +++ b/crates/chain/src/spk_txout_index.rs @@ -90,6 +90,15 @@ macro_rules! scan_txout { }}; } +/// The total sent and received amounts for a `Transaction`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct TotalSentReceived { + /// amount sent, in sats + pub sent: u64, + /// amount received, in sats + pub received: u64, +} + impl SpkTxOutIndex { /// Scans an object containing many txouts. /// @@ -334,4 +343,33 @@ impl SpkTxOutIndex { .any(|output| self.spk_indices.contains_key(&output.script_pubkey)); input_matches || output_matches } + + /// Return `TotalSentReceived` for a `Transaction` in relation to the `SpkTxOutIndex`. + pub fn total_sent_received(&self, tx: &Transaction) -> TotalSentReceived { + let received = tx + .output + .iter() + .map(|txout| { + if self.index_of_spk(&txout.script_pubkey).is_some() { + txout.value + } else { + 0 + } + }) + .sum(); + + let sent = tx + .input + .iter() + .map(|txin| { + if let Some((_, txout)) = self.txout(txin.previous_output) { + txout.value + } else { + 0 + } + }) + .sum(); + + TotalSentReceived { sent, received } + } } diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index db80f106d1..a60150e2ae 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -81,7 +81,7 @@ fn main() -> Result<(), Box> { .add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT) .enable_rbf(); - let (mut psbt, _) = tx_builder.finish()?; + let mut psbt = tx_builder.finish()?; let finalized = wallet.sign(&mut psbt, SignOptions::default())?; assert!(finalized); diff --git a/example-crates/wallet_esplora/src/main.rs b/example-crates/wallet_esplora/src/main.rs index 119d9cbd79..a1c17986ee 100644 --- a/example-crates/wallet_esplora/src/main.rs +++ b/example-crates/wallet_esplora/src/main.rs @@ -82,7 +82,7 @@ fn main() -> Result<(), Box> { .add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT) .enable_rbf(); - let (mut psbt, _) = tx_builder.finish()?; + let mut psbt = tx_builder.finish()?; let finalized = wallet.sign(&mut psbt, SignOptions::default())?; assert!(finalized); diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index 7cb218ec24..7aae79b8fd 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -85,7 +85,7 @@ async fn main() -> Result<(), Box> { .add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT) .enable_rbf(); - let (mut psbt, _) = tx_builder.finish()?; + let mut psbt = tx_builder.finish()?; let finalized = wallet.sign(&mut psbt, SignOptions::default())?; assert!(finalized);