Skip to content

Commit 03b7eca

Browse files
committed
fix(wallet): off-by-one error checking coinbase maturity in optional UTxOs
The `preselect_utxos` method has an off-by-one error that is making the selection of optional UTxOs too restrictive, by requiring the coinbase outputs to surpass or equal coinbase maturity time at the current height of the selection, and not in the block in which the transaction may be included in the blockchain. The changes in this commit fix it by considering the maturity of the coinbase output at the spending height and not the transaction creation height, this means, a +1 at the considered height at the moment of building the transaction.
1 parent 7067da1 commit 03b7eca

File tree

3 files changed

+17
-8
lines changed

3 files changed

+17
-8
lines changed

crates/wallet/src/wallet/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2052,9 +2052,10 @@ impl Wallet {
20522052
match chain_position {
20532053
ChainPosition::Confirmed { anchor, .. } => {
20542054
// https://github.com/bitcoin/bitcoin/blob/c5e67be03bb06a5d7885c55db1f016fbf2333fe3/src/validation.cpp#L373-L375
2055-
spendable &= (current_height
2056-
.saturating_sub(anchor.block_id.height))
2057-
>= COINBASE_MATURITY;
2055+
let spend_height = current_height + 1;
2056+
let coin_age_at_spend_height =
2057+
spend_height.saturating_sub(anchor.block_id.height);
2058+
spendable &= coin_age_at_spend_height >= COINBASE_MATURITY;
20582059
}
20592060
ChainPosition::Unconfirmed { .. } => spendable = false,
20602061
}

crates/wallet/src/wallet/tx_builder.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -556,9 +556,9 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
556556
/// 1. Set the nLockTime for preventing fee sniping.
557557
/// **Note**: This will be ignored if you manually specify a nlocktime using [`TxBuilder::nlocktime`].
558558
/// 2. Decide whether coinbase outputs are mature or not. If the coinbase outputs are not
559-
/// mature at `current_height`, we ignore them in the coin selection.
560-
/// If you want to create a transaction that spends immature coinbase inputs, manually
561-
/// add them using [`TxBuilder::add_utxos`].
559+
/// mature at spending height, which is `current_height` + 1, we ignore them in the coin
560+
/// selection. If you want to create a transaction that spends immature coinbase inputs,
561+
/// manually add them using [`TxBuilder::add_utxos`].
562562
///
563563
/// In both cases, if you don't provide a current height, we use the last sync height.
564564
pub fn current_height(&mut self, height: u32) -> &mut Self {

crates/wallet/tests/wallet.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3875,8 +3875,16 @@ fn test_spend_coinbase() {
38753875
};
38763876
insert_anchor(&mut wallet, txid, anchor);
38773877

3878-
let not_yet_mature_time = confirmation_height + COINBASE_MATURITY - 1;
3879-
let maturity_time = confirmation_height + COINBASE_MATURITY;
3878+
// NOTE: A transaction spending an output coming from the coinbase tx at height h, is eligible
3879+
// to be included in block h + [100 = COINBASE_MATURITY] or higher.
3880+
// Tx elibible to be included in the next block will be accepted in the mempool, used in block
3881+
// templates and relayed on the network.
3882+
// Miners may include such tx in a block when their chaintip is at h + [99 = COINBASE_MATURITY - 1].
3883+
// This means these coins are available for selection at height h + 99.
3884+
//
3885+
// By https://bitcoin.stackexchange.com/a/119017
3886+
let not_yet_mature_time = confirmation_height + COINBASE_MATURITY - 2;
3887+
let maturity_time = confirmation_height + COINBASE_MATURITY - 1;
38803888

38813889
let balance = wallet.balance();
38823890
assert_eq!(

0 commit comments

Comments
 (0)