@@ -23,6 +23,7 @@ use crate::ln::chan_utils::{
23
23
} ;
24
24
use crate :: events:: Event ;
25
25
use crate :: prelude:: HashMap ;
26
+ use crate :: sync:: Mutex ;
26
27
use crate :: util:: logger:: Logger ;
27
28
use crate :: util:: ser:: Writeable ;
28
29
@@ -308,7 +309,8 @@ pub struct CoinSelection {
308
309
309
310
/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
310
311
/// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC,
311
- /// which most wallets should be able to satisfy.
312
+ /// which most wallets should be able to satisfy. Otherwise, consider implementing [`WalletSource`],
313
+ /// which can provide a default implementation of this trait when used with [`Wallet`].
312
314
pub trait CoinSelectionSource {
313
315
/// Performs coin selection of a set of UTXOs, with at least 1 confirmation each, that are
314
316
/// available to spend. Implementations are free to pick their coin selection algorithm of
@@ -336,6 +338,141 @@ pub trait CoinSelectionSource {
336
338
fn sign_tx ( & self , tx : & mut Transaction ) -> Result < ( ) , ( ) > ;
337
339
}
338
340
341
+ /// An alternative to [`CoinSelectionSource`] that can be implemented and used along [`Wallet`] to
342
+ /// provide a default implementation to [`CoinSelectionSource`].
343
+ pub trait WalletSource {
344
+ /// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
345
+ fn list_confirmed_utxos ( & self ) -> Result < Vec < Utxo > , ( ) > ;
346
+ /// Returns a script to use for change above dust resulting from a successful coin selection
347
+ /// attempt.
348
+ fn change_script ( & self ) -> Result < Script , ( ) > ;
349
+ /// Signs and provides the full witness for all inputs within the transaction known to the
350
+ /// wallet (i.e., any provided via [`WalletSource::list_confirmed_utxos`]).
351
+ fn sign_tx ( & self , tx : & mut Transaction ) -> Result < ( ) , ( ) > ;
352
+ }
353
+
354
+ /// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would
355
+ /// avoid conflicting double spends. If not enough UTXOs are available to do so, conflicting double
356
+ /// spends may happen.
357
+ pub struct Wallet < W : Deref > where W :: Target : WalletSource {
358
+ source : W ,
359
+ // TODO: Do we care about cleaning this up once the UTXOs have a confirmed spend? We can do so
360
+ // by checking whether any UTXOs that exist in the map are no longer returned in
361
+ // `list_confirmed_utxos`.
362
+ locked_utxos : Mutex < HashMap < OutPoint , BumpId > > ,
363
+ }
364
+
365
+ impl < W : Deref > Wallet < W > where W :: Target : WalletSource {
366
+ /// Returns a new instance backed by the given [`WalletSource`] that serves as an implementation
367
+ /// of [`CoinSelectionSource`].
368
+ pub fn new ( source : W ) -> Self {
369
+ Self { source, locked_utxos : Mutex :: new ( HashMap :: new ( ) ) }
370
+ }
371
+
372
+ /// Performs coin selection on the set of UTXOs obtained from
373
+ /// [`WalletSource::list_confirmed_utxos`]. Its algorithm can be described as "smallest
374
+ /// above-dust-after-spend first", with a slight twist: we may skip UTXOs that are above dust
375
+ /// after spending them at the target feerate if `force_conflicting_utxo_spend` is unset to
376
+ /// avoid producing conflicting transactions.
377
+ fn select_confirmed_utxos_internal (
378
+ & self , bump_id : BumpId , force_conflicting_utxo_spend : bool ,
379
+ target_feerate_sat_per_1000_weight : u32 , preexisting_tx_weight : u64 , target_amount : u64 ,
380
+ ) -> Result < CoinSelection , ( ) > {
381
+ let mut utxos = self . source . list_confirmed_utxos ( ) ?;
382
+ utxos. sort_unstable_by_key ( |utxo| utxo. output . value ) ;
383
+
384
+ let mut selected_amount = 0 ;
385
+ let mut fee_amount = preexisting_tx_weight * target_feerate_sat_per_1000_weight as u64 ;
386
+ let selected_utxos = {
387
+ let mut locked_utxos = self . locked_utxos . lock ( ) . unwrap ( ) ;
388
+ let selected_utxos = utxos. into_iter ( ) . scan (
389
+ ( & mut selected_amount, & mut fee_amount) , |( selected_amount, fee_amount) , utxo| {
390
+ if let Some ( utxo_bump_id) = locked_utxos. get ( & utxo. outpoint ) {
391
+ if * utxo_bump_id != bump_id && !force_conflicting_utxo_spend {
392
+ return None ;
393
+ }
394
+ }
395
+ let need_more_inputs = * * selected_amount < target_amount + * * fee_amount;
396
+ if need_more_inputs {
397
+ let fee_to_spend_utxo = target_feerate_sat_per_1000_weight as u64 *
398
+ ( ( 41 * WITNESS_SCALE_FACTOR ) as u64 + utxo. witness_weight ) ;
399
+ let utxo_value_after_fee = utxo. output . value . saturating_sub ( fee_to_spend_utxo) ;
400
+ if utxo_value_after_fee > 0 {
401
+ * * selected_amount += utxo. output . value ;
402
+ * * fee_amount += fee_to_spend_utxo;
403
+ Some ( utxo)
404
+ } else {
405
+ None
406
+ }
407
+ } else {
408
+ None
409
+ }
410
+ }
411
+ ) . collect :: < Vec < _ > > ( ) ;
412
+ let need_more_inputs = selected_amount < target_amount + fee_amount;
413
+ if need_more_inputs {
414
+ return Err ( ( ) ) ;
415
+ }
416
+ for utxo in & selected_utxos {
417
+ locked_utxos. insert ( utxo. outpoint , bump_id) ;
418
+ }
419
+ selected_utxos
420
+ } ;
421
+
422
+ let remaining_amount = selected_amount - target_amount - fee_amount;
423
+ let change_script = self . source . change_script ( ) ?;
424
+ let change_output_fee = target_feerate_sat_per_1000_weight as u64
425
+ * ( 8 + change_script. consensus_encode ( & mut sink ( ) ) . unwrap ( ) as u64 ) ;
426
+ let change_output_amount = remaining_amount. saturating_sub ( change_output_fee) ;
427
+ let change_output = if change_output_amount < change_script. dust_value ( ) . to_sat ( ) {
428
+ None
429
+ } else {
430
+ Some ( TxOut { script_pubkey : change_script, value : change_output_amount } )
431
+ } ;
432
+
433
+ Ok ( CoinSelection {
434
+ confirmed_utxos : selected_utxos,
435
+ change_output,
436
+ } )
437
+ }
438
+ }
439
+
440
+ impl < W : Deref > CoinSelectionSource for Wallet < W > where W :: Target : WalletSource {
441
+ fn select_confirmed_utxos (
442
+ & self , bump_id : BumpId , must_spend : Vec < Input > , must_pay_to : Vec < TxOut > ,
443
+ target_feerate_sat_per_1000_weight : u32 ,
444
+ ) -> Result < CoinSelection , ( ) > {
445
+ // TODO: Use fee estimation utils when we upgrade to bitcoin v0.30.0.
446
+ let base_tx_weight = 4 /* version */ + 1 /* input count */ + 1 /* output count */ + 4 /* locktime */ ;
447
+ let total_input_weight = must_spend. len ( ) *
448
+ ( 32 /* txid */ + 4 /* vout */ + 4 /* sequence */ + 1 /* script sig */ ) ;
449
+ let total_output_weight: usize = must_pay_to. iter ( ) . map ( |output|
450
+ 8 /* value */ + 1 /* script len */ + output. script_pubkey . len ( )
451
+ ) . sum ( ) ;
452
+ let total_non_witness_weight = base_tx_weight + total_input_weight + total_output_weight;
453
+ let total_witness_weight: u64 = must_spend. iter ( ) . map ( |input| input. witness_weight ) . sum ( ) ;
454
+
455
+ let preexisting_tx_weight = 2 /* segwit marker & flag */ + total_witness_weight +
456
+ ( total_non_witness_weight * WITNESS_SCALE_FACTOR ) as u64 ;
457
+ let target_amount = must_pay_to. iter ( ) . map ( |output| output. value ) . sum ( ) ;
458
+ let do_coin_selection = |force_conflicting_utxo_spend : bool | {
459
+ self . select_confirmed_utxos_internal (
460
+ bump_id, force_conflicting_utxo_spend, target_feerate_sat_per_1000_weight,
461
+ preexisting_tx_weight, target_amount,
462
+ )
463
+ } ;
464
+ do_coin_selection ( false ) . or_else ( |_| do_coin_selection ( true ) )
465
+ }
466
+
467
+ fn change_script ( & self ) -> Result < Script , ( ) > {
468
+ self . source . change_script ( )
469
+ }
470
+
471
+ fn sign_tx ( & self , tx : & mut Transaction ) -> Result < ( ) , ( ) > {
472
+ self . source . sign_tx ( tx)
473
+ }
474
+ }
475
+
339
476
/// A handler for [`Event::BumpTransaction`] events that sources confirmed UTXOs from a
340
477
/// [`CoinSelectionSource`] to fee bump transactions via Child-Pays-For-Parent (CPFP) or
341
478
/// Replace-By-Fee (RBF).
@@ -402,7 +539,6 @@ where
402
539
// input, without caring where the change goes, we use an output just above dust backed by
403
540
// the wallet's change script. If the wallet ends up producing its own change output when
404
541
// funding the transaction, we'll join them into one, saving the user a few satoshis.
405
- // TODO: Prevent change address inflation.
406
542
let change_script = self . utxo_source . change_script ( ) ?;
407
543
let dust_change_output = TxOut {
408
544
value : change_script. dust_value ( ) . to_sat ( ) ,
0 commit comments