From 1cd979c8c609a45b935db684e2290414abcfc2b4 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 21:59:38 -0300 Subject: [PATCH 01/15] test: add `get_test_pkh` --- wallet/src/test_utils.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wallet/src/test_utils.rs b/wallet/src/test_utils.rs index 7e1778fa..fd0d143c 100644 --- a/wallet/src/test_utils.rs +++ b/wallet/src/test_utils.rs @@ -134,6 +134,11 @@ pub fn get_funded_wallet_wpkh() -> (Wallet, Txid) { get_funded_wallet(desc, change_desc) } +/// `pkh` single key descriptor +pub fn get_test_pkh() -> &'static str { + "pkh(cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMm)" +} + /// `wpkh` single key descriptor pub fn get_test_wpkh() -> &'static str { "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)" From cc64d72d5d1122185812dc91ee7bdf01914d6aef Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 22:12:57 -0300 Subject: [PATCH 02/15] fix: make change descriptor optional on `get_funded_wallet()` --- wallet/src/test_utils.rs | 6 +++--- wallet/tests/psbt.rs | 4 ++-- wallet/tests/wallet.rs | 14 ++++++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/wallet/src/test_utils.rs b/wallet/src/test_utils.rs index fd0d143c..3ca727f0 100644 --- a/wallet/src/test_utils.rs +++ b/wallet/src/test_utils.rs @@ -17,8 +17,8 @@ use crate::{KeychainKind, Update, Wallet}; /// The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000 /// to a foreign address and one returning 50_000 back to the wallet. The remaining 1000 /// sats are the transaction fee. -pub fn get_funded_wallet(descriptor: &str, change_descriptor: &str) -> (Wallet, Txid) { - new_funded_wallet(descriptor, Some(change_descriptor)) +pub fn get_funded_wallet(descriptor: &str, change_descriptor: Option<&str>) -> (Wallet, Txid) { + new_funded_wallet(descriptor, change_descriptor) } fn new_funded_wallet(descriptor: &str, change_descriptor: Option<&str>) -> (Wallet, Txid) { @@ -131,7 +131,7 @@ pub fn get_funded_wallet_single(descriptor: &str) -> (Wallet, Txid) { /// Get funded segwit wallet pub fn get_funded_wallet_wpkh() -> (Wallet, Txid) { let (desc, change_desc) = get_test_wpkh_and_change_desc(); - get_funded_wallet(desc, change_desc) + get_funded_wallet(desc, Some(change_desc)) } /// `pkh` single key descriptor diff --git a/wallet/tests/psbt.rs b/wallet/tests/psbt.rs index a4d17493..081202b1 100644 --- a/wallet/tests/psbt.rs +++ b/wallet/tests/psbt.rs @@ -143,7 +143,7 @@ fn test_psbt_fee_rate_with_missing_txout() { let desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)"; let change_desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/1)"; - let (mut pkh_wallet, _) = get_funded_wallet(desc, change_desc); + let (mut pkh_wallet, _) = get_funded_wallet(desc, Some(change_desc)); let addr = pkh_wallet.peek_address(KeychainKind::External, 0); let mut builder = pkh_wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); @@ -172,7 +172,7 @@ fn test_psbt_multiple_internalkey_signers() { let keypair = Keypair::from_secret_key(&secp, &prv.inner); let change_desc = "tr(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; - let (mut wallet, _) = get_funded_wallet(&desc, change_desc); + let (mut wallet, _) = get_funded_wallet(&desc, Some(change_desc)); let to_spend = wallet.balance().total(); let send_to = wallet.peek_address(KeychainKind::External, 0); let mut builder = wallet.build_tx(); diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index 35cbd85d..fce022a7 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -2798,7 +2798,7 @@ fn test_sign_single_xprv_no_hd_keypaths() { fn test_include_output_redeem_witness_script() { let desc = get_test_wpkh(); let change_desc = "sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))"; - let (mut wallet, _) = get_funded_wallet(desc, change_desc); + let (mut wallet, _) = get_funded_wallet(desc, Some(change_desc)); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); @@ -3288,7 +3288,7 @@ fn test_get_address_no_reuse() { #[test] fn test_taproot_psbt_populate_tap_key_origins() { let (desc, change_desc) = get_test_tr_single_sig_xprv_and_change_desc(); - let (mut wallet, _) = get_funded_wallet(desc, change_desc); + let (mut wallet, _) = get_funded_wallet(desc, Some(change_desc)); let addr = wallet.reveal_next_address(KeychainKind::External); let mut builder = wallet.build_tx(); @@ -3323,7 +3323,8 @@ fn test_taproot_psbt_populate_tap_key_origins() { #[test] fn test_taproot_psbt_populate_tap_key_origins_repeated_key() { - let (mut wallet, _) = get_funded_wallet(get_test_tr_repeated_key(), get_test_tr_single_sig()); + let (mut wallet, _) = + get_funded_wallet(get_test_tr_repeated_key(), Some(get_test_tr_single_sig())); let addr = wallet.reveal_next_address(KeychainKind::External); let path = vec![("rn4nre9c".to_string(), vec![0])] @@ -4118,7 +4119,7 @@ fn test_keychains_with_overlapping_spks() { let wildcard_keychain = "wpkh(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)"; let non_wildcard_keychain = "wpkh(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/1)"; - let (mut wallet, _) = get_funded_wallet(wildcard_keychain, non_wildcard_keychain); + let (mut wallet, _) = get_funded_wallet(wildcard_keychain, Some(non_wildcard_keychain)); assert_eq!(wallet.balance().confirmed, Amount::from_sat(50000)); let addr = wallet @@ -4154,7 +4155,7 @@ fn test_tx_cancellation() { }}; } - let (mut wallet, _) = get_funded_wallet(get_test_wpkh(), get_test_tr_single_sig_xprv()); + let (mut wallet, _) = get_funded_wallet(get_test_wpkh(), Some(get_test_tr_single_sig_xprv())); let psbt1 = new_tx!(wallet); let change_derivation_1 = psbt1 @@ -4280,7 +4281,8 @@ fn test_wallet_transactions_relevant() { // add not relevant transaction to test wallet let (other_external_desc, other_internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); - let (other_wallet, other_txid) = get_funded_wallet(other_internal_desc, other_external_desc); + let (other_wallet, other_txid) = + get_funded_wallet(other_internal_desc, Some(other_external_desc)); let test_wallet_update = Update { tx_update: other_wallet.tx_graph().clone().into(), ..Default::default() From eb66ef408082823470a1f83194702f56c10f4d88 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 22:22:47 -0300 Subject: [PATCH 03/15] test: add `test_legacy_get_funded_wallet_tx_fee_rate()` --- wallet/tests/wallet.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index fce022a7..04efc134 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -439,6 +439,26 @@ fn test_get_funded_wallet_tx_fee_rate() { assert_eq!(tx_fee_rate.to_sat_per_vb_ceil(), 9); } +#[test] +fn test_legacy_get_funded_wallet_tx_fee_rate() { + let (wallet, txid) = get_funded_wallet(get_test_pkh(), None); + + let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; + let tx_fee_rate = wallet + .calculate_fee_rate(&tx) + .expect("transaction fee rate"); + + // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000 + // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000 + // sats are the transaction fee. + + // tx weight = 464 wu, as vbytes = (464)/4 = 116 + // fee rate (sats per kwu) = fee / weight = 1000sat / 0.464kwu = 2155 + // fee rate (sats per vbyte ceil) = fee / kwu = 1000 / 116 = 8.621 + assert_eq!(tx_fee_rate.to_sat_per_kwu(), 2155); + assert_eq!(tx_fee_rate.to_sat_per_vb_ceil(), 9); +} + #[test] fn test_list_output() { let (wallet, txid) = get_funded_wallet_wpkh(); From 5d3b86404250cde7288db8c525e29d48f1636163 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 23:12:28 -0300 Subject: [PATCH 04/15] fix: make `assert_fee_rate!` work with legacy transactions --- wallet/tests/wallet.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index 04efc134..c93ae28a 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -490,7 +490,7 @@ macro_rules! assert_fee_rate { } )* - #[allow(unused_mut)] + #[allow(unused_mut)] #[allow(unused_assignments)] let mut dust_change = false; $( @@ -498,10 +498,20 @@ macro_rules! assert_fee_rate { dust_change = true; )* - let fee_amount = psbt + let fee_amount = psbt .inputs .iter() - .fold(Amount::ZERO, |acc, i| acc + i.witness_utxo.as_ref().unwrap().value) + .enumerate() + .fold(Amount::ZERO, |acc, (idx, input)| { + if let Some(utxo) = input.witness_utxo.as_ref() { + acc + utxo.value + } else if let Some(prev_tx) = input.non_witness_utxo.as_ref() { + let vout = psbt.unsigned_tx.input[idx].previous_output.vout as usize; + acc + prev_tx.output[vout].value + } else { + panic!("this weird transaction has neither a witness or a non-witness UTXO!"); + } + }) - psbt .unsigned_tx .output @@ -517,8 +527,14 @@ macro_rules! assert_fee_rate { .unwrap() .to_sat_per_kwu(); + let is_segwit = psbt.inputs.iter().any(|input| input.witness_utxo.is_some()); + if !dust_change { - assert!(tx_fee_rate >= fee_rate && tx_fee_rate - fee_rate < half_default, "Expected fee rate of {:?}, the tx has {:?}", fee_rate, tx_fee_rate); + if is_segwit { + assert!(tx_fee_rate >= fee_rate && tx_fee_rate - fee_rate < half_default, "Expected fee rate of {:?}, the tx has {:?}", fee_rate, tx_fee_rate); + } else { + assert!(tx_fee_rate >= fee_rate, "Expected fee rate of {:?}, the tx has {:?}", fee_rate, tx_fee_rate); + } } else { assert!(tx_fee_rate >= fee_rate, "Expected fee rate of at least {:?}, the tx has {:?}", fee_rate, tx_fee_rate); } From 2925a400ab8a6c41343ac0f5c23c7787fe4a39cb Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 23:13:31 -0300 Subject: [PATCH 05/15] test: add `test_legacy_create_tx_custom_fee_rate()` --- wallet/tests/wallet.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index c93ae28a..ab487672 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -928,6 +928,20 @@ fn test_create_tx_custom_fee_rate() { assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), FeeRate::from_sat_per_vb_unchecked(5), @add_signature); } +#[test] +fn test_legacy_create_tx_custom_fee_rate() { + let (mut wallet, _) = get_funded_wallet(get_test_pkh(), None); + let addr = wallet.next_unused_address(KeychainKind::External); + let mut builder = wallet.build_tx(); + builder + .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) + .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); + let psbt = builder.finish().unwrap(); + let fee = check_fee!(wallet, psbt); + + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), FeeRate::from_sat_per_vb_unchecked(5), @add_signature); +} + #[test] fn test_create_tx_absolute_fee() { let (mut wallet, _) = get_funded_wallet_wpkh(); From 35207cdd0f3d1cdf1edb0e9e9926ce510d913082 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 23:16:35 -0300 Subject: [PATCH 06/15] test: add `test_legacy_create_tx_absolute_fee()` --- wallet/tests/wallet.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index ab487672..a720a8cd 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -962,6 +962,26 @@ fn test_create_tx_absolute_fee() { ); } +#[test] +fn test_legacy_create_tx_absolute_fee() { + let (mut wallet, _) = get_funded_wallet(get_test_pkh(), None); + let addr = wallet.next_unused_address(KeychainKind::External); + let mut builder = wallet.build_tx(); + builder + .drain_to(addr.script_pubkey()) + .drain_wallet() + .fee_absolute(Amount::from_sat(100)); + let psbt = builder.finish().unwrap(); + let fee = check_fee!(wallet, psbt); + + assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::from_sat(100)); + assert_eq!(psbt.unsigned_tx.output.len(), 1); + assert_eq!( + psbt.unsigned_tx.output[0].value, + Amount::from_sat(50_000) - fee.unwrap_or(Amount::ZERO) + ); +} + #[test] fn test_create_tx_absolute_zero_fee() { let (mut wallet, _) = get_funded_wallet_wpkh(); From 634189436c50591e893c5bfe6e1aadd9e0498b74 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 23:24:32 -0300 Subject: [PATCH 07/15] test: add `test_legacy_create_tx_absolute_zero_fee()` --- wallet/tests/wallet.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index a720a8cd..df8ee138 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -994,11 +994,35 @@ fn test_create_tx_absolute_zero_fee() { let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); - assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::ZERO); + // we can't `unwrap_or(Amount::ZERO)` because the + // test would succeed if there was an error with `fee` + assert_eq!(fee.unwrap_or(Amount::from_sat(21)), Amount::ZERO); assert_eq!(psbt.unsigned_tx.output.len(), 1); assert_eq!( psbt.unsigned_tx.output[0].value, - Amount::from_sat(50_000) - fee.unwrap_or(Amount::ZERO) + Amount::from_sat(50_000) - fee.unwrap_or(Amount::from_sat(21)) + ); +} + +#[test] +fn test_legacy_create_tx_absolute_zero_fee() { + let (mut wallet, _) = get_funded_wallet(get_test_pkh(), None); + let addr = wallet.next_unused_address(KeychainKind::External); + let mut builder = wallet.build_tx(); + builder + .drain_to(addr.script_pubkey()) + .drain_wallet() + .fee_absolute(Amount::ZERO); + let psbt = builder.finish().unwrap(); + let fee = check_fee!(wallet, psbt); + + // we can't `unwrap_or(Amount::ZERO)` because the + // test would succeed if there was an error with `fee` + assert_eq!(fee.unwrap_or(Amount::from_sat(21)), Amount::ZERO); + assert_eq!(psbt.unsigned_tx.output.len(), 1); + assert_eq!( + psbt.unsigned_tx.output[0].value, + Amount::from_sat(50_000) - fee.unwrap_or(Amount::from_sat(21)) ); } From 9ab79a508faa88dfb5f948f305d22e05ac91d645 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 23:35:36 -0300 Subject: [PATCH 08/15] test: add `test_legacy_create_tx_absolute_high_fee()` --- wallet/tests/wallet.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index df8ee138..f5d1f03b 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -1039,6 +1039,19 @@ fn test_create_tx_absolute_high_fee() { let _ = builder.finish().unwrap(); } +#[test] +#[should_panic(expected = "InsufficientFunds")] +fn test_legacy_create_tx_absolute_high_fee() { + let (mut wallet, _) = get_funded_wallet(get_test_pkh(), None); + let addr = wallet.next_unused_address(KeychainKind::External); + let mut builder = wallet.build_tx(); + builder + .drain_to(addr.script_pubkey()) + .drain_wallet() + .fee_absolute(Amount::from_sat(60_000)); + let _ = builder.finish().unwrap(); +} + #[test] fn test_create_tx_add_change() { use bdk_wallet::tx_builder::TxOrdering; From 436e09bbf2091dab694cd8fdf54f7c5f34dcc7d5 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 23:37:00 -0300 Subject: [PATCH 09/15] test: add `test_legacy_create_tx_default_sighash()` --- wallet/tests/wallet.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index f5d1f03b..945dda95 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -1151,6 +1151,17 @@ fn test_create_tx_default_sighash() { assert_eq!(psbt.inputs[0].sighash_type, None); } +#[test] +fn test_legacy_create_tx_default_sighash() { + let (mut wallet, _) = get_funded_wallet(get_test_pkh(), None); + let addr = wallet.next_unused_address(KeychainKind::External); + let mut builder = wallet.build_tx(); + builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)); + let psbt = builder.finish().unwrap(); + + assert_eq!(psbt.inputs[0].sighash_type, None); +} + #[test] fn test_create_tx_custom_sighash() { let (mut wallet, _) = get_funded_wallet_wpkh(); From d99a5839da60d804d3a2a88b0ce6d36b98d2a59a Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 23:40:13 -0300 Subject: [PATCH 10/15] test: add `test_legacy_create_tx_custom_sighash()` --- wallet/tests/wallet.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index 945dda95..8f1e831b 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -1178,6 +1178,22 @@ fn test_create_tx_custom_sighash() { ); } +#[test] +fn test_legacy_create_tx_custom_sighash() { + let (mut wallet, _) = get_funded_wallet(get_test_pkh(), None); + let addr = wallet.next_unused_address(KeychainKind::External); + let mut builder = wallet.build_tx(); + builder + .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) + .sighash(EcdsaSighashType::Single.into()); + let psbt = builder.finish().unwrap(); + + assert_eq!( + psbt.inputs[0].sighash_type, + Some(EcdsaSighashType::Single.into()) + ); +} + #[test] fn test_create_tx_input_hd_keypaths() { use bitcoin::bip32::{DerivationPath, Fingerprint}; From b7e4cb4bac78b0b2261ab6764694fd938bc4b805 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 23:44:49 -0300 Subject: [PATCH 11/15] test: add `test_legacy_bump_fee_zero_abs()` --- wallet/tests/wallet.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index 8f1e831b..dc08f2e4 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -2056,6 +2056,24 @@ fn test_bump_fee_zero_abs() { builder.finish().unwrap(); } +#[test] +#[should_panic(expected = "FeeTooLow")] +fn test_legacy_bump_fee_zero_abs() { + let (mut wallet, _) = get_funded_wallet(get_test_pkh(), None); + let addr = wallet.next_unused_address(KeychainKind::External); + let mut builder = wallet.build_tx(); + builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); + let psbt = builder.finish().unwrap(); + + let tx = psbt.extract_tx().expect("failed to extract tx"); + let txid = tx.compute_txid(); + insert_tx(&mut wallet, tx); + + let mut builder = wallet.build_fee_bump(txid).unwrap(); + builder.fee_absolute(Amount::ZERO); + builder.finish().unwrap(); +} + #[test] fn test_bump_fee_reduce_change() { let (mut wallet, _) = get_funded_wallet_wpkh(); From 50933c0aa7e64186f0087357c91af9ac098ec15e Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Mon, 21 Apr 2025 23:56:02 -0300 Subject: [PATCH 12/15] test: add `test_legacy_bump_fee_drain_wallet()` --- wallet/tests/wallet.rs | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index dc08f2e4..587f6542 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -2308,6 +2308,62 @@ fn test_bump_fee_drain_wallet() { assert_eq!(sent_received.0, Amount::from_sat(75_000)); } +#[test] +fn test_legacy_bump_fee_drain_wallet() { + let (mut wallet, _) = get_funded_wallet(get_test_pkh(), None); + // receive an extra tx so that our wallet has two utxos. + let tx = Transaction { + version: transaction::Version::ONE, + lock_time: absolute::LockTime::ZERO, + input: vec![], + output: vec![TxOut { + value: Amount::from_sat(25_000), + script_pubkey: wallet + .next_unused_address(KeychainKind::External) + .script_pubkey(), + }], + }; + let txid = tx.compute_txid(); + insert_tx(&mut wallet, tx.clone()); + let anchor = ConfirmationBlockTime { + block_id: wallet.latest_checkpoint().block_id(), + confirmation_time: 42_000, + }; + insert_anchor(&mut wallet, txid, anchor); + + let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") + .unwrap() + .assume_checked(); + + let mut builder = wallet.build_tx(); + builder + .drain_to(addr.script_pubkey()) + .add_utxo(OutPoint { + txid: tx.compute_txid(), + vout: 0, + }) + .unwrap() + .manually_selected_only(); + let psbt = builder.finish().unwrap(); + let tx = psbt.extract_tx().expect("failed to extract tx"); + let original_sent_received = wallet.sent_and_received(&tx); + + let txid = tx.compute_txid(); + insert_tx(&mut wallet, tx); + assert_eq!(original_sent_received.0, Amount::from_sat(25_000)); + + // for the new feerate, it should be enough to reduce the output, but since we specify + // `drain_wallet` we expect to spend everything + let mut builder = wallet.build_fee_bump(txid).unwrap(); + builder + .drain_wallet() + .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); + let psbt = builder.finish().unwrap(); + let sent_received = wallet.sent_and_received(&psbt.extract_tx().expect("failed to extract tx")); + + assert_eq!(sent_received.0, Amount::from_sat(75_000)); +} + #[test] #[should_panic(expected = "InsufficientFunds")] fn test_bump_fee_remove_output_manually_selected_only() { From dbd5a7b27190031a5f02779522d24e9df6a600a4 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Tue, 22 Apr 2025 00:04:55 -0300 Subject: [PATCH 13/15] test: add `test_legacy_bump_fee_add_input()` --- wallet/tests/wallet.rs | 72 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index 587f6542..99fb981f 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -2494,6 +2494,78 @@ fn test_bump_fee_add_input() { assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), FeeRate::from_sat_per_vb_unchecked(50), @add_signature); } +#[test] +fn test_legacy_bump_fee_add_input() { + let (mut wallet, _) = get_funded_wallet(get_test_pkh(), None); + let init_tx = Transaction { + version: transaction::Version::ONE, + lock_time: absolute::LockTime::ZERO, + input: vec![], + output: vec![TxOut { + script_pubkey: wallet + .next_unused_address(KeychainKind::External) + .script_pubkey(), + value: Amount::from_sat(25_000), + }], + }; + let txid = init_tx.compute_txid(); + let pos: ChainPosition = + wallet.transactions().last().unwrap().chain_position; + insert_tx(&mut wallet, init_tx); + match pos { + ChainPosition::Confirmed { anchor, .. } => insert_anchor(&mut wallet, txid, anchor), + other => panic!("all wallet txs must be confirmed: {:?}", other), + } + + let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") + .unwrap() + .assume_checked(); + let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); + builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); + let psbt = builder.finish().unwrap(); + let tx = psbt.extract_tx().expect("failed to extract tx"); + let original_details = wallet.sent_and_received(&tx); + let txid = tx.compute_txid(); + insert_tx(&mut wallet, tx); + + let mut builder = wallet.build_fee_bump(txid).unwrap(); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50)); + let psbt = builder.finish().unwrap(); + let sent_received = + wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); + let fee = check_fee!(wallet, psbt); + assert_eq!( + sent_received.0, + original_details.0 + Amount::from_sat(25_000) + ); + assert_eq!( + fee.unwrap_or(Amount::ZERO) + sent_received.1, + Amount::from_sat(30_000) + ); + + let tx = &psbt.unsigned_tx; + assert_eq!(tx.input.len(), 2); + assert_eq!(tx.output.len(), 2); + assert_eq!( + tx.output + .iter() + .find(|txout| txout.script_pubkey == addr.script_pubkey()) + .unwrap() + .value, + Amount::from_sat(45_000) + ); + assert_eq!( + tx.output + .iter() + .find(|txout| txout.script_pubkey != addr.script_pubkey()) + .unwrap() + .value, + sent_received.1 + ); + + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), FeeRate::from_sat_per_vb_unchecked(50), @add_signature); +} + #[test] fn test_bump_fee_absolute_add_input() { let (mut wallet, _) = get_funded_wallet_wpkh(); From def019fed05a85f3ca1dd4c8776b318e40f8d0c6 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Tue, 22 Apr 2025 00:10:46 -0300 Subject: [PATCH 14/15] test: add `test_legacy_bump_fee_no_change_add_input_and_change()` --- wallet/tests/wallet.rs | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index 99fb981f..41945186 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -2620,6 +2620,60 @@ fn test_bump_fee_absolute_add_input() { assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::from_sat(6_000)); } +#[test] +fn test_legacy_bump_fee_absolute_add_input() { + let (mut wallet, _) = get_funded_wallet(get_test_pkh(), None); + receive_output_in_latest_block(&mut wallet, 25_000); + let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") + .unwrap() + .assume_checked(); + let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); + builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); + let psbt = builder.finish().unwrap(); + let tx = psbt.extract_tx().expect("failed to extract tx"); + let original_sent_received = wallet.sent_and_received(&tx); + let txid = tx.compute_txid(); + insert_tx(&mut wallet, tx); + + let mut builder = wallet.build_fee_bump(txid).unwrap(); + builder.fee_absolute(Amount::from_sat(6_000)); + let psbt = builder.finish().unwrap(); + let sent_received = + wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); + let fee = check_fee!(wallet, psbt); + + assert_eq!( + sent_received.0, + original_sent_received.0 + Amount::from_sat(25_000) + ); + assert_eq!( + fee.unwrap_or(Amount::ZERO) + sent_received.1, + Amount::from_sat(30_000) + ); + + let tx = &psbt.unsigned_tx; + assert_eq!(tx.input.len(), 2); + assert_eq!(tx.output.len(), 2); + assert_eq!( + tx.output + .iter() + .find(|txout| txout.script_pubkey == addr.script_pubkey()) + .unwrap() + .value, + Amount::from_sat(45_000) + ); + assert_eq!( + tx.output + .iter() + .find(|txout| txout.script_pubkey != addr.script_pubkey()) + .unwrap() + .value, + sent_received.1 + ); + + assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::from_sat(6_000)); +} + #[test] fn test_bump_fee_no_change_add_input_and_change() { let (mut wallet, _) = get_funded_wallet_wpkh(); From 5f9d18c6eb59589687dcb5968a245bd5025dccaa Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Tue, 22 Apr 2025 00:17:03 -0300 Subject: [PATCH 15/15] fix: clippy --- wallet/src/wallet/coin_selection.rs | 6 +++--- wallet/src/wallet/persisted.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/wallet/src/wallet/coin_selection.rs b/wallet/src/wallet/coin_selection.rs index e5654fb4..1513ae84 100644 --- a/wallet/src/wallet/coin_selection.rs +++ b/wallet/src/wallet/coin_selection.rs @@ -204,12 +204,12 @@ pub trait CoinSelectionAlgorithm: core::fmt::Debug { /// Perform the coin selection /// /// - `required_utxos`: the utxos that must be spent regardless of `target_amount` with their - /// weight cost + /// weight cost /// - `optional_utxos`: the remaining available utxos to satisfy `target_amount` with their - /// weight cost + /// weight cost /// - `fee_rate`: fee rate to use /// - `target_amount`: the outgoing amount and the fees already accumulated from adding - /// outputs and transaction’s header. + /// outputs and transaction’s header. /// - `drain_script`: the script to use in case of change /// - `rand`: random number generated used by some coin selection algorithms such as [`SingleRandomDraw`] fn coin_select( diff --git a/wallet/src/wallet/persisted.rs b/wallet/src/wallet/persisted.rs index 28a6ec78..69e403eb 100644 --- a/wallet/src/wallet/persisted.rs +++ b/wallet/src/wallet/persisted.rs @@ -112,11 +112,11 @@ pub trait AsyncWalletPersister { /// /// * Ensure the persister is initialized before data is persisted. /// * Ensure there were no previously persisted wallet data before creating a fresh wallet and -/// persisting it. +/// persisting it. /// * Only clear the staged changes of [`Wallet`] after persisting succeeds. /// * Ensure the wallet is persisted to the same `P` type as when created/loaded. Note that this is -/// not completely fool-proof as you can have multiple instances of the same `P` type that are -/// connected to different databases. +/// not completely fool-proof as you can have multiple instances of the same `P` type that are +/// connected to different databases. #[derive(Debug)] pub struct PersistedWallet

{ inner: Wallet,