Skip to content

Commit a4978af

Browse files
committed
Merge #1830: Fix off-by-one error checking coinbase maturity in optional UTxOs
03b7eca fix(wallet): off-by-one error checking coinbase maturity in optional UTxOs (nymius) Pull request description: ### Description As I was developing the changes in #1798 I discover issue #1810. So I introduced the fixes in that PR but later I split them in two to ease the review by suggestion of @oleonardolima . 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. Fixes #1810. ### Notes to the reviewers Tests for issue #1810 have not been explicitly added, as there already was a `text_spend_coinbase` test which was corrected to ensure coinbase maturation is considered in alignment with the new logic. Changes are not breaking but I'm modifying slightly the documentation for the public method `TxBuilder::current_height` to adjust to the fixed code. Does this merit an entry in the CHANGELOG? ### Changelog notice `Wallet` now considers a utxo originated from a coinbase transaction (`coinbase utxo`) as available for selection if it will mature in the next block after the height provided to the selection, the current height by default. The previous behavior allowed selecting a `coinbase utxo` only when the height at the moment of selection was equal to maturity height or greater. ### Checklists * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing * [x] I've updated existing tests to match the fix * [x] I've updated docs to match the fix logic * [x] This pull request DOES NOT break the existing API * [x] I'm linking the issue being fixed by this PR ACKs for top commit: LagginTimes: ACK 03b7eca evanlinjin: ACK 03b7eca Tree-SHA512: f270b73963bd6f141c8a3e759bc9b9bf75de7c52f37fff93f0a6b8b996b449d98c58e5eeb2b56f0ee236222f0807da5c8201ade7462813743e0c4d255313e2b5
2 parents 7067da1 + 03b7eca commit a4978af

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)