diff --git a/src/wallet/coin_selection.rs b/src/wallet/coin_selection.rs index 2d56b42d1d..a9ca356e74 100644 --- a/src/wallet/coin_selection.rs +++ b/src/wallet/coin_selection.rs @@ -43,7 +43,7 @@ //! fee_rate: FeeRate, //! amount_needed: u64, //! fee_amount: u64, -//! _drain_output: &TxOut, +//! _drain_script: &Script, //! ) -> Result { //! let mut selected_amount = 0; //! let mut additional_weight = 0; @@ -71,7 +71,7 @@ //! Ok(CoinSelectionResult { //! selected: all_utxos_selected, //! fee_amount: fee_amount + additional_fees, -//! excess: Excess::Change(0, 0), +//! excess: Excess::Change { amount: 0, fee: 0 }, //! }) //! } //! } @@ -97,7 +97,7 @@ use crate::{database::Database, WeightedUtxo}; use crate::{error::Error, Utxo}; use bitcoin::consensus::encode::serialize; -use bitcoin::TxOut; +use bitcoin::Script; use rand::seq::SliceRandom; #[cfg(not(test))] @@ -122,11 +122,21 @@ pub(crate) const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4 + 1) * 4; /// Remaining amount after performing coin selection pub enum Excess { /// It's not possible to create spendable output from excess using the current drain output - /// NoChange(target, remaining_amount, change_output_fee) - NoChange(u64, u64, u64), + NoChange { + /// Threshold to consider amount as dust for this particular change script_pubkey + dust_threshold: u64, + /// Exceeding amount of current selection over outgoing value and fee costs + remaining_amount: u64, + /// The calculated fee for the drain TxOut with the selected script_pubkey + change_fee: u64, + }, /// It's possible to create spendable output from excess using the current drain output - /// Change(change_amount, change_output_fee) - Change(u64, u64), + Change { + /// Effective amount available to create change after deducting the change output fee + amount: u64, + /// The deducted change output fee + fee: u64, + }, } /// Result of a successful coin selection @@ -177,6 +187,7 @@ pub trait CoinSelectionAlgorithm: std::fmt::Debug { /// - `amount_needed`: the amount in satoshi to select /// - `fee_amount`: the amount of fees in satoshi already accumulated from adding outputs and /// the transaction's header + /// - `drain_script`: the script to use in case of change #[allow(clippy::too_many_arguments)] fn coin_select( &self, @@ -186,7 +197,7 @@ pub trait CoinSelectionAlgorithm: std::fmt::Debug { fee_rate: FeeRate, amount_needed: u64, fee_amount: u64, - _drain_output: &TxOut, + drain_script: &Script, ) -> Result; } @@ -206,7 +217,7 @@ impl CoinSelectionAlgorithm for LargestFirstCoinSelection { fee_rate: FeeRate, amount_needed: u64, fee_amount: u64, - drain_output: &TxOut, + drain_script: &Script, ) -> Result { log::debug!( "amount_needed = `{}`, fee_amount = `{}`, fee_rate = `{:?}`", @@ -225,7 +236,7 @@ impl CoinSelectionAlgorithm for LargestFirstCoinSelection { .chain(optional_utxos.into_iter().rev().map(|utxo| (false, utxo))) }; - select_sorted_utxos(utxos, fee_rate, amount_needed, fee_amount, drain_output) + select_sorted_utxos(utxos, fee_rate, amount_needed, fee_amount, drain_script) } } @@ -245,7 +256,7 @@ impl CoinSelectionAlgorithm for OldestFirstCoinSelection { fee_rate: FeeRate, amount_needed: u64, fee_amount: u64, - drain_output: &TxOut, + drain_script: &Script, ) -> Result { // query db and create a blockheight lookup table let blockheights = optional_utxos @@ -285,7 +296,25 @@ impl CoinSelectionAlgorithm for OldestFirstCoinSelection { .chain(optional_utxos.into_iter().map(|utxo| (false, utxo))) }; - select_sorted_utxos(utxos, fee_rate, amount_needed, fee_amount, drain_output) + select_sorted_utxos(utxos, fee_rate, amount_needed, fee_amount, drain_script) + } +} + +fn decide_change(remaining_amount: u64, change_fee: u64, drain_script: &Script) -> Excess { + let drain_val = remaining_amount.saturating_sub(change_fee); + + if drain_val.is_dust(drain_script) { + let dust_threshold = drain_script.dust_value().as_sat(); + Excess::NoChange { + dust_threshold, + change_fee, + remaining_amount, + } + } else { + Excess::Change { + amount: drain_val, + fee: change_fee, + } } } @@ -294,7 +323,7 @@ fn select_sorted_utxos( fee_rate: FeeRate, amount_needed: u64, mut fee_amount: u64, - drain_output: &TxOut, + drain_script: &Script, ) -> Result { let mut selected_amount = 0; let selected = utxos @@ -331,16 +360,11 @@ fn select_sorted_utxos( let remaining_amount = selected_amount - amount_needed_with_fees; - let change_fee = fee_rate.fee_vb(serialize(drain_output).len()); + // drain_output_len = size(len(script_pubkey)) + len(script_pubkey) + size(output_value) + let drain_output_len = serialize(drain_script).len() + 8usize; + let change_fee = fee_rate.fee_vb(drain_output_len); - let drain_val = remaining_amount.saturating_sub(change_fee); - - let excess = if drain_val.is_dust(&drain_output.script_pubkey) { - let target = drain_output.script_pubkey.dust_value().as_sat(); - Excess::NoChange(target, change_fee, remaining_amount) - } else { - Excess::Change(drain_val, change_fee) - }; + let excess = decide_change(remaining_amount, change_fee, drain_script); Ok(CoinSelectionResult { selected, @@ -406,7 +430,7 @@ impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection { fee_rate: FeeRate, amount_needed: u64, fee_amount: u64, - _drain_output: &TxOut, + drain_script: &Script, ) -> Result { // Mapping every (UTXO, usize) to an output group let required_utxos: Vec = required_utxos @@ -448,11 +472,27 @@ impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection { .try_into() .expect("Bitcoin amount to fit into i64"); + // drain_output_len = size(len(script_pubkey)) + len(script_pubkey) + size(output_value) + let drain_output_len = serialize(drain_script).len() + 8usize; + let change_fee = fee_rate.fee_vb(drain_output_len); + if curr_value > actual_target { + let selected_amount = required_utxos + .iter() + .map(|u| u.effective_value) + .sum::(); + // remaining_amount can't be negative as that would mean the + // selection wasn't successfull + // actual_target = amount_needed + (fee_amount - vin_fees) + let remaining_amount = (selected_amount - actual_target) as u64; + + let excess = decide_change(remaining_amount, change_fee, drain_script); + return Ok(BranchAndBoundCoinSelection::calculate_cs_result( vec![], required_utxos, fee_amount, + excess, )); } @@ -465,6 +505,8 @@ impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection { actual_target, fee_amount, cost_of_change, + drain_script, + change_fee, ) .unwrap_or_else(|_| { self.single_random_draw( @@ -473,6 +515,8 @@ impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection { curr_value, actual_target, fee_amount, + drain_script, + change_fee, ) })) } @@ -491,6 +535,8 @@ impl BranchAndBoundCoinSelection { actual_target: i64, fee_amount: u64, cost_of_change: f32, + drain_script: &Script, + change_fee: u64, ) -> Result { // current_selection[i] will contain true if we are using optional_utxos[i], // false otherwise. Note that current_selection.len() could be less than @@ -582,15 +628,31 @@ impl BranchAndBoundCoinSelection { .into_iter() .zip(best_selection) .filter_map(|(optional, is_in_best)| if is_in_best { Some(optional) } else { None }) - .collect(); + .collect::>(); + + let selected_amount = selected_utxos + .iter() + .map(|u| u.effective_value) + .sum::(); + + // remaining_amount can't be negative as that would mean the + // selection wasn't successfull + // actual_target = amount_needed + (fee_amount - vin_fees) + let remaining_amount = (selected_amount - actual_target) as u64; + + let excess = decide_change(remaining_amount, change_fee, drain_script); Ok(BranchAndBoundCoinSelection::calculate_cs_result( selected_utxos, required_utxos, fee_amount, + excess, )) } + #[allow(clippy::too_many_arguments)] + // REVIEW: Other approaches required more parameters or a large number of + // argument passing between functions that didn't use them fn single_random_draw( &self, required_utxos: Vec, @@ -598,6 +660,8 @@ impl BranchAndBoundCoinSelection { curr_value: i64, actual_target: i64, fee_amount: u64, + drain_script: &Script, + change_fee: u64, ) -> CoinSelectionResult { #[cfg(not(test))] optional_utxos.shuffle(&mut thread_rng()); @@ -620,13 +684,31 @@ impl BranchAndBoundCoinSelection { }) .collect::>(); - BranchAndBoundCoinSelection::calculate_cs_result(selected_utxos, required_utxos, fee_amount) + let selected_amount = selected_utxos + .iter() + .map(|u| u.effective_value) + .sum::(); + + // remaining_amount can't be negative as that would mean the + // selection wasn't successfull + // actual_target = amount_needed + (fee_amount - vin_fees) + let remaining_amount = (selected_amount - actual_target) as u64; + + let excess = decide_change(remaining_amount, change_fee, drain_script); + + BranchAndBoundCoinSelection::calculate_cs_result( + selected_utxos, + required_utxos, + fee_amount, + excess, + ) } fn calculate_cs_result( mut selected_utxos: Vec, mut required_utxos: Vec, mut fee_amount: u64, + excess: Excess, ) -> CoinSelectionResult { selected_utxos.append(&mut required_utxos); fee_amount += selected_utxos.iter().map(|u| u.fee).sum::(); @@ -638,7 +720,7 @@ impl BranchAndBoundCoinSelection { CoinSelectionResult { selected, fee_amount, - excess: Excess::Change(0, 0), + excess, } } } @@ -683,13 +765,6 @@ mod test { } } - fn get_test_txout() -> TxOut { - TxOut { - script_pubkey: Script::default(), - value: 0, - } - } - fn get_test_utxos() -> Vec { vec![ utxo(100_000, 0), @@ -807,7 +882,7 @@ mod test { fn test_largest_first_coin_selection_success() { let utxos = get_test_utxos(); let database = MemoryDatabase::default(); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = LargestFirstCoinSelection::default() .coin_select( @@ -817,7 +892,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 250_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); @@ -830,7 +905,7 @@ mod test { fn test_largest_first_coin_selection_use_all() { let utxos = get_test_utxos(); let database = MemoryDatabase::default(); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = LargestFirstCoinSelection::default() .coin_select( @@ -840,7 +915,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 20_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); @@ -853,7 +928,7 @@ mod test { fn test_largest_first_coin_selection_use_only_necessary() { let utxos = get_test_utxos(); let database = MemoryDatabase::default(); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = LargestFirstCoinSelection::default() .coin_select( @@ -863,7 +938,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 20_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); @@ -877,7 +952,7 @@ mod test { fn test_largest_first_coin_selection_insufficient_funds() { let utxos = get_test_utxos(); let database = MemoryDatabase::default(); - let drain_output = get_test_txout(); + let drain_script = Script::default(); LargestFirstCoinSelection::default() .coin_select( @@ -887,7 +962,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 500_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); } @@ -897,7 +972,7 @@ mod test { fn test_largest_first_coin_selection_insufficient_funds_high_fees() { let utxos = get_test_utxos(); let database = MemoryDatabase::default(); - let drain_output = get_test_txout(); + let drain_script = Script::default(); LargestFirstCoinSelection::default() .coin_select( @@ -907,7 +982,7 @@ mod test { FeeRate::from_sat_per_vb(1000.0), 250_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); } @@ -916,7 +991,7 @@ mod test { fn test_oldest_first_coin_selection_success() { let mut database = MemoryDatabase::default(); let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = OldestFirstCoinSelection::default() .coin_select( @@ -926,7 +1001,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 180_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); @@ -941,7 +1016,7 @@ mod test { let utxo1 = utxo(120_000, 1); let utxo2 = utxo(80_000, 2); let utxo3 = utxo(300_000, 3); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let mut database = MemoryDatabase::default(); @@ -984,7 +1059,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 180_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); @@ -997,7 +1072,7 @@ mod test { fn test_oldest_first_coin_selection_use_all() { let mut database = MemoryDatabase::default(); let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = OldestFirstCoinSelection::default() .coin_select( @@ -1007,7 +1082,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 20_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); @@ -1020,7 +1095,7 @@ mod test { fn test_oldest_first_coin_selection_use_only_necessary() { let mut database = MemoryDatabase::default(); let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = OldestFirstCoinSelection::default() .coin_select( @@ -1030,7 +1105,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 20_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); @@ -1044,7 +1119,7 @@ mod test { fn test_oldest_first_coin_selection_insufficient_funds() { let mut database = MemoryDatabase::default(); let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database); - let drain_output = get_test_txout(); + let drain_script = Script::default(); OldestFirstCoinSelection::default() .coin_select( @@ -1054,7 +1129,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 600_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); } @@ -1067,7 +1142,7 @@ mod test { let amount_needed: u64 = utxos.iter().map(|wu| wu.utxo.txout().value).sum::() - (FEE_AMOUNT + 50); - let drain_output = get_test_txout(); + let drain_script = Script::default(); OldestFirstCoinSelection::default() .coin_select( @@ -1077,7 +1152,7 @@ mod test { FeeRate::from_sat_per_vb(1000.0), amount_needed, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); } @@ -1089,7 +1164,7 @@ mod test { let utxos = generate_same_value_utxos(100_000, 20); let database = MemoryDatabase::default(); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = BranchAndBoundCoinSelection::default() .coin_select( @@ -1099,7 +1174,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 250_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); @@ -1112,7 +1187,7 @@ mod test { fn test_bnb_coin_selection_required_are_enough() { let utxos = get_test_utxos(); let database = MemoryDatabase::default(); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = BranchAndBoundCoinSelection::default() .coin_select( @@ -1122,7 +1197,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 20_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); @@ -1135,7 +1210,7 @@ mod test { fn test_bnb_coin_selection_optional_are_enough() { let utxos = get_test_utxos(); let database = MemoryDatabase::default(); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = BranchAndBoundCoinSelection::default() .coin_select( @@ -1145,7 +1220,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 299756, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); @@ -1168,7 +1243,7 @@ mod test { assert_eq!(amount, 100_000); let amount: u64 = optional.iter().map(|u| u.utxo.txout().value).sum(); assert!(amount > 150_000); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = BranchAndBoundCoinSelection::default() .coin_select( @@ -1178,7 +1253,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 150_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); @@ -1192,7 +1267,7 @@ mod test { fn test_bnb_coin_selection_insufficient_funds() { let utxos = get_test_utxos(); let database = MemoryDatabase::default(); - let drain_output = get_test_txout(); + let drain_script = Script::default(); BranchAndBoundCoinSelection::default() .coin_select( @@ -1202,7 +1277,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 500_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); } @@ -1212,7 +1287,7 @@ mod test { fn test_bnb_coin_selection_insufficient_funds_high_fees() { let utxos = get_test_utxos(); let database = MemoryDatabase::default(); - let drain_output = get_test_txout(); + let drain_script = Script::default(); BranchAndBoundCoinSelection::default() .coin_select( @@ -1222,7 +1297,7 @@ mod test { FeeRate::from_sat_per_vb(1000.0), 250_000, FEE_AMOUNT, - &drain_output, + &drain_script, ) .unwrap(); } @@ -1231,7 +1306,7 @@ mod test { fn test_bnb_coin_selection_check_fee_rate() { let utxos = get_test_utxos(); let database = MemoryDatabase::default(); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = BranchAndBoundCoinSelection::new(0) .coin_select( @@ -1241,7 +1316,7 @@ mod test { FeeRate::from_sat_per_vb(1.0), 99932, // first utxo's effective value 0, - &drain_output, + &drain_script, ) .unwrap(); @@ -1261,7 +1336,7 @@ mod test { for _i in 0..200 { let mut optional_utxos = generate_random_utxos(&mut rng, 16); let target_amount = sum_random_utxos(&mut rng, &mut optional_utxos); - let drain_output = get_test_txout(); + let drain_script = Script::default(); let result = BranchAndBoundCoinSelection::new(0) .coin_select( &database, @@ -1270,7 +1345,7 @@ mod test { FeeRate::from_sat_per_vb(0.0), target_amount, 0, - &drain_output, + &drain_script, ) .unwrap(); assert_eq!(result.selected_amount(), target_amount); @@ -1290,6 +1365,13 @@ mod test { let size_of_change = 31; let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb(); + + let drain_script = Script::default(); + // drain_output_len = size(len(script_pubkey)) + len(script_pubkey) + size(output_value) + let drain_output_len = serialize(&drain_script).len() + 8usize; + + let change_fee = fee_rate.fee_vb(drain_output_len); + BranchAndBoundCoinSelection::new(size_of_change) .bnb( vec![], @@ -1299,6 +1381,8 @@ mod test { 20_000, FEE_AMOUNT, cost_of_change, + &drain_script, + change_fee, ) .unwrap(); } @@ -1317,6 +1401,12 @@ mod test { let size_of_change = 31; let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb(); + let drain_script = Script::default(); + // drain_output_len = size(len(script_pubkey)) + len(script_pubkey) + size(output_value) + let drain_output_len = serialize(&drain_script).len() + 8usize; + + let change_fee = fee_rate.fee_vb(drain_output_len); + BranchAndBoundCoinSelection::new(size_of_change) .bnb( vec![], @@ -1326,6 +1416,8 @@ mod test { 20_000, FEE_AMOUNT, cost_of_change, + &drain_script, + change_fee, ) .unwrap(); } @@ -1350,6 +1442,12 @@ mod test { // cost_of_change + 5. let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change.ceil() as i64 + 5; + let drain_script = Script::default(); + // drain_output_len = size(len(script_pubkey)) + len(script_pubkey) + size(output_value) + let drain_output_len = serialize(&drain_script).len() + 8usize; + + let change_fee = fee_rate.fee_vb(drain_output_len); + let result = BranchAndBoundCoinSelection::new(size_of_change) .bnb( vec![], @@ -1359,6 +1457,8 @@ mod test { target_amount, FEE_AMOUNT, cost_of_change, + &drain_script, + change_fee, ) .unwrap(); assert_eq!(result.selected_amount(), 100_000); @@ -1387,6 +1487,12 @@ mod test { let target_amount = optional_utxos[3].effective_value + optional_utxos[23].effective_value; + let drain_script = Script::default(); + // drain_output_len = size(len(script_pubkey)) + len(script_pubkey) + size(output_value) + let drain_output_len = serialize(&drain_script).len() + 8usize; + + let change_fee = fee_rate.fee_vb(drain_output_len); + let result = BranchAndBoundCoinSelection::new(0) .bnb( vec![], @@ -1396,6 +1502,8 @@ mod test { target_amount, 0, 0.0, + &drain_script, + change_fee, ) .unwrap(); assert_eq!(result.selected_amount(), target_amount as u64); @@ -1415,12 +1523,20 @@ mod test { .map(|u| OutputGroup::new(u, fee_rate)) .collect(); + let drain_script = Script::default(); + // drain_output_len = size(len(script_pubkey)) + len(script_pubkey) + size(output_value) + let drain_output_len = serialize(&drain_script).len() + 8usize; + + let change_fee = fee_rate.fee_vb(drain_output_len); + let result = BranchAndBoundCoinSelection::default().single_random_draw( vec![], utxos, 0, target_amount as i64, FEE_AMOUNT, + &drain_script, + change_fee, ); assert!(result.selected_amount() > target_amount); diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 602d11261c..3722b9d7c5 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -751,19 +751,12 @@ where )?; // prepare the drain output - let mut drain_output = { - let script_pubkey = match params.drain_to { - Some(ref drain_recipient) => drain_recipient.clone(), - None => self - .get_internal_address(AddressIndex::New)? - .address - .script_pubkey(), - }; - - TxOut { - script_pubkey, - value: 0, - } + let drain_script = match params.drain_to { + Some(ref drain_recipient) => drain_recipient.clone(), + None => self + .get_internal_address(AddressIndex::New)? + .address + .script_pubkey(), }; let coin_selection = coin_selection.coin_select( @@ -773,7 +766,7 @@ where fee_rate, outgoing, fee_amount, - &drain_output, + &drain_script, )?; let mut fee_amount = coin_selection.fee_amount; let excess = &coin_selection.excess; @@ -791,10 +784,15 @@ where if tx.output.is_empty() { if params.drain_to.is_some() { - if let NoChange(target, remaining_amount, fee) = excess { + if let NoChange { + dust_threshold, + remaining_amount, + change_fee, + } = excess + { return Err(Error::InsufficientFunds { - needed: *target, - available: remaining_amount.saturating_sub(*fee), + needed: *dust_threshold, + available: remaining_amount.saturating_sub(*change_fee), }); } } else { @@ -803,13 +801,20 @@ where } match excess { - NoChange(_, _, remaining_amount) => fee_amount += remaining_amount, - Change(value, fee) => { - if self.is_mine(&drain_output.script_pubkey)? { - received += value; + NoChange { + remaining_amount, .. + } => fee_amount += remaining_amount, + Change { amount, fee } => { + if self.is_mine(&drain_script)? { + received += amount; } fee_amount += fee; - drain_output.value = *value; + + let drain_output = TxOut { + value: *amount, + script_pubkey: drain_script, + }; + tx.output.push(drain_output); } };