commands: exclude immature coins from coin selection

An immature coin could in principle be marked as change
as this depends only on whether the address is derived from
the wallet's change descriptor.
This commit is contained in:
jp1ac4 2024-03-11 16:55:56 +00:00
parent 3a7c151674
commit 2c96ef57bd
No known key found for this signature in database
GPG Key ID: A7ACD32423568D7B

View File

@ -470,7 +470,7 @@ impl DaemonControl {
.filter_map(|(op, c)| {
if c.block_info.is_some() {
Some((c, None)) // confirmed coins have no ancestor info
} else if c.is_change {
} else if c.is_change && !c.is_immature {
// In case the mempool_entry is None, the coin will be included without
// any ancestor info.
Some((
@ -1691,7 +1691,7 @@ mod tests {
txid: dummy_op.txid,
vout: dummy_op.vout + 100,
};
db_conn.new_unspent_coins(&[Coin {
let unconfirmed_coin = Coin {
outpoint: confirmed_op_1,
is_immature: false,
block_info: None,
@ -1700,7 +1700,8 @@ mod tests {
is_change: true,
spend_txid: None,
spend_block: None,
}]);
};
db_conn.new_unspent_coins(&[unconfirmed_coin]);
// Coin selection error due to insufficient funds.
assert!(matches!(
control.create_spend(&destinations, &[], 1, None),
@ -1782,6 +1783,41 @@ mod tests {
manual_input.sort();
assert_eq!(auto_input, manual_input);
// Now check that the spend created above using auto-selection only works when the unconfirmed coin
// is change and not immature.
// 1. not change and not immature
db_conn.remove_coins(&[unconfirmed_coin.outpoint]);
let mut unconfirmed_coin_2 = unconfirmed_coin;
unconfirmed_coin_2.is_change = false;
unconfirmed_coin_2.is_immature = false; // (this is already the case)
db_conn.new_unspent_coins(&[unconfirmed_coin_2]);
assert!(matches!(
control.create_spend(&destinations, &[], 1, None),
Ok(CreateSpendResult::InsufficientFunds { .. }),
));
// 2. change and immature
db_conn.remove_coins(&[unconfirmed_coin_2.outpoint]);
unconfirmed_coin_2.is_change = true;
unconfirmed_coin_2.is_immature = true;
db_conn.new_unspent_coins(&[unconfirmed_coin_2]);
assert!(matches!(
control.create_spend(&destinations, &[], 1, None),
Ok(CreateSpendResult::InsufficientFunds { .. }),
));
// 3. not change and immature
db_conn.remove_coins(&[unconfirmed_coin_2.outpoint]);
unconfirmed_coin_2.is_change = false;
unconfirmed_coin_2.is_immature = true;
db_conn.new_unspent_coins(&[unconfirmed_coin_2]);
assert!(matches!(
control.create_spend(&destinations, &[], 1, None),
Ok(CreateSpendResult::InsufficientFunds { .. }),
));
// Re-insert the original unconfirmed coin again.
db_conn.remove_coins(&[unconfirmed_coin_2.outpoint]);
db_conn.new_unspent_coins(&[unconfirmed_coin]);
// Now do the same again, but this time specifying the change address to be the same
// as for the auto spend.
let change_address = bitcoin::Address::from_script(