diff --git a/src/commands/mod.rs b/src/commands/mod.rs index dfbf74c59..bb34e511b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1036,17 +1036,28 @@ impl DaemonControl { } }; } - // We take all previous inputs as mandatory candidates and, if not self-send, include - // confirmed coins as optional. - // TODO: For `is_cancel`, shall we include just one previous input? - let candidate_coins: Vec = prev_coins + // If `!is_cancel`, we take the previous coins as mandatory candidates and add confirmed coins as optional. + // Otherwise, we take the previous coins as optional candidates and let coin selection find the + // best solution that includes at least one of these. If there are insufficient funds to create the replacement + // transaction in this way, then we set candidates in the same way as for the `!is_cancel` case. + let mut candidate_coins: Vec = prev_coins .values() - .chain(db_conn.coins(&[CoinStatus::Confirmed], &[]).values()) .map(|c| CandidateCoin { coin: *c, - must_select: prev_coins.contains_key(&c.outpoint), + must_select: !is_cancel, }) .collect(); + if !is_cancel { + candidate_coins.extend( + db_conn + .coins(&[CoinStatus::Confirmed], &[]) + .into_values() + .map(|c| CandidateCoin { + coin: c, + must_select: false, + }), + ); + } // Try with increasing feerate until fee paid by replacement transaction is high enough. // Replacement fee must be at least: // sum of fees paid by original transactions + incremental feerate * replacement size. @@ -1057,13 +1068,34 @@ impl DaemonControl { // will ensure that the PSBT meets the required replacement fee and the loop will exit. let min_fee = mempool_entry.fees.descendant.to_sat() + rbf_vsize; println!("trying min_fee of {}", min_fee); - let rbf_psbt = self.create_rbf_spend( + let rbf_psbt = match self.create_rbf_spend( &destinations, &candidate_coins, feerate_vb, min_fee, change_index, - )?; + ) { + Ok(psbt) => psbt, + // If we get a coin selection error due to insufficient funds and we want to cancel the + // transaction, then set all previous coins as mandatory and add confirmed coins as + // optional, unless we have already done this. + Err(CommandError::CoinSelectionError(_)) + if is_cancel && candidate_coins.iter().all(|c| !c.must_select) => + { + candidate_coins = prev_coins + .values() + .chain(db_conn.coins(&[CoinStatus::Confirmed], &[]).values()) + .map(|c| CandidateCoin { + coin: *c, + must_select: prev_coins.contains_key(&c.outpoint), + }) + .collect(); + continue; + } + Err(e) => { + return Err(e); + } + }; rbf_vsize = { // TODO: Make this a function (can be used also in `sanity_check`). let witness_factor: u64 =