Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/wallet/coin_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -705,16 +705,17 @@ impl CoinSelectionAlgorithm for SingleRandomDraw {
}

fn calculate_cs_result(
mut selected_utxos: Vec<OutputGroup>,
mut required_utxos: Vec<OutputGroup>,
selected_utxos: Vec<OutputGroup>,
required_utxos: Vec<OutputGroup>,
excess: Excess,
) -> CoinSelectionResult {
selected_utxos.append(&mut required_utxos);
let fee_amount = selected_utxos.iter().map(|u| u.fee).sum();
let selected = selected_utxos
let mut selected = required_utxos;
selected.extend(selected_utxos);
let fee_amount = selected.iter().map(|u| u.fee).sum();
let selected = selected
.into_iter()
.map(|u| u.weighted_utxo.utxo)
.collect::<Vec<_>>();
.map(|output_group| output_group.weighted_utxo.utxo)
.collect();

CoinSelectionResult {
selected,
Expand Down
52 changes: 52 additions & 0 deletions tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3026,3 +3026,55 @@ fn test_tx_ordering_untouched_preserves_insertion_ordering() {
// Check vout is sorted by recipient insertion order
assert!(txouts == vec![400, 300, 500]);
}

// BnB coin selection should find a solution using the optional UTXO.
// This demonstrates that `calculate_cs_result` correctly orders required UTXOs before selected
// ones.
#[test]
fn test_tx_ordering_untouched_preserves_insertion_ordering_bnb_success() {
// Create empty wallet
let (desc, change_desc) = get_test_wpkh_and_change_desc();
let mut wallet = Wallet::create(desc, change_desc)
.network(bdk_wallet::bitcoin::Network::Regtest)
.create_wallet_no_persist()
.unwrap();

// Set up UTXOs with specific values so BnB can find an exact match (avoiding change).
// - outpoint_0 (required): 35,000 sat - not enough alone
// - outpoint_1 (optional): 25,200 sat
// - Target: 60,000 sat
// - Expected fee: 200 sat

let outpoint_0 = receive_output(
&mut wallet,
Amount::from_sat(35_000),
ReceiveTo::Mempool(50),
);
let outpoint_1 = receive_output(
&mut wallet,
Amount::from_sat(25_200),
ReceiveTo::Mempool(100),
);

let send_to = wallet.next_unused_address(KeychainKind::External).address;
let mut tx_builder = wallet.build_tx();
tx_builder
.add_utxo(outpoint_0)
.unwrap()
.add_recipient(send_to.script_pubkey(), Amount::from_sat(60_000))
.fee_rate(FeeRate::from_sat_per_vb(1).unwrap())
.ordering(bdk_wallet::TxOrdering::Untouched);
let psbt = tx_builder.finish().unwrap();

// Verify that both UTXOs are selected in the correct order:
// required (outpoint_0) should appear before optional (outpoint_1)
assert_eq!(
psbt.unsigned_tx
.input
.iter()
.map(|txin| txin.previous_output)
.collect::<Vec<_>>(),
vec![outpoint_0, outpoint_1],
"UTXOs should be ordered with required first, then selected"
);
}