@@ -73,6 +73,7 @@ use crate::testutils;
73
73
use crate :: types:: * ;
74
74
75
75
const CACHE_ADDR_BATCH_SIZE : u32 = 100 ;
76
+ const COINBASE_MATURITY : u32 = 100 ;
76
77
77
78
/// A Bitcoin wallet
78
79
///
@@ -765,6 +766,7 @@ where
765
766
params. drain_wallet ,
766
767
params. manually_selected_only ,
767
768
params. bumping_fee . is_some ( ) , // we mandate confirmed transactions if we're bumping the fee
769
+ current_height,
768
770
) ?;
769
771
770
772
let coin_selection = coin_selection. coin_select (
@@ -1335,6 +1337,7 @@ where
1335
1337
/// Given the options returns the list of utxos that must be used to form the
1336
1338
/// transaction and any further that may be used if needed.
1337
1339
#[ allow( clippy:: type_complexity) ]
1340
+ #[ allow( clippy:: too_many_arguments) ]
1338
1341
fn preselect_utxos (
1339
1342
& self ,
1340
1343
change_policy : tx_builder:: ChangeSpendPolicy ,
@@ -1343,6 +1346,7 @@ where
1343
1346
must_use_all_available : bool ,
1344
1347
manual_only : bool ,
1345
1348
must_only_use_confirmed_tx : bool ,
1349
+ current_height : Option < u32 > ,
1346
1350
) -> Result < ( Vec < WeightedUtxo > , Vec < WeightedUtxo > ) , Error > {
1347
1351
// must_spend <- manually selected utxos
1348
1352
// may_spend <- all other available utxos
@@ -1361,23 +1365,44 @@ where
1361
1365
return Ok ( ( must_spend, vec ! [ ] ) ) ;
1362
1366
}
1363
1367
1364
- let satisfies_confirmed = match must_only_use_confirmed_tx {
1365
- true => {
1366
- let database = self . database . borrow ( ) ;
1367
- may_spend
1368
- . iter ( )
1369
- . map ( |u| {
1370
- database
1371
- . get_tx ( & u. 0 . outpoint . txid , true )
1372
- . map ( |tx| match tx {
1373
- None => false ,
1374
- Some ( tx) => tx. confirmation_time . is_some ( ) ,
1375
- } )
1368
+ let database = self . database . borrow ( ) ;
1369
+ let satisfies_confirmed = may_spend
1370
+ . iter ( )
1371
+ . map ( |u| {
1372
+ database
1373
+ . get_tx ( & u. 0 . outpoint . txid , true )
1374
+ . map ( |tx| match tx {
1375
+ // We don't have the tx in the db for some reason,
1376
+ // so we can't know for sure if it's mature or not.
1377
+ // We prefer not to spend it.
1378
+ None => false ,
1379
+ Some ( tx) => {
1380
+ // Whether the UTXO is mature and, if needed, confirmed
1381
+ let mut spendable = true ;
1382
+ if must_only_use_confirmed_tx && tx. confirmation_time . is_none ( ) {
1383
+ return false ;
1384
+ }
1385
+ if tx
1386
+ . transaction
1387
+ . expect ( "We specifically ask for the transaction above" )
1388
+ . is_coin_base ( )
1389
+ {
1390
+ if let Some ( current_height) = current_height {
1391
+ match & tx. confirmation_time {
1392
+ Some ( t) => {
1393
+ // https://github.com/bitcoin/bitcoin/blob/c5e67be03bb06a5d7885c55db1f016fbf2333fe3/src/validation.cpp#L373-L375
1394
+ spendable &= ( current_height. saturating_sub ( t. height ) )
1395
+ >= COINBASE_MATURITY ;
1396
+ }
1397
+ None => spendable = false ,
1398
+ }
1399
+ }
1400
+ }
1401
+ spendable
1402
+ }
1376
1403
} )
1377
- . collect :: < Result < Vec < _ > , _ > > ( ) ?
1378
- }
1379
- false => vec ! [ true ; may_spend. len( ) ] ,
1380
- } ;
1404
+ } )
1405
+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
1381
1406
1382
1407
let mut i = 0 ;
1383
1408
may_spend. retain ( |u| {
@@ -4643,4 +4668,70 @@ pub(crate) mod test {
4643
4668
"The signature should have been made with the right sighash"
4644
4669
) ;
4645
4670
}
4671
+
4672
+ #[ test]
4673
+ fn test_spend_coinbase ( ) {
4674
+ let descriptors = testutils ! ( @descriptors ( get_test_wpkh( ) ) ) ;
4675
+ let wallet = Wallet :: new (
4676
+ & descriptors. 0 ,
4677
+ None ,
4678
+ Network :: Regtest ,
4679
+ AnyDatabase :: Memory ( MemoryDatabase :: new ( ) ) ,
4680
+ )
4681
+ . unwrap ( ) ;
4682
+
4683
+ let confirmation_time = 5 ;
4684
+
4685
+ crate :: populate_test_db!(
4686
+ wallet. database. borrow_mut( ) ,
4687
+ testutils! ( @tx ( ( @external descriptors, 0 ) => 25_000 ) ( @confirmations 0 ) ) ,
4688
+ Some ( confirmation_time) ,
4689
+ ( @coinbase true )
4690
+ ) ;
4691
+
4692
+ let not_yet_mature_time = confirmation_time + COINBASE_MATURITY - 1 ;
4693
+ let maturity_time = confirmation_time + COINBASE_MATURITY ;
4694
+
4695
+ // The balance is nonzero, even if we can't spend anything
4696
+ // FIXME: we should differentiate the balance between immature,
4697
+ // trusted, untrusted_pending
4698
+ // See https://github.com/bitcoindevkit/bdk/issues/238
4699
+ let balance = wallet. get_balance ( ) . unwrap ( ) ;
4700
+ assert ! ( balance != 0 ) ;
4701
+
4702
+ // We try to create a transaction, only to notice that all
4703
+ // our funds are unspendable
4704
+ let addr = Address :: from_str ( "2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX" ) . unwrap ( ) ;
4705
+ let mut builder = wallet. build_tx ( ) ;
4706
+ builder
4707
+ . add_recipient ( addr. script_pubkey ( ) , balance / 2 )
4708
+ . set_current_height ( confirmation_time) ;
4709
+ assert ! ( matches!(
4710
+ builder. finish( ) . unwrap_err( ) ,
4711
+ Error :: InsufficientFunds {
4712
+ needed: _,
4713
+ available: 0
4714
+ }
4715
+ ) ) ;
4716
+
4717
+ // Still unspendable...
4718
+ let mut builder = wallet. build_tx ( ) ;
4719
+ builder
4720
+ . add_recipient ( addr. script_pubkey ( ) , balance / 2 )
4721
+ . set_current_height ( not_yet_mature_time) ;
4722
+ assert ! ( matches!(
4723
+ builder. finish( ) . unwrap_err( ) ,
4724
+ Error :: InsufficientFunds {
4725
+ needed: _,
4726
+ available: 0
4727
+ }
4728
+ ) ) ;
4729
+
4730
+ // ...Now the coinbase is mature :)
4731
+ let mut builder = wallet. build_tx ( ) ;
4732
+ builder
4733
+ . add_recipient ( addr. script_pubkey ( ) , balance / 2 )
4734
+ . set_current_height ( maturity_time) ;
4735
+ builder. finish ( ) . unwrap ( ) ;
4736
+ }
4646
4737
}
0 commit comments